上述小诗先逗比一下,接下来切入正题;
自从RecyclerView的诞生起,人们就为她贴上了高贵的标签;她灵活华丽高度可定制,而另一边ListView确已是明日黄花;人们趋之若鹜的奔向了RecyclerView赞美她吹捧她似乎一切的一切都尽在他们的掌握中;各大技术论坛开始讲解RecyclerView是多么的好用;于是乎我也加入了使用RecyclerView的行列中,哦不!是混入了;
说实在的我不愿意从头开始讲解她是如何工作,如何建立Adapter;因为早就有人写了相关用法的文章;再此我也不会赘述,我就当你会基本的用法了;如果还不知道她的基本用法可移至Android RecyclerView 使用完全解析 体验艺术般的控件,正所谓前人种树后人乘凉相信我们也从这些前辈的博客中学到了不少;
使用流程大概总结如下:
RecyclerView.setLayoutManager(layout);
LinearLayoutManager:线性布局通常就是替换ListView布局
GridLayoutManager:表格布局通常就是替换GridView布局
StaggeredGridLayoutManager:这个布局比较灵活可以用来替换ListView,GridView还可以做出瀑布流的效果;
RecyclerView.setAdapter(adapter)
RecyclerView.setItemAnimator(new DefaultItemAnimator());
RecyclerView.addItemDecoration(new DividerItemDecoration(
getActivity(), DividerItemDecoration.HORIZONTAL_LIST));
(ps:分割线我觉得可以设置视图的margin来实现,或者自己添加边框;用这种方式做分割线反而是个累赘,当然还是根据实际情况来;)
看过不少RecyclerView的例子,大多是一些静态的测试环境;这样一看我们会认为RecyclerView果真是个好东西;于是乎我也心动了,我在上一篇文章中Android学习之优化美女图片浏览器中使用了GridView决定要替换为 RecyclerView并实现瀑布流的效果;想想还是有些小激动的,替换实现之分分钟搞定,下面是测试截图;
看了RecyclerView的使用后,我分分钟加入了上次写的项目中,本以为可以万事大吉;但是结果总是不会差强人意;当然我也怀疑过也会质疑应该没那么简单,果真遵循了墨菲定律;索性程序还是跑起来了,至少没有崩溃(ps:其实崩溃才是最好解决,哈哈:)
下面进入看图说话
上面两幅图中分别出现了两个bug
通过notifyDataSetChanged()方法后滑动到顶部图片会出现移位的效果
下拉到底部自动加载的时候全屏图片闪动(ps:相信看了我上一篇文章的同学已经知道如何避免下拉加载时整个页面闪动的问题,这里我同样采用了那种方式却失败了)
由于上面的两个bug,我们已经猜出了个大概;可能得原因视图在刷新后相同位置的视图对象被替换了,这样导致他内部存储的唯一标识改变了;
下面我将对ListView和RecyclerView进行一个比较:
测试代码如下,我打印对象id看相同位置的TextView对象是否一致;
RecyclerView测试
public void onBindViewHolder(MyViewHolder holder,
final int position) {
if (position == 0) {
Log.e(TAG, "position:" + position + " tv:" + holder.tv.toString());
}else {
Log.i(TAG, "position:" + position + " tv:" + holder.tv.toString());
}
holder.tv.setText(mDatas.get(position));
}
还是进入看图说话,我经过两次刷新视图得到如上图的log信息;你会发现视图没有移动,只是刷新之后相应位置的TextView对象都已经改变了,我想这种情况我是不能容忍的;心中有万千草泥马奔腾而来,Google怎么会犯如此问题呢?(ps:哈哈,我也想是Google的问题,但是往往是我们自己的问题)
ListView测试
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.item_simple, parent,false);
holder.tv = (TextView) convertView.findViewById(R.id.textView);
convertView.setTag(holder);
}else {
holder = (ViewHolder) convertView.getTag();
}
if (position == 0) {
Log.e(TAG, "position:" + position + " tv:" + holder.tv.toString());
}else {
Log.i(TAG, "position:" + position + " tv:" + holder.tv.toString());
}
holder.tv.setText(getItem(position));
return convertView;
}
还是还是看图说话,发现没有看到没有这是ListView在进过两次刷新之后的结果;相同位置多次刷新之后可以完美的返回相同的对象,这样的好处是我们无需重复赋值;具体思路是可以将唯一标识保存在对象中,刷新之后我们比较标识就可以知道是否改变;
“有了新欢就抛弃了旧爱,我勤勤恳恳任劳任怨多年,最终等到的却是被遗弃的命运(@deprecated),等到你们被伤的遍体鳞伤的时候别来找我,哼…”
——ListView的告白
怎么办难道我的与时俱进就止步于此了吗,我不甘心;好吧,只能去看官方API了;
功夫不负有心人终于让我找到了他,我就说Google不会这么坑吗?是谁说的拖出去斩了…:)
RecyclerView.java
/**
* Returns true if this adapter publishes a unique <code>long</code> value that can
* act as a key for the item at a given position in the data set. If that item is relocated
* in the data set, the ID returned for that item should be the same.
*
* @return true if this adapter‘s items have stable IDs
*/
public final boolean hasStableIds() {
return mHasStableIds;
}
上述代码出自RecyclerView的源码描述,注释的大体意思是我们要重写getItemId方法给他一个不同位置的唯一标识,并且hasStableIds返回true的时候应该返回相同的数据集;
接下来我们复写getItemId()方法,并且设置setHasStableIds(true); (ps:在构造函数中设置)
好吧,眼泪不停落下… 返回对象惊人的一致…
接下来我们继续研究API:
以上是RecyclerView.Adapter的刷新数据的API,看到没如今notifyDataSetChanged()方法似乎显得黯然失色了;以上为我们提供了:
更新1-n条数据;
插入1-n条数据;
删除1-n条数据;
具体用法我想大家看方法名称应该就会使用了,无需解释;
首先我们改进原有使用的方式:
下拉加载数据我们可以使用notifyItemRangeChanged(int, int)或者notifyItemRangeInserted(int, int) 这个方法,这样也避免了整个页面的全部刷新问题,也无需自己判断是否原来的View了;
尽量使用固定ID,这样可以正确返回position的位置否则我们可能在使用了notifyItemInserted(int)或者notifyItemRangeInserted(int, int) 的时候position出现异常
疑惑:
照Google文档描述在使用了notifyItemInserted时候原来位置的position会加1,但是事实证明数据的position位置毫无变化;可是当我设置了使用了固定ID之后,位置确实动态变化了但是插入却出现了问题,虽有动画但是结构没有变化,可是notifyItemRemoved方法却是可用的;
不设置固定ID,但是要让他position正确
public void addData(int pos){
mDatas.add(pos,"Insert One");
notifyItemInserted(pos);
// 加入如下代码保证position的位置正确性
if (pos != mDatas.size() - 1) {
notifyItemRangeChanged(pos, mDatas.size() - pos);
}
}
public void deleteData(int pos){
mDatas.remove(pos);
notifyItemRemoved(pos);
// 加入如下代码保证position的位置正确性
if (pos != mDatas.size() - 1) {
notifyItemRangeChanged(pos, mDatas.size() - pos);
}
}
我们主要是对于插入删除之后手动更新position未变的位置,当时这样会导致新的对象创建;
设置固定ID,就无需担心position的问题了
虽然设置了固定ID无需担心position的问题,可是要记得这该死的墨菲定律 ;增加一项会在原来的位置不断增加,而删除正常;what’s a fucking bug!这个令人匪夷所思的问题很遗憾的告诉大家如今我也不知道是为什么,可是值得庆幸是删除没问题;你要知道我们在列表中增加一项和删除一项哪个用的频率会高一些呢?(ps:如大家有更好的办法也欢迎留言)
3. 这个是官方的建议,有两种事件:数据更新和结构变化,但是不管是哪种方式他总是最有效和保险的方式,他能让我们得到正确的position;也就是针对上面第二条时,我们不确定position是否正确时我们可以在最后在调用他一次;
如今采用新的方式之后感觉还是不错的,但是我们其实还有一个问题没有解决就是第一幅图刷新之后回到顶部图片还是会移动位置,我没有办法找到解决办法可能是由于它瀑布流本生的问题;可是我们有替代方案就是让recyclerview刷新之后滚动到顶部RecyclerView.scrollToPosition(0);这样也算是间接解决了这个问题;
玫瑰虽美可是往往我们没有注意到她的刺,美不美香不香只有试过才知道切不可人云亦云;好了写到这里算是告一段落了;TMD截图真的好累…
原文地址:http://blog.csdn.net/xiaozhiwz/article/details/46664719