前段时间,在和同事合作的时候看到了一段代码,scrollview 嵌套 recyclerview,然后adapter中没有处理view的复用,然后我就开始好奇这里不会出现bug吗?不过之前写代码的时候一直没注意过这种嵌套,那么,先实验一下。
activity_main.xml
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="110dp"
android:background="#EEE"
android:gravity="center"
android:text="textview" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
MyAdapter.java
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recyccler, parent, false));
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
Log.d("VODD", "item: " + position);
holder.tvItem.setText(String.format("Item:%1$d", position));
}
@Override
public int getItemCount() {
return 20;
}
protected static class MyViewHolder extends RecyclerView.ViewHolder {
TextView tvItem;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
tvItem = itemView.findViewById(R.id.tv_item);
}
}
}
代码贴在上面了,然后看log日志,毫不意外的发现,所有的view都在第一时间绘制出来了,包括屏幕外面的,因此recyclerview 完全没有复用呵呵。
2021-08-28 17:12:15.646 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 0
2021-08-28 17:12:15.649 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 1
2021-08-28 17:12:15.651 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 2
2021-08-28 17:12:15.653 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 3
2021-08-28 17:12:15.655 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 4
2021-08-28 17:12:15.658 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 5
2021-08-28 17:12:15.660 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 6
2021-08-28 17:12:15.662 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 7
2021-08-28 17:12:15.664 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 8
2021-08-28 17:12:15.666 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 9
2021-08-28 17:12:15.668 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 10
2021-08-28 17:12:15.671 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 11
2021-08-28 17:12:15.673 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 12
2021-08-28 17:12:15.675 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 13
2021-08-28 17:12:15.677 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 14
2021-08-28 17:12:15.679 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 15
2021-08-28 17:12:15.681 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 16
2021-08-28 17:12:15.683 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 17
2021-08-28 17:12:15.686 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 18
2021-08-28 17:12:15.688 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 19
那么这个地方为什么没有进行复用?我先把recyclerView 的 onMeasure 方法打印一下看看
2021-08-28 17:18:44.308 23468-23468/com.vodlee.nestedscrollrecycler D/VODD: widthSpec: 1073742904
2021-08-28 17:18:44.308 23468-23468/com.vodlee.nestedscrollrecycler D/VODD: heightSpec: 0
2021-08-28 17:18:44.318 23468-23468/com.vodlee.nestedscrollrecycler D/VODD: widthSpec: 1073742904
2021-08-28 17:18:44.318 23468-23468/com.vodlee.nestedscrollrecycler D/VODD: heightSpec: 0
通过log,发现 heightSpec 竟然是0?
为什么会是0呢,咱们看 MeasureSpec 的代码
private static final int MODE_SHIFT = 30;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
也就是只有父布局测量子布局时,传递的 mode 为 UNSPECIFIED 并且 size 为0的时候,才会出现 heightSpec 是0的情况。
而其他情况下,recyclerView 高度为固定值、match_parent 或 wrap_content 的时候,都不会出现0的情况。
这样,就会导致 recyclerView 无法得知当前屏幕展示多少个就够了。
那么问题如何解决?
方案一:通过 getItemViewType(int position) 方法,在 RecyclerView 中创建不同的 viewholder
@Override
public int getItemViewType(int position) {
return position == 0 ? 0 : 1;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == 0) {
return new HeaderViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recyccler, parent, false));
}
return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recyccler, parent, false));
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
Log.d("VODD", "item: " + position);
if (getItemViewType(position) == 0) {
((HeaderViewHolder)holder).tvItem.setText("This is Header.");
} else {
((MyViewHolder)holder).tvItem.setText(String.format("Item:%1$d", position));
}
}
Log日志如下
2021-08-28 23:22:07.298 13234-13234/com.vodlee.nestedscrollrecycler D/VODD: item: 0
2021-08-28 23:22:07.302 13234-13234/com.vodlee.nestedscrollrecycler D/VODD: item: 1
2021-08-28 23:22:07.304 13234-13234/com.vodlee.nestedscrollrecycler D/VODD: item: 2
2021-08-28 23:22:07.306 13234-13234/com.vodlee.nestedscrollrecycler D/VODD: item: 3
2021-08-28 23:22:07.308 13234-13234/com.vodlee.nestedscrollrecycler D/VODD: item: 4
方案二:通过 CoordinatorLayout + AppBarLayout 方法实现顶部布局
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#CCC"
android:gravity="center"
android:text="This is Header"
app:layout_scrollFlags="scroll" />
</com.google.android.material.appbar.AppBarLayout>
<com.vodlee.nestedscrollrecycler.MyRecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Log日志如下
2021-08-28 23:26:17.478 13973-13973/com.vodlee.nestedscrollrecycler D/VODD: item: 0
2021-08-28 23:26:17.481 13973-13973/com.vodlee.nestedscrollrecycler D/VODD: item: 1
2021-08-28 23:26:17.482 13973-13973/com.vodlee.nestedscrollrecycler D/VODD: item: 2
2021-08-28 23:26:17.484 13973-13973/com.vodlee.nestedscrollrecycler D/VODD: item: 3
2021-08-28 23:26:17.486 13973-13973/com.vodlee.nestedscrollrecycler D/VODD: item: 4
|