# RecyclerView

# 基本结构

  • **RecyclerView:**RecyclerView本身是一个ViewGroup, 只是作为一个父view来填充子view。在这个类中有内部类LayoutManager、Recycler、ViewHolder、Adapter分别实现他的布局管理和数据复用、UI展示。

  • **LayoutManager:**布局管理器LayoutManager才真正负责RecyclerView的填充、回收、测量、布局、滚动的逻辑。在源码中,RecyclerView的measure测量方法和layout布局方法实际上调用了LayoutManager的测量和布局方法。LayoutManager接管了RecyclerView的测量和布局流程。

  • **Recycler:**Recycler管理ViewHolder,负责ViewHolder的回收和复用,并实现4级缓存。4级缓存分别是屏幕内缓存、屏幕外缓存、自定义缓存、缓存池缓存。 LayoutManager向Recycler要ViewHolder的时候,会依次从 屏幕内->屏幕外->自定义->缓存池获取。 获取不到就会走onCreateViewHolder方法来创建ViewHolder。

    1. 屏幕内缓存和屏幕外缓存通过position来获取,有就直接拿出来用,不会走onBindViewHolder方法来重新绑定数据,刷新展示。 屏幕外缓存的默认个数是2个。满了之后就会把原先添加进来的缓存到缓存池缓存中,再缓存后添加进来到自身。

    2. 自定义缓存是需要开发者自己来实现的一个扩展缓存。 用于其他缓存不能够满足需求的时候使用。 比如通过position来获取, 但是需要刷新数据的复用情况。

    3. 缓存池是通过ViewType来获取和存储ViewHolder的,这个VeiwType是int值。通过SparseArray进行键值对存储。默认一个VeiwType类型存储5个。超出之后就会把之前添加的移除后再添加。

  • **ViewHolder内容:**里面存储的字段:itemView、mPosition、mItemViewType

  • **Adapter:**Adapter的作用就是将data数据,ItemView视图转换成ViewHolder。 对应上面说的onCreateViewHolder和onBindViewHolder方法。 并在数据刷新更新的时候刷新数据。

  • **Adapter的数据刷新:**通过观察者模式实现,在调用Adapter.notifyDataSetChanged中,AdapterDataObservable这个被观察者调用了每一个观察者AdapterDataObserver的onChange方法,在这个方法中,调用了RecyclerView的重新测量和布局,就回到了上面的LayoutManager接管RecyclerView的测量布局的流程上了。

# 优化

  • 减少item的布局的嵌套层级、 减少ViewType的类型
  • 根据业务需求配置缓存机制,如配置来回滑动的业务,配置屏幕外的缓存个数多些。如,内容不变,位置不变的view,自定义缓存机制实现create和bind只走一次
  • 优化bind方法、 注意耗时操作、点击事件,业务数据计算(可在model中定义一个字段来避免重复计算)。因为bind方法在ViewHolder复用,刷新数据时会被再次调用
  • item高度如果是固定的,可以设置给recyclerview.setHasFixedSize,优化测量性能
  • 注意使用notifyDataSetChanged、局部修改可用notifyItemChanged这样的方法。

# 和ListView的不同

  • 缓存机制的不同

    ListView只有两级缓存,屏幕内缓存和屏幕外缓存。 RecyclerView有4级缓存,可参考上面。

  • 复用对象的不同

    ListView回收复用的View、RecyclerView回收复用的ViewHolder。因为ListView缓存的对象是View,在每次复用View时,都要通过View的findViewById来获取控件,非常消耗性能。

  • 整体实现结构上的不同

    ListView的测量、布局、滚动、缓存等等这些逻辑都是在ListView这一个类里面处理。 RecyclerView把这些逻辑抽离出来,托管给布局管理器、缓存管理器处理。这样扩展性更强,方便实现不同方式的列表布局和缓存。

  • 一些API接口上的不同

    ListView可以直接设置列表的headerView,footerView和emptyView。RecyclerView没有对应的方法。在滚动监听上二者所提供方法也不同。

# 自定义布局管理器

自定义布局管理是很强大也经常用到的功能。 当系统自带的线性布局、表格布局满足不了需求的时候,就需要自定义布局管理器了。 甚至很多可以用其他方式实现的功能,也可以通过自定义布局管理器来完成。比如,左右滑动的画廊效果,上下自动滚动的跑马灯效果,流式文本展示效果、抖音上下翻动效果,探探左右滑动选择卡片效果。

# 自定义布局管理的步骤
  • 集成RecyclerView.LayoutManager,重写generateDefaultLayoutParams方法,这个方法返回的参数表示子view的布局方式,是warp还是match。 一般返回warp即可。

  • 重写onLayoutChildren来实现第一次加载时,填充子view的逻辑。

  • 重写canScrollVertically返回true来支持垂直滚动,重写scrollVerticallyBy来实现,view随手势上下滑的逻辑和滚动时子view的回收和填充逻辑。当然对应水平滑动的方法是,canScrollHorizontally和scrollHorizontallyBy。

自定义的管理器的难点主要在于view的填充和回收逻辑。

# 开发遇到的问题

进来之后自动滚动, recyclerview抢占焦点导致, 在布局添加:

android:focusable="true"
android:focusableInTouchMode="true"

# 添加间距

  binding.rv.addItemDecoration(getItemDecoration());


    private RecyclerView.ItemDecoration getItemDecoration() {
        return new RecyclerView.ItemDecoration() {
            @Override
            public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
//                if (parent.getChildAdapterPosition(view) == 0){ //给第一位的item设置50上边距
//                    outRect.top = 50;
//                    return;
//                }
                if (parent.getChildAdapterPosition(view) == state.getItemCount() -1 ){ //给最后一位的item设置50下边距
                    outRect.bottom = 80;
                     return;
                }
            }
        };
    }

# grid布局间距

int spanCount = 3; // 3 columns
int spacing = 10; // 50px
boolean includeEdge = false;
binding.rv.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge));



public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {

    private int spanCount; //列数
    private int spacing; //间隔
    private boolean includeEdge; //是否包含边缘

    public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
        this.spanCount = spanCount;
        this.spacing = spacing;
        this.includeEdge = includeEdge;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {

        //这里是关键,需要根据你有几列来判断
        int position = parent.getChildAdapterPosition(view); // item position
        int column = position % spanCount; // item column

        if (includeEdge) {
            outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
            outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)

            if (position < spanCount) { // top edge
                outRect.top = spacing;
            }
            outRect.bottom = spacing; // item bottom
        } else {
            outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
            outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f /    spanCount) * spacing)
            if (position >= spanCount) {
                outRect.top = spacing; // item top
            }
        }
    }
}

# 表格布局最后一行设置间距

       android:paddingBottom="10dp"
       android:clipToPadding="false"

# 设置分割线

        int color = ContextCompat.getColor(this, R.color.bg_f2);
        binding.rv.addItemDecoration(CommItemDecoration.createVertical(this, color, 1));

# 关闭拉伸效果

  android:overScrollMode="never"

# RecyclerView做聊天页面

# 弹出键盘时,recyclerview内容被遮挡

https://www.cnblogs.com/liyiran/p/7490740.html

# 初始化adapter

public void initAdapter(WishAdapter.ItemLongClick itemLongClick) {
    mAdapter = new WishAdapter();
    mAdapter.setItemLongClick(itemLongClick);
    mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
        @Override
        public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
            WishDetailActivity.start(ActivityManager.getInstance().getActivity(WishHomeActivity.class), mAdapter.getData().get(position).getMemberWorshipId());
        }
    });
    
binding.rv.setLayoutManager(new GridLayoutManager(this, 2));
binding.rv.setAdapter(viewModel.mAdapter);
}

Last Updated: 1/9/2024, 11:22:13 AM