react-native 在android封装原生listView

时间:2022-02-17 21:56:45

react-native 在android封装原生listView

前言

react-native中的ListView的性能问题是react-native开发的一个痛点。虽然0.43中推出了FlatList,但是快速滑动的时候的白屏问题仍然是很影响用户体验。
最近在项目中需要使用react-native开发相册,在经过再三的考虑后,决定直接封装原生的listView。虽然这样做使得ios、android的代码不可共用,也放弃了react-native热更新的优势,但却实实在在的拥有了原生的性能。

问题

ios轻易的完成了封装,但是android这边却遇到了坑–listView的更新实时,setAdapter或者adapter.notifyDataSetChanged后页面没有进行相应的更新,必须滚动页面后才会进行相应的刷新。

思考

作为一名程序猿,首先当然是上google去了。遗憾的是没有找到任何有用的信息,甚至没有人提出这个问题。没办法,自己撸源码去。首先看了一下listView中setAdapter和adapter.notifyDataSetChanged的源码,发现其最终都是调用requestLayout来更新页面的。
然后,我就去查了一下requestLayout到底做了什么:

在requestLayout方法中,首先先判断当前View树是否正在布局流程,接着为当前子View设置标记位,该标记位的作用就是标记了当前的View是需要进行重新布局的,接着调用mParent.requestLayout方法,这个十分重要,因为这里是向父容器请求布局,即调用父容器的requestLayout方法,为父容器添加PFLAG_FORCE_LAYOUT标记位,而父容器又会调用它的父容器的requestLayout方法,即requestLayout事件层层向上传递,直到DecorView,即根View,而根View又会传递给ViewRootImpl,也即是说子View的requestLayout事件,最终会被ViewRootImpl接收并得到处理。纵观这个向上传递的流程,其实是采用了责任链模式,即不断向上传递该事件,直到找到能处理该事件的上级,在这里,只有ViewRootImpl能够处理requestLayout事件。– Android View 深度分析requestLayout、invalidate与postInvalidate

解决

既然requestLayout是层层向上传递,那么问题就应该是在父view上了。作为一个控件,在使用中,其父view必然是一个react-native的控件。而react-native的view大部分是继承View,所以我又查看了一下VIew的原生实现ReactVIewGroup的源码,终于找到了原因所在:


@Override
public void requestLayout() {
// No-op, terminate `requestLayout` here, UIManagerModule handles laying out children and
// `layout` is called on all RN-managed views by `NativeViewHierarchyManager`
}

React-Native重写了ReactViewGroup的requestLayout方法,这使得ListView在使用requestLayout的时候无法事件无法传递到ViewRootImpl。就是说,所有的view在ReactNative都将失效。

知道问题所在,事情就好解决了。在Stack Overflow找到了解决的方法:

@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(measureAndLayout);
}

private final Runnable measureAndLayout = new Runnable() {
@Override
public void run() {
measure(
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
layout(getLeft(), getTop(), getRight(), getBottom());
}
};

http://*.com/questions/39836356/react-native-resize-custom-ui-component