美文网首页
Code Review - 顶部嵌入式参数面板

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

作者: nimw | 来源:发表于2018-11-25 23:31 被阅读11次

    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. 代码结构应尽量与现有功能点匹配,合理设计而不需要过度设计。

    相关文章

      网友评论

          本文标题:Code Review - 顶部嵌入式参数面板

          本文链接:https://www.haomeiwen.com/subject/rctvqqtx.html