创建了2倍的viewHolder
假设只显示了n个item
1. 人们通常认为recyclerView最多只缓存n + 2个viewHolder,这是错误的,正确的答案是n * 2 + 2
解释,当item显示完全后,如果再次调用notifyItemChanged,那么会再次创建一个viewHolder,之后调用notifyItemChanged才不会调用createViewHolder, 关键是,这里notify之后调用的bindViewHolder是轮流使用这两个viewHolder(针对同一个item)
怎么做到只缓存n + 2个viewHolder,调用方法setItemAnimator(null), 动画的末尾会改变一个标志,这个标志,会影响mState.mRunPredictiveAnimations,这个如果为true,那么会多走一次mLayout.onLayoutChildren(mRecycler, mState); 如下代码
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
// anather method
boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
mState.mRunSimpleAnimations = mFirstLayoutComplete && mItemAnimator != null &&
(mDataSetHasChangedAfterLayout || animationTypeSupported ||
mLayout.mRequestedSimpleAnimations) &&
(!mDataSetHasChangedAfterLayout || mAdapter.hasStableIds());
mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations &&
animationTypeSupported && !mDataSetHasChangedAfterLayout &&
predictiveItemAnimationsEnabled();
如果不用动画,那么和listView又有什么区别呢
scroll监听无效
滚动(scrollToPositionWithOffset), 当滚动到最后一页的时候,最上方显示了一个item的一半,假设显示的是6,5,4 和3的一半那么此时如果我要滚动到3,让3完全显示,6不完全显示,此时不会触发scroll监听,但是布局是有改变的,即以人的常识认为是有滑动的,详情如下代码
//dispatchLayout
if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
dispatchOnScrolled(0, 0);
}
private boolean didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout) {
int count = mChildHelper.getChildCount();
if (count == 0) {
return minPositionPreLayout != 0 || maxPositionPreLayout != 0;
}
// 判断当前布局的view的位置,是否和原来的不一样
for (int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore()) {
continue;
}
final int pos = holder.getLayoutPosition();
if (pos < minPositionPreLayout || pos > maxPositionPreLayout) {
return true;
}
}
return false;
}
scrollToPosition()问题
如果界面上显示了该位置的item,则界面不会滚动,改为调用scrollToPositionWithOffset方法
smoothScrollToPosition
同样存在上面的问题,分析了大半天源码,发现,这里可以这样修改自定义LinearSmoothScroller,重写如下方法,并且自定义LinearLayoutManager,重写smoothScrollToPosition方法,方法内使用的scroller替换成自定义的scroller
这里是从上往下布局,暂时没有发现改动造成的不适,可能从下往上布局会有影响,待验证;其余影响暂时只发现一点
当调用smothScroll的时候,不能notifyInset,会有无法插入的现象,即不会调用onbindViewHolder方法
具体可参考我的博文
protected int getVerticalSnapPreference() {
// return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :
// mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;
// // TODO: 2017/2/28 这里修改是为了view已经在界面上存在的时候,也能够滚动
return SNAP_TO_START;
}
notifyItemChanged无效
有一个需求先把item展开出来,再显示具体的数据,从逻辑上来说,通用逻辑就是
expandItem->showData,也就是说我在presenter中调用了这两个方法,然后ui中每个方法都notifyItemChanged一次,最终只bindViewHolder了一次(第二次notify)
有人会说,你非要分两个方法那能怪谁,是的,不能怪谁,只是想逻辑上细分,也就是说所谓的单元化,或者说单一功能
两个position
viewHolder有连个getPosiiton的方法,一个是getAdapterPosition,一个是getLayoutPosition,到底该用哪个,我并不能分得清,在调试中,这两个值一直是相等的。
复用的处理比较复杂
当然了,并不是只有viewHolder有这种情况
考虑一下情景:
item0中有一个点赞按钮,假设访问服务用了30秒,
在第5秒的时候,我滚动了recyclerView,此时item0对应的viewHolder被复用,
第7秒我再滚动回来,此时item0对应的viewHolder不一定是原来的viewHolder,假设是viewHolder1。
假设网络超时,那么在访问结束的时候,我要重置item0的显示,也就是viewHolder1的显示,
也就是说,我操作了viewHolder0,服务返回的时候,要修改的是viewHolder1。
那么这个逻辑,如果要顺序处理,那么会比较复杂,特别是如果用了mvp,操作从v传到p,数据从p传到m,然后又要从m传会p,此时要处理的是哪一个视图,不管怎么,数据总是要来回传递的,至于视图….总之要考虑许多问题。
当然解决办法也有,神奇的eventBus(虽然不建议使用,但这里确实非常好用),
只需要服务返回之后发送一个event,然后界面接受到之后更新数据对应的item就行了。
不过有个问题,recyclerView更新item的动画会有一个闪一下的效果,非常不爽, 产品说必须去掉,我只更新这个操作的按钮不久行了?
然而,都没能获得viewHolder1的引用,如果更新这个按钮,所以得绕过去。修改更新item动画,简单,自定义动画,把default的复制过来,设置时间变短就行了。
最后欢迎关注我的微信公众号:云端看大地