Code Review - 顶部嵌入式参数面板

2018-11-25  本文已影响11人  nimw

1. 重构与开发步骤

1.1 面板动画

1.1.1 问题分析

  1. 参数面板的选择,显示/隐藏控制安全由ContentPage类控制。
showParameterView(animated, refresh) {
    setHistoryParameterPanelState(HistoryParameterState.ON);
    let model = this.contentPageModel.getQueryPaneModel();
    let type = Device.pad() && Device.isApp() ? BaseQueryPaneView.TYPE.MODEL : BaseQueryPaneView.TYPE.PUSH;
    let title = this.props.navigation.state.params.title;
    let showBackIcon = !this.isHome();
    this.queryPaneTag = BaseQueryPaneView.show(model, type, title, animated, showBackIcon, () => {
        this.queryPaneLeftPress()
    });
    refresh && this.forceUpdate();
}
  1. 参数面板tag标识在ContentPage层保存控制
isContentHide(){
    return this.queryPaneTag && BaseQueryPaneView.isOpen(this.queryPaneTag);
}
  1. 只能控制一个参数面板
close: (tag, type, animated) => {
    if (type === _QueryPaneType.PUSH) {
        BaseQueryPaneSlider.instance && BaseQueryPaneSlider.instance.close(animated);
    } else {
        Window.dismiss(tag);
    }
}
  1. phone端和pad端接口不统一
    show: (model, type, title, animated, showBackIcon, onBackPress) => {
        let tag = null;
        if (type === _QueryPaneType.PUSH) {
            tag = _showSlider(model, title, showBackIcon, onBackPress, animated);
        } else {
            tag = _showScreen(model, onBackPress);
        }
        return tag;
    },

    close: (tag, type, animated) => {
        if (type === _QueryPaneType.PUSH) {
            BaseQueryPaneSlider.instance && BaseQueryPaneSlider.instance.close(animated);
        } else {
            Window.dismiss(tag);
        }
    }
  1. pad端通过react-navigation导航实现,不支持h5
const _showScreen = (model: BaseQueryPaneModel, onBackPress: Function) => {
    let dismissScreen = () => {
        if (this.windowTag) {
            Window.dismiss(this.windowTag);
            this.windowTag = null;
            onBackPress && onBackPress(this.windowTag);
        }
    };
    dismissScreen = dismissScreen.bind(this);
    let routeConfigMap = {
        QueryPane: {
            screen: BaseQueryPaneScreen,
        },
        EditorPane: {
            screen: EditorPanelScreen
        }
    };
    let stackConfig = {
        initialRouteName: 'QueryPane',
        initialRouteParams: {
            model: model,
            title: I18N.getLocale('FR_PARAMETER_COMMIT'),
            backText: I18N.getLocale('FR_CLOSE'),
            onBackPress: dismissScreen,
            transitionConfig: () => ({
                transitionSpec: {
                    duration: 350,
                    easing: Easing.out(Easing.poly(5)),
                    timing: Animated.timing,
                },
                screenInterpolator: CardStackStyleInterpolator.forVertical
            })
        },
    };
    let navigationReducer = (preState, nextState, passedAction) => {
        if (passedAction.type === NavigationActions.NAVIGATE) {
            const editorRouteCount = nextState.routes.filter((route) => (route.routeName === 'EditorPane')).length;
            if (editorRouteCount > 1) {
                return preState;
            }
        }
        return nextState;
    };
    this.windowTag = Window.present(routeConfigMap, stackConfig, navigationReducer);

    return this.windowTag;
};
  1. 参数面板和编辑面板动画不统一
<SafeAreaView style={{paddingTop, flex: 1, backgroundColor: 'transparent'}}>
    <Animated.View style={[{height}, {
        transform: [{
            translateY: this.state.offset.interpolate({
                inputRange: [0, 1],
                outputRange: [height, 0]
            })
        }]
    }]}>
        <BaseQueryPaneView model={model}
                           title={title}
                           backText={backText}
                           backIcon={backIcon}
                           onBackPress={this._onBackPress}
                           openEditor={this._openEditor}/>
    </Animated.View>
</SafeAreaView>
static open = (model, onFinishEdit) => {
    if (!model.isEnabled()) {
        return;
    }
    if (Device.pad() && Device.isApp()) {
        EditorPanelWindow.present(model);
    } else {
        const tag = Drawer.allocateTag();
        Drawer.openDrawer(
            tag,
            <EditorPanelUI
                model={model}
                onFinishEdit={() => {
                    Drawer.closeDrawer(tag);
                    onFinishEdit && onFinishEdit();
                }}
            />,
            {
                remainWidth: MODAL_WIDTH_LEFT,
                animationType: Drawer.ANIMATION.SLIDE_IN_RIGHT,
                transparent: false,
                cancelable: true
            }
        );
    }
}

