React-native 封装安卓UI原生控件刷新无效的解决办法

最近封装了一个RecyclerView的rn组件,然而在原生下跑demo无比正常的情况下却在rn环境发生了没有刷新的情况,于是乎有了这一篇的发现。
我们了解完安卓下的requestLayout机制和RN下的requestLayout的对应调整,就可以淡定的修复该bug了。n(≧▽≦)n

安卓的 requestLayout 机制

  • 第一步

当一个 View 调用 requestLayout 的时候,会给当前的 View 设置一个FORCE_LAYOUT 标记,由此向 ViewParent 请求布局

便从这个 View 开始向上一直 requestLayout 直到 ViewRootImpl

ViewParent 就是当前的传输链

  • 第二步:

ViewRootImpl 发现请求了布局。那么就会调用 measure 去确认当前 View 是否有FORCE_LAYOUT标记

如果有,那么就会进行重新 measure。并且设置标记 LAYOUT_REQUIRED

  • 第三步

在随后的 layout 方法中,会判断这个标记。如果这个标记为true,就一定会调用onLayout

onLayout调用后会清理LAYOUT_REQUIRED标记

layout调用之后再清理掉FORCE_LAYOUT标记

只要调用了requestLayout, 那么measure、onMeasure、layout、onLayout、draw、onDraw都会被调用。

React-native下 的 requestLayout 对应的调整

react-native 为了提升安卓的性能,将 requestLayout 的执行过程统一通过UIManagerModule 来管理,而原 ReactViewGroup 内的 requestLayout 被重写为空方法,就等同于将该 ViewGroup 包裹下的所有子元素的requestLayout都被拦截,无法通知到ViewRootImpl去进行重绘


14269618-b7f2e1a118258744.jpg
ReactViewGroup的requestLayout实现

解决方案:

在自定义 View 中重写 requestLayout 方法如下

@Override
public void requestLayout() {
    super.requestLayout();
    // The spinner relies on a measure + layout pass happening after it calls requestLayout().
    // Without this, the widget never actually changes the selection and doesn't call the
    // appropriate listeners. Since we override onLayout in our ViewGroups, a layout pass never
    // happens after a call to requestLayout, so we simulate one here.
    post(new Runnable(){
            @Override
            public void run() {
                // 就差一个 draw 重绘了
                measure(
                        MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY)
                );
                layout(getLeft(), getTop(), getRight(), getBottom());
                // 自定义样式处理
            }
    });
}

通过post方法异步去主动调用layout来实现刷新
该方案详细见 ----> stackoverflow React Native: Resize custom UI component

假如你封装的组件继承自ViewGroupMananger,在对应的createViewInstance内添加如下代码

Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
            child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
        }
        getViewTreeObserver().dispatchOnGlobalLayout();
    }
    Choreographer.getInstance().postFrameCallback(this);
});

该方案详细见 ----> react-native的Github issue#17968

猜你喜欢

转载自blog.csdn.net/weixin_33722405/article/details/87591473
今日推荐