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