RecyclerView面试问答

RecycleView 和 ListView对比:
使用方法上
ListView:继承重写 BaseAdapter,自定义 ViewHolder 与 converView优化。

RecyclerView: 继承重写 RecyclerView.Adapter 与 RecyclerView.ViewHolder。设置 LayoutManager 来展示不同的布局样式

ViewHolder的编写规范化,ListView是需要自己定义的,而RecyclerView是规范好的;
RecyclerView复用item全部搞定,不需要像ListView那样setTag()与getTag();
RecyclerView多了一些LayoutManager工作,但实现了布局效果多样化;
2. 动画api
在RecyclerView中自带动画效果,例如:notifyItemChanged(), notifyDataInserted(), notifyItemMoved()等等;同时内置有许多动画API,如果需要自定义动画效果,可以通过实现(RecyclerView.ItemAnimator类)完成自定义动画效果,然后调用RecyclerView.setItemAnimator();
但是ListView并没有实现动画效果,需要在Adapter自己自定义;
3. 缓存区别
ListView和RecyclerView最大的区别在于数据源改变时的缓存的处理逻辑,ListView有二级缓存,
RecycleBin在layout的过程中便于view重用,RecycleBin有两级存储:ActiveViews和ScrapViews。
ActiveViews存储的是layout开始的时候屏幕上那些view。layout结束后,所有ActiveViews中的view被移动到ScrapViews中。
ScrapViews中的views是那些可能被adapter重新用到的view,以避免重新创建不必要的view。
而RecyclerView则是更加灵活地采用了四级缓存。
在这里插入图片描述

  • RecyclerView缓存的item的复用机制
    触摸角度
    在这里插入图片描述
    layout角度的话少一个scrapView的调用
    在这里插入图片描述
    在这里插入图片描述

设置缓存相关代码方法

//设置mCahceView的缓存数量
RecyclerView.setItemViewCacheSize

 public abstract static class ViewCacheExtension {
        @Nullable
        public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
                int type);
    }
//自定义缓存ImageCacheExtension继承RecyclerView.ViewCacheExtension,一般不自己写,你写了之后就不会走onBindView了,只会走getViewForPositionAndType,你可以从onBindView方法调用你的缓存方法,毕竟这个方法对于同个Viewholder的第二次回调只会发生在recyclerPool中,如果优先了ViewCacheExtension,那就只会走自己的缓存。
ImageCacheExtension cacheExtension = new ImageCacheExtension();

//自定义Viewholder的缓存和获取缓存
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setViewCacheExtension(cacheExtension);

recyclerView.setAdapter(adapter);

RecyclerViewPool

RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool();
RecyclerView.ViewHolder type1Holder = new Type1ViewHolder(LayoutInflater.from(this).inflate(R.layout.item_type_1, null));
//设置pool的类型为0的数量为10,默认mMaxScrap是5
pool.setMaxRecycledViews(0, 10);
pool.putRecycledView(type1Holder);

每种类型的heap超过了pool指定类型的heap就return了

        /**
         * Add a scrap ViewHolder to the pool.
         * <p>
         * If the pool is already full for that ViewHolder's type, it will be immediately discarded.
         *
         * @param scrap ViewHolder to be added to the pool.
         */
        public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType();
            final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
            if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
                //超过数量就不要了,callPoolingContainerOnRelease只是回调onRelease监听的接口PoolingContainer.callPoolingContainerOnRelease(scrap.itemView);
                return;
            }
            if (DEBUG && scrapHeap.contains(scrap)) {
                throw new IllegalArgumentException("this scrap item already exists");
            }
            scrap.resetInternal();
            scrapHeap.add(scrap);
        }
  • scrap
    scrap是用来保存被rv移除掉但最近又马上要使用的缓存,比如说rv中自带item的动画效果本质上就是计算item的偏移量然后执行属性动画的过程,这中间可能就涉及到需要将动画之前的item保存下位置信息(pre-layout),动画后的item再保存下位置信息(post-Layout),然后利用这些位置数据生成相应的属性动画。如何保存这些viewholer呢,就需要使用到scrap了,因为这些viewholer数据上是没有改变的,只是位置改变而已,所以放置到scrap最为合适。

稍微仔细看的话就能发现scrap缓存有两个成员mChangedScrap和mAttachedScrap,它们保存的对象有些不一样,一般调用adapter的notifyItemRangeChanged被移除的viewholder会保存到mChangedScrap,其余的notify系列方法(不包括notifyDataSetChanged)移除的viewholder会被保存到mAttachedScrap中。

简单来说就是执行Notify动画时,notifyItemRangeChanged移除的ViewHolder会放入mChangedScrap,其余动画(不包括notifyDataSetChanged)移除的ViewHolder会保存到mAttachedScrap

        void scrapView(View view) {
            final ViewHolder holder = getChildViewHolderInt(view);
            if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
                    || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
                if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
                    throw new IllegalArgumentException("Called scrap view with an invalid view."
                            + " Invalid views cannot be reused from scrap, they should rebound from"
                            + " recycler pool." + exceptionLabel());
                }
                holder.setScrapContainer(this, false);
               
                mAttachedScrap.add(holder);
            } else {
                if (mChangedScrap == null) {
                    mChangedScrap = new ArrayList<ViewHolder>();
                }
                holder.setScrapContainer(this, true);
                mChangedScrap.add(holder);
            }
        }

  • RecyclerView.setFixSize(true)