1.1.2 代码提交

  1. 代码提交记录
    FineReact / FineReactBase
  2. 提交内容介绍
    (1) FRBI参数面板代码分离。
    (2) 创建QueryManager类,管理各种类型参数面板。
    (3) QueryManager类管理参数面板的创建、显示、隐藏等。
    (4) 将参数面板渲染、显示、隐藏的逻辑从ContentPage转移到QueryManager
    (5) 分离现有两种portal类型参数面板:slideUpmodal
    (6) 重构现有slideUp参数面板,将动画层与内容层分离。
    (7) 重构modal参数面板,不再使用导航创建参数面板。
    (8) 引入有限状态机javascript-state-machine管理参数面板显示/隐藏状态。
    (9) model不直接传递给queryView,通过QueryManager接口代理传递。
    (10) 参数面板显示/隐藏与编辑面板显示/隐藏使用同一个动画组件。

1.2 面板动画控制层

1.2.1 问题分析

  1. 编辑面板渲染以及显示/隐藏逻辑耦合在BaseAnimatedQuery内部。
//QueryManager类控制参数面板
show(animated) {
    this.query.show(animated)
}

hide(animated) {
    this.query.hide(animated)
}

isShow() {
   return this.query.isShow()
}

isHide() {
    return this.query.isHide()
}

afterQueryShow = () => {
    this.options.afterQueryShow()
}

afterQueryHide = () => {
    this.options.afterQueryHide()
}
//BaseAnimatedQuery控制编辑面板
showEditor(widgetModel){
    const tag = Portal.allocateTag();
    Portal.showModal(
        tag,
        <AnimatedModal {...this.editorAnimateConfig(tag)}>
            <EditorPaneUI {...this.editorUIConfig(widgetModel, tag)}/>
        </AnimatedModal>
    )
}

hideEditor() {
    this.editorNode && this.editorNode.hide()
}
  1. 编辑面板与编辑面板header头部未统一。
//QueryPaneUI
<QueryHeader
    title={this.props.title}
    backIcon={this.props.backIcon}
    backText={this.props.backText}
    onBackPress={this._onBackPress}
 />
//EditorPaneUI
 _renderNavigationView() {
     return (
         <SafeAreaView style={{flex: 1,paddingTop: this._getPaddingTop()}}>
             <View style={[styles.navigationBar, createDefaultHeaderStyle()]}>
                 <ButtonView
                     onPress={this._clear}
                     style={styles.button}>
                     <Text style={[styles.buttonText,{color:PlatformStandard.WRONG_RED}]}>
                         {I18N.getLocale('FR_CLEAR')}
                     </Text>
                 </ButtonView>
                 <ButtonView
                     onPress={this._submitEditorValue}
                     style={styles.button}>
                     <Text style={[styles.buttonText, {color: MainBoard.getInstance().getThemeStorage().getTheme()}]}>
                         {I18N.getLocale('FR_CONFIRM')}
                     </Text>
                 </ButtonView>
             </View>
             <EditorViewContainer
                 ref={node => this.containerView = node}
                 finishEdit = {this._onFinishEdit}
                 widgetModel={this.props.model}
                 style={{flex: 1}}
             />
         </SafeAreaView>
     )
 }
  1. 在编辑面板UIconstructor里执行控件的loadData
//EditorViewContainer
constructor(props, context){
    super(props, context);
    this.submitEditorValue = this.submitEditorValue.bind(this);
    this._closeEditView = this._closeEditView.bind(this);
    this._onBackClicked = this._onBackClicked.bind(this);
    this.props.widgetModel.loadData();
}

1.2.2 代码提交

  1. 代码提交记录
    FineReactBase / FineReactBase
  2. 提交内容介绍
    (1) 创建编辑面板基类(BaseAnimatedEditor),负责管理编辑面板。
    (2) 实现ModelSlideLeft两种编辑面板。
    (3) 创建通用头部组件HeaderUI
    (4) 参数面板与编辑面板使用HeaderUI组件。
    (5) 在编辑面板的afterEditorShow钩子函数中加载控件数据。

1.3 引入顶部参数面板

1.3.1 问题分析

  1. 导航头部在FormPage层渲染,但却在FormView层计算高度。
//FormPage
_getTopBarHeight() {
    return this.isZoomIn ? 0 : (FormView.getHeaderHeight(this._isHeaderAbsolute()));
}
//FormView
static getHeaderHeight(isAbsolute) {
    return ((Orientation.isPortrait() || Device.pad()) && isAbsolute)
        ? (APPBAR_HEIGHT + (Device.isIphoneX() ? 0 : Device.getStatusBarHeight(false)))
        : 0;
}

1.3.2 代码提交

  1. 代码提交记录
    FineReact / FineReactBase
  2. 提交内容介绍
    (1) 参数面板类型管理以及Embed视图渲染。
    (2) 适配顶部参数面板视图对横竖屏切换、组件放大的影响。

1.4 支持不同动画配置信息

