列表視圖在app中是很是常見的,目前React Native比較嚴重的性能問題集中在FlatList大列表等地方,如下經過js層的優化,甚至原生層的優化封裝,使性能媲美原生。java
React Native 0.43版本推出FlatList替代ListView,FlatList實現繼承自VirtualizedList,底層的VirtualizedList提供更高的靈活性,但使用便捷性不如FlatList,如無特殊需求沒法知足直接使用FlatList。VirtualizedList實現繼承自ScrollView,因此FlatList繼承了VirtualizedList和ScrollView所有的props,在查閱相關文檔時,如在FlatList中找不到相應的prop或者方法可使用另外兩個組件的。React Native的FlatList與android listview、ios uitableview類似,將屏幕外的視圖組件回收,達到高性能的目的。react
如下實例代碼均使用typescriptandroid
<FlatList<number>
// 數據數組
data={[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
// key
keyExtractor={(item, index) => index.toString()}
// item渲染
renderItem={({item: num}) => (
<Text>{num}</Text>
)}
/>
複製代碼
有除data之外的數據用在列表中,在此屬性中指定,不然界面極可能不會刷新ios
設置爲 true 則變爲水平佈局模式web
翻轉滾動方向,多用於聊天列表之類反向展現數據typescript
指定一列顯示多少個item數組
滑動到視圖底部bash
滑動到指定位置app
滑動到指定像素async
<FlatList
// 上拉回調
onEndReached={() => console.log('上拉加載')}
// 滑動到最後視圖內容比例,設置爲0-1,例如0.5則表示滑到最後一個視圖一半開始回調
onEndReachedThreshold={0.1}
/>
複製代碼
<FlatList
// true顯示刷新組件
refreshing={this.state.refreshing}
// 下拉回調
onRefresh=(async () => {
this.setState({
refreshing: true
});
await 耗時操做
this.setState({
refreshing: false
});
});
/>
複製代碼
手指按下開始滑動,調用一次,用於監聽交互開始
手指滑動,調用屢次
手指鬆開,調用一次,開始慣性滾動,用於監聽交互結束
慣性滾動開始,調用一次,用於監聽滑動慣性動畫開始
慣性滾動結束,調用一次,用於監聽滑動慣性動畫結束
滑動中,調用屢次,用於監聽滑動位置
開始滑動,調用一次,用於監聽滑動開始
滑動結束,調用一次,用於監聽滑動結束
用以開發簡單輪播視圖,分頁滑動查看內容等
// 當前視圖索引
private index = 0;
// 必須與this綁定,不然拋出異常
private viewabilityConfig = {viewAreaCoveragePercentThreshold: 100};
handleViewableItemsChanged = (info: { viewableItems: Array<ViewToken>; changed: Array<ViewToken>}) => {
// index爲當前可見視圖在view的索引
this.index = info.changed[0].index!;
}
<FlatList
// 每次滑動後一個item停留在整個視圖
pagingEnabled={true}
// 可見視圖設置,1-100,50表示一半可見時回調,100表示所有可見時回調
viewabilityConfig={this.viewabilityConfig}
// 可見視圖變動回調
onViewableItemsChanged={this.handleViewableItemsChanged}
// onViewableItemsChanged會屢次回調,監聽慣性滑動結束判斷分頁滑動結束,如須要實時判斷視圖索引顯示,則直接使用onViewableItemsChanged
onMomentumScrollEnd={() => console.log('滑動至', this.index)}
/>
複製代碼
移除在屏幕外組件,默認爲true,對性能有最大的影響,不要修改成false
保持視圖個數,即在屏幕外也不移除,默認值爲11,在高耗性能組件中,能夠適當設置小的值,在會快速滑動的視圖中,設置大的值如300,避免快速滑動後當前視圖尚未渲染出現空白。
獲取高度,如視圖高度固定,設置該屬性能夠大大改善性能,避免了渲染過程當中每一次都須要從新計算視圖高度。
getItemLayout={(data, index) => ({length: height, offset: height * index, index})}
合理設置key提升react對組件的複用,能很大的優化性能,在組件移出屏幕外,被回收後複用。
在要求極高的列表視圖中,數據達上千甚至上萬,在部分狀況FlatList已經沒法知足,特別是android設備。如下介紹如何直接使用原生android RecyclerView視圖來完成高要求的列表視圖。
public class MyFlatListManager extends SimpleViewManager<MyFlatListManager.MyRecyclerView> {
// 自定義RecyclerView
public static class MyRecyclerView extends RecyclerView {
// 數據列表
public List<Data> list = new ArrayList<>();
// 適配器
public MyAdapter myAdapter;
// 佈局管理器
public LinearLayoutManager mLayoutManager;
public MyRecyclerView(Context context) {
super(context);
myAdapter = new MyAdapter(this, list);
// 設置爲垂直方向
mLayoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false);
setLayoutManager(mLayoutManager);
// 固定高度避免從新測量,提升性能
setHasFixedSize(true);
// 禁止數據變動時動畫,避免閃爍
setItemAnimator(null);
setAdapter(myAdapter);
}
@Override
public void requestLayout() {
super.requestLayout();
// react native android根視圖requestLayout爲空函數,避免加入新視圖沒法顯示或者高度寬度不正確,手動執行測量
post(measureAndLayout);
}
public final Runnable measureAndLayout = new Runnable() {
@Override
public void run() {
measure(
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
Log.d(TAG, "measureAndLayout");
layout(getLeft(), getTop(), getRight(), getBottom());
}
};
}
private static class MyViewHolder extends RecyclerView.ViewHolder {
public MyViewHolder(View itemView) {
super(itemView);
}
}
private static class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
private List<MyViewHolder> holders;
private List<Data> list;
private MyRecyclerView recyclerView;
public MyAdapter(MyRecyclerView recyclerView, List<VideoInfo> list) {
this.list = list;
this.holders = new ArrayList<>();
this.recyclerView = recyclerView;
}
// 視圖建立
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.movie_list_row, parent, false);
// 手動從新設置高度,match parent
itemView.getLayoutParams().height = parent.getHeight();
itemView.getLayoutParams().width = parent.getWidth();
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
Data data = list.get(position);
// Log.i(TAG, "setTag " + position);
holder.itemView.setTag(position);
// 綁定視圖數據
}
@Override
public int getItemCount() {
return list.size();
}
}
private static final String TAG = "MyFlatListViewManager";
@Override
public String getName() {
return "MyFlatListViewManager";
}
@Override
protected MyRecyclerView createViewInstance(final ThemedReactContext reactContext) {
return new MyRecyclerView(reactContext);
}
@Nullable
@Override
public Map<String, Integer> getCommandsMap() {
Map<String, Integer> commandsMap = new HashMap<>();
commandsMap.put("addData", 1);
return commandsMap;
}
@Override
public void receiveCommand(MyRecyclerView root, int commandId, @Nullable ReadableArray args) {
MyAdapter myAdapter = (MyAdapter) root.getAdapter();
switch (commandId) {
case 1:
if (args == null) return;
Log.i(TAG, "addData size: " + args.size());
Integer position = root.list.size();
for (int i = 0; i < args.size(); i++) {
// 初始化值,getData爲從map中獲取data的函數,自行根據結構實現
Data data = getData(args.getMap(i));
Log.i(TAG, "add data " + data);
root.list.add(data);
}
Log.i(TAG, "addDatas old position " + position + " size " + args.size());
// 通知變動
myAdapter.notifyItemRangeInserted(position, args.size());
break;
}
}
}
複製代碼
須要注意的有幾個地方
在原生組件和js層進行props傳遞,如數據量太大,使用props直接傳遞已經不合適,數據可能已經達到幾m甚至更大。react的props模式已經再也不適合這樣的場景,在web中也是,大量的數據每一次單個數據的變動都所有從新傳遞,會致使嚴重的性能問題。在這種狀況下,使用組件ref調用函數來一個一個添加或者一個一個移除相關數組這些大的對象,會很好的提高性能。在android的代碼中,再也不使用prop傳遞FlatList的data,而是使用add的方法來添加,而後在js層再進行一層的原生組件封裝,讓使用與其餘組件一致。