最近封装了一个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去进行重绘
解决方案:
在自定义 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