1.4.1 问题分析

  1. 面板控制过程代码重复
    //BaseAnimatedQuery
    show(animated = false) {
        this.isHide() && this.queryState.show(animated)
    }

    isShow() {
       return this.queryState.is('显示')
    }

    hide(animated) {
        this.isShow() && this.queryState.hide(animated)
    }

    isHide() {
        return this.queryState.is('隐藏')
    }

    _initQueryState() {
        let self = this;
        this.queryState = new StateMachine({
            init: '隐藏',
            transitions: [
                { name: 'show', from: '隐藏', to: '显示' },
                { name: 'hide', from: '显示', to: '隐藏' },
            ],
            methods: {
                onShow(state, animated) {
                    self.showQuery(animated)
                },
                onHide(state, animated) {
                    self.hideQuery(animated)
                }
            }
        })
    }
    //BaseAnimatedEditor
    show(widgetModel, animated) {
        this.isHide() && this.editorState.show(widgetModel, animated)
    }

    isShow() {
        return this.editorState.is('显示')
    }

    hide(animated) {
        this.isShow() && this.editorState.hide(animated)
    }

    isHide() {
        return this.editorState.is('隐藏')
    }

    _initEditorState() {
        let self = this;
        this.editorState = new StateMachine({
            init: '隐藏',
            transitions: [
                { name: 'show', from: '隐藏', to: '显示' },
                { name: 'hide', from: '显示', to: '隐藏' },
            ],
            methods: {
                onShow(state, widgetModel, animated) {
                    self.showEditor(widgetModel, animated)
                },
                onHide(state, animated) {
                    self.hideEditor(animated)
                }
            }
        })
    }
  1. 参数面板编辑面板配置信息类似
   //ModelPortalQuery
    queryAnimateConfig(tag, animated) {
        return {
            ...super.queryAnimateConfig(tag, animated),
            showType: 'fadeIn',
            hideType: 'fadeOut',
            containerStyle: styles.containerStyle,
            contentStyle: ModelPortalQuery.getContentStyle()
        }
    }

    static getContentStyle() {
        const {height} = Device.getScreenSize(Orientation.getCurrentOrientation());
        return {
            width: 540,
            borderRadius: 4,
            backgroundColor: 'white',
            height: Math.min(620, height * 0.8),
        }
    }
    //ModalPortalEditor
    editorAnimateConfig(tag, animated) {
        return {
            ...super.editorAnimateConfig(tag, animated),
            transparent: true,
            showType: 'bounceInRight',
            hideType:'bounceOutRight',
            containerStyle: styles.containerStyle,
            contentStyle: ModelPortalEditor.getContentStyle(),
        }
    }

    static getContentStyle() {
        const {height} = Device.getScreenSize(Orientation.getCurrentOrientation());
        return {
            width: 540,
            borderRadius: 4,
            backgroundColor: 'white',
            height: Math.min(620, height * 0.8),
        }
    }
  1. 开发过程遇到的问题


    image.png

    (1) 如何对不同控件弹出不同编辑面板?
    (2) 如何弹出非全屏编辑面板?
    (3) 顶部参数面板的嵌入式视图层级?

1.4.2 代码提交

  1. 代码提交记录
    FineReact / FineReactBase
  2. 提交内容介绍
    (1) 创建AnimatedController类,统一管理面板动画。
    (2) 删除BaseAnimatedQueryBaseAnimatedEditor中管理面板状态的代码。
    (3) 编辑面板删除状态管理相关代码之后太简单了,没必要存在。
    (4) 细化showshowEditor接口,支持自定义配置和只渲染视图。
//显示参数面板(默认配置)
show = (animated = false)
//显示参数面板(自定义配置)
showWithConfig(uiConfig, animateConfigFn)
//获取参数面板模态层(不含全屏Portal),该接口还没用到。
renderQueryModal(uiConfig, animateConfigFn)

//显示编辑面板(默认配置)
showEditor(model, animated = true) 
//显示编辑面板(自定义配置)
showEditorWithConfig(uiConfig, animateConfigFn)
//获取编辑面板模态层(不含全屏Portal)
renderEditorModal(uiConfig, animateConfigFn) 

(5) 修改顶部参数面板内嵌节点的视图结构。
(6) 实现顶部嵌入式参数面板。

  1. 问题
    (1) 为什么1.2.2中创建了编辑面板相关类,1.4.2中又删除了。
    (2) 参数面板需要创建几个AnimatedController
    this.query = AnimatedController.create()
    this.editor = AnimatedController.create()
    (3) 如果只有之前的slideUpModel两种参数面板,面板展示(showshowWithConfigrenderQueryModal)接口设计到哪种细度比较合适。

2. 经验总结

  1. 将目光聚焦在眼前的一小步,添加新功能与重构过程应该交替进行,而不是同时进行。
  2. 代码结构应尽量与现有功能点匹配,合理设计而不需要过度设计。
上一篇下一篇

猜你喜欢

热点阅读