onItemRangeChanged()
onItemRangeInserted()
onItemRangeRemoved()
onItemRangeMoved()

这样看就很明白了,当调用Adapter的增删改插方法,最后就会根据mHasFixedSize这个值来判断需要不需要requestLayout();所以这4个方法不会重新绘制。
那我们再来看一下notifyDataSetChanged()执行的代码,最后是调用了onChanged,调用了requestLayout(),会去重新测量宽高,所以这也是为什么我们设置为true时,大小还是重新计算了的原因。

    void triggerUpdateProcessor() {
            if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
                ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
            } else {
                mAdapterUpdateDuringMeasure = true;
                requestLayout();
            }
        }
  • Adapter.setHasStableIds
    使itemview的焦点固定,解决了闪烁问题。直接缓存到mChangedScrap中,从mChangedScrap中获取id关联的viewholder

  • onViewRecycled、onDetachedFromRecyclerView 和 onViewDetachedFromWindow。这三者有什么区别?

https://www.coder.work/article/655498
onDetachedFromRecyclerView(RecyclerView recyclerView) - 当 RecyclerView 停止观察此适配器时由 RecyclerView 调用。
你可能没有注意到,在这个方法之前总是调用一个匹配方法:
onAttachedToRecyclerView(RecyclerView recyclerView) - 当 RecyclerView 开始观察这个 Adapter 时调用。
当您调用 recyclerView.setAdapter(adapter) 时,adapter 会收到对 onAttachedToRecyclerView(recyclerView) 的调用。然后调用 recyclerView.setAdapter(null) 将触发 adapter 的 onDetachedFromRecyclerView(recyclerView)。
除了某些特殊情况(比如保持对观察到的 recyclerView 的计数等)之外,您通常不需要重写此方法。
onViewRecycled(VH holder)更简单,它在将viewHolder发送到recycleViewPool之前调用。
您可以将其视为 onBindViewHolder(VH holder, int position) 的“清理”方法.
onViewDetachedFromWindow(VH holder) VH从窗口中移除
onViewAttachedToWindow(VH holder) VH添加到窗口中

  • 控制RecyclerView Item的宽度,setSpanSizeLookup
GridLayoutManager mLayoutManager = new GridLayoutManager(this, 3);
mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
	@Override
	public int getSpanSize(int position) {
	if (position == mAdapter.getItemCount() - 1) {
			return 2;
		} else {
			return 1;
		}
	}
});
  • getLayoutPosition与getAdapterPosition(已过期,最新getBindingAdapterPosition)区别详解
    在使用mList.remove(pos);notifyItemRemove(pos);
    用来进行列表的Item删除的时候,多次点击删除按钮,getLayoutPosition会变化,例如从9变到8;
    getAdapterPositiion remove过程中会出现-1的情况
    所以应该使用getAdapterPositiion然后判断是-1就return

  • 在RecyclerView的元素比较高,一屏只能显示一个元素的时候,第一次滑动到第二个元素会卡顿。这种情况就可以通过设置额外的缓存空间,重写getExtraLayoutSpace方法即可。

new LinearLayoutManager(this) {
    @Override
    protected int getExtraLayoutSpace(RecyclerView.State state) {
        return size;
    }
};

  • 预拉取机制与缓存复用机制的怎么协作的?
    https://mp.weixin.qq.com/s/zEpmDXBxfVqKB5fsndao5A
    「可复用ViewHolder对象」是添加到「预拉取ViewHolder对象」前面的,
    如下图,往上滑动,由于预拉取机制,在帧间的空隙将position6添加进cacheView,然后刷新的时候从cacheView获取,继续上划,position7添加进cacheView,当postion0完全不见的时候,也会添加进cacheView,因为预加载机制,LinearLayoutManager的cacheViewSize会从默认的2变成3,postion0存入cacheView,排在预加载的postion6前面,相当于移到recyclePool的话优先级更高
    在这里插入图片描述
    假设此时需要添加复用的ViewHolder,则会查找最早的prefetch的position,添加到它前面
    请添加图片描述
    在这里插入图片描述
// 添加之前,先移除最老的一个ViewHolder对象
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {   // 当前已经放满
    recycleCachedViewAt(0); // 移除mCachedView结构中的第1个
    cachedViewSize--;   // 总数减1
}

// 默认从尾部添加
int targetCacheIndex = cachedViewSize;
// 处理预拉取的情况
if (ALLOW_THREAD_GAP_WORK
        && cachedViewSize > 0
        && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
    // 从最后一个开始,跳过所有最近预拉取的对象排在其前面
    int cacheIndex = cachedViewSize - 1;
    while (cacheIndex >= 0) {
        int cachedPos = mCachedViews.get(cacheIndex).mPosition;
        // 添加到最近一个非预拉取的对象后面
        if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
            break;
        }
        cacheIndex--;
    }
    targetCacheIndex = cacheIndex + 1;
}
mCachedViews.add(targetCacheIndex, holder);

https://blog.csdn.net/jdsjlzx/article/details/129700461

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值