RecyclerView进行数据更新时焦点丢失的解决方案
在使用RecyclerView时,难免会用到adapter的notifyDataSetChanged方法来更新数据,其实notify**Changed系列方法都存在一个已知的焦点丢失的bug,如果在notify之后重新手动requestFocus,又会导致焦点可能不对应的问题。
可以使用如下步骤规避此问题:
1.为adapter提供stableId:
stableId在Listview和RecyclerView中都被用来定位一个item,官方建议使用item本身的hashcode或item内部的属性的hashcode,来唯一标识一个item,因此,需要在adapter中复写此方法:
@Override
public long getItemId(int position) {
return position;
}
更简单一点,可以如上代码所示直接return item的position。
2.设置item的setHasStableIds()为true:
adapter.setHasStableIds(true);
在listview中也有复写此方法的方案。
3.在官方推荐的做法来看,这两步已经可以保存recyclerview的焦点了,但是使用notify系列方法仍然会出现focus丢失的问题,现在官方已经承认这是一个bug,可以暂时使用禁用notifyDataSetChanged动画的方法来规避:
mRecyclerView.setItemAnimator(null);
这种写法禁用了所有的item动画,你也可以只禁用notifyDataSetChanged来实现同样的效果。
更多关于stableId的讨论请前往[http://stackoverflow.com/questions/10267731/android-how-to-make-an-adapter-with-stable-ids]
更多关于禁用动画的方法请前往:[http://stackoverflow.com/questions/29873859/how-to-implement-itemanimator-of-recyclerview-to-disable-the-animation-of-notify]
关于第三步提到的bug请前往:[https://code.google.com/p/android/issues/detail?id=204277]
网友评论
如果你是用鼠标或者手势滑出去的,其实应该在一开始发生touchEvent的时候就让所有view失去焦点,因为这时候已经切换到了TouchMode,焦点只在非TouchMode才有意义
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
super.onLayoutChildren(recycler, state);
Log.e(TAG, "onLayoutChildren: didStructureChange : " + state.didStructureChange() + " willRunPredictiveAnimations:" + state.willRunPredictiveAnimations() + " willRunSimpleAnimations:" + state.willRunSimpleAnimations() + " isPreLayout:" + state.isPreLayout() );
// notifyDataSetChange() 完成後呼叫 :
if(state.didStructureChange() && !state.willRunSimpleAnimations() && !state.willRunPredictiveAnimations() && !state.isPreLayout()){
// 這邊呼叫 recyclerView.getChildAt(position).requestFocusFromTouch();
// recyclerView = 你的recyclerView
// position = 你要focus的 item 位置
}
}
};
recyclerView.setLayoutManager(gm); // 可以換成 其他layoutManager
目前我自己的做法是:
将右边RecyclerView放到一个Viewgroup中,这个ViewGroup的 focusable="true",当你要刷新右边RecyclerView的时候,记录当前焦点位置,手动让这个ViewGroup requestFocus,然后等刷新结束,scrollTo上次记录的位置,再获取对应View,然后再手动RequestFocus