1. SwipRefreshLayout刷新的同时Recyclerview快速上滑或者点击item时crash
Error
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{1ab30d24 position=4 id=-1, oldPos=-1, pLpos:-1 no parent}
at android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:4801)
Reason
...
onRefreshListener = new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if (NetWorkTools.isNetworkConnected(getContext())){
mPage = 0;
mList.clear();
mIsRefreshing=true;
mThread = new GetMemberItemThread(memberRefreshHandler);
mThread.start();
} else {
Toast.makeText(getContext(),"请检查网络连接",Toast.LENGTH_SHORT).show();
refreshLayout.setRefreshing(false);
}
}
};
...
@Override
public void handleMessage(Message msg) {
MemberFragment memberFragment = refMemberFragment.get();
if (memberFragment != null){
switch (msg.what){
case Constant.HANDLER_REQUEST_MEMBERS:
mList = (List<MemberInfo>) msg.obj;
mList.addAll(cacheList);
adapter.notifyDataSetChanged();
isFirstPage = false;
canLoadMore = true;
refreshLayout.setRefreshing(false);
break;
case Constant.HANDLER_REFRESH_SEARCHLIST:
...
Analyse
SwipeRefreshView
刷新时回调onRefresh()
方法,上面的代码mList.clear()
将数据清空,然后开启线程请求数据,成功时候会通过handler
传递过来给handleMessage()
处理。由于新的线程请求数据需要一定的时间,如果数据还没请求成功此时点击item或者上滑的话, adapter
并没有调用notify**
函数通知recyclerview
更新数据,造成前后数据不一致,就会出现上面的错误。
Solve:
在mList.clear();
后面加上notifyDataChanged();
方法后问题解决。然而,这样又会造成RecyclerView
闪烁一下然后显示数据。因此,可以将mList.clear()
方法放到新数据到来之后执行。修改后的代码如下:
...
onRefreshListener = new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if (NetWorkTools.isNetworkConnected(getContext())){
mPage = 0;
isFirstPage = true;
mThread = new GetMemberItemThread(memberRefreshHandler);
mThread.start();
} else {
Toast.makeText(getContext(),"请检查网络连接",Toast.LENGTH_SHORT).show();
refreshLayout.setRefreshing(false);
}
}
};
...
@Override
public void handleMessage(Message msg) {
MemberFragment memberFragment = refMemberFragment.get();
if (memberFragment != null){
switch (msg.what){
case Constant.HANDLER_REQUEST_MEMBERS:
List<MemberInfo> cacheList;
cacheList = (List<MemberInfo>) msg.obj;
mList.addAll(cacheList);
if (isFirstPage){
adapter.setData(cacheList);
mList.clear();
mList.addAll(cacheList);
} else {
adapter.setData(mList);
}
adapter.notifyDataSetChanged();
isFirstPage = false;
canLoadMore = true;
refreshLayout.setRefreshing(false);
break;
case Constant.HANDLER_REFRESH_SEARCHLIST:
...
2.Glide加载CircleImageView时的图片时设置了 placeholder 同时设置 crossfade 时抖动闪烁,占用内存超大
图片头像用了开源的圆形 Imageview ( https://github.com/hdodenhof/CircleImageView ),但是用 Recyclerview 加载时会出现错乱的问题,就是快速滑动时在显示正确图片前会在其他图片之间跳动几次最终才显示正确。作者其实已经给出了说明:
- If you use an image loading library like Picasso or Glide, you need to disable their fade animations to avoid messed up images. For Picasso use the noFade() option, for Glide use dontAnimate(). If you want to keep the fadeIn animation, you have to fetch the image into a Target and apply a custom animation yourself when receiving the Bitmap.
- Using a TransitionDrawable with CircleImageView doesn't work properly and leads to messed up images.
就是说如果使用 Picasso 或者 Glide 的话,需要去除动画效果来避免图像混乱。如果非要使用动画的话,就要将 image 转化成 Target (在 Glide 中,Target 是 GlideDrawableImageViewTarget,BitmapImageViewTarget,DrawableImageViewTarget之一)并且自定义动画。
解决方案
- 按照作者给的方案,使用 dontAnimate(),但是这样没有淡入动画效果了。
- 不用 placeholder,这样的话就没有占位符。。。
- 最终方案是不用 crossfade(),使用自定义动画 anim(R.anim.fade_in),其实 crossfade() 函数就是创建了一个淡入动画
private static class DefaultAnimationFactory implements ViewAnimation.AnimationFactory {
private final int duration;
DefaultAnimationFactory(int duration) {
this.duration = duration;
}
@Override
public Animation build() {
AlphaAnimation animation = new AlphaAnimation(0f, 1f);
animation.setDuration(duration);
return animation;
}
}
为啥 crossfade() 就有问题呢?通过源码得知 crossfade 最终动画的执行是在DrawableCrossFadeViewAnimation
public boolean animate(T current, ViewAdapter adapter) {
Drawable previous = adapter.getCurrentDrawable();
if (previous != null) {
TransitionDrawable transitionDrawable = new TransitionDrawable(new Drawable[] { previous, current });
transitionDrawable.setCrossFadeEnabled(true);
transitionDrawable.startTransition(duration);
adapter.setDrawable(transitionDrawable);
return true;
} else {
defaultAnimation.animate(current, adapter);
return false;
}
}而自定义动画的执行是在
ViewAnimation`
@Override
public boolean animate(R current, ViewAdapter adapter) {
View view = adapter.getView();
if (view != null) {
view.clearAnimation();
Animation animation = animationFactory.build();
view.startAnimation(animation);
}
return false;
}
作者说了使用 TransitionDrawable 会混乱,具体原因没有深入去研究,如果有谁知道还望赐教。