打算写一个系列了,讲解Android Support包内那些经常使用or冷门有用的工具类的合集。javascript
最近leader在优化IM会话列表,同事之前的作法是无脑notifyDatasetChanged()刷新RecyclerView的。
在消息聊得很嗨不少的时候,界面频繁刷新,会话列表会出现丢失焦点现象。且性能毕竟不高。
遂想采用定向刷新。java
同事知道我之前研究过DiffUtil和定向刷新相关内容,因而便和我讨论。android
(不知道DiffUtil的点这里)blog.csdn.net/zxt0601/art…git
(不了解定向刷新的点这里)blog.csdn.net/zxt0601/art…github
因为IM会话列表是从数据库里读的,他还告诉我会有数据集重复的现象,且会话列表确定是按时间排序的,因此这对咱们的数据组织提出了两点要求:有序、去重。数据库
个人想法是:服务器
利用Set自己元素不重复的特性,加之Tree的有序性,来解决数据组织的两个需求。数据结构
但是leader不知道从哪搜出来一个SortedList,告诉我这是Android SDK提供的。也能够完成排序and去重。
我心说这是哪路神仙,我觉得是JDK给的呢,因而也查阅了一番资料,遂揭开了SortedList的神秘面纱,也有了今天的文章。app
转载请标明出处:
gold.xitu.io/post/58416f…
本文出自:【张旭童的稀土掘金】(gold.xitu.io/user/56de21…)
代码传送门:喜欢的话,随手点个star。多谢
github.com/mcxtzhang/S…异步
源码头注释以下:
A Sorted list implementation that can keep items in order and also notify for changes in the list。
翻译:
一个有序列表(数据集)的实现,能够保持ItemData都是有序的,并(自动)通知列表(RecyclerView)(数据集)中的更改。
人话:
首先它是一种数据结构,是一个有序的List,list改变后(增删改查),也能够一直保持数据集List有序,而且会自动调用adapter中定向更新notifyXXXX方法,更新RecyclerView。
对了,它还会自动去重。
关键点:
搭配RecyclerView使用,去重,有序,自动定向刷新
刚看到这里,我以为这特么自动定向刷新这一点特性,怎么有点像DiffUtil,后来我查阅资料才发现,这家伙出来的比DiffUtil要早,是在Support Library 22 引入的。因此说应该是DiffUtil像它。
并且SortedList 和 DiffUtil 内部 都实现、持有了一些共同的接口,暴漏出供咱们重写比较规则的Callback的方法名都几乎一毛同样。
我我的感受SortedList从设计上和DiffUtil比,是有一点点不足,这可能也是官方后来又在Support Library 24 中引入DiffUtil的一个理由吧。具体异同,稍后总结。先看怎么用吧。
咱们来看看若是使用SortedList该怎么写:
要写RecyclerView,就少不了Adapter。
一个常规的Adapter内部通常持有一个List<T>
的数据集,
使用SortedList
的话,须要将存储数据源的变量类型改变成SortedList,
其余的话,倒没有变化,由于SortedList虽然没有继承自List,可是暴漏出API还和List同样的。
public class SortedAdapter extends RecyclerView.Adapter<SortedAdapter.VH> {
/** * 数据源替换为SortedList, * 之前可能会用ArrayList。 */
private SortedList<TestSortBean> mDatas;
...
public SortedAdapter(Context mContext, SortedList<TestSortBean> mDatas) {
this.mContext = mContext;
this.mDatas = mDatas;
mInflater = LayoutInflater.from(mContext);
}
public void setDatas(SortedList<TestSortBean> mDatas) {
this.mDatas = mDatas;
}
@Override
public SortedAdapter.VH onCreateViewHolder(ViewGroup parent, int viewType) {
return new SortedAdapter.VH(mInflater.inflate(R.layout.item_diff, parent, false));
}
@Override
public void onBindViewHolder(final SortedAdapter.VH holder, final int position) {
TestSortBean bean = mDatas.get(position);
holder.tv1.setText(bean.getName());
holder.tv2.setText(bean.getId() + "");
holder.iv.setImageResource(bean.getIcon());
}
...
}复制代码
无任何修改,就是一个普通的实体类。与上文DiffUtil里的同样。
看过DiffUtil详解的同窗对这个Callback的编写和理解就易如反掌了,编写规则和套路和DiffUtil.Callback同样。
并且还少写一个方法public Object getChangePayload(int oldItemPosition, int newItemPosition)
,这里顺带复习一下上文内容,这个方法返回 一个 表明着新老item的改变内容的 payload对象
这里说远一点,关于这个少写的方法,正是定向刷新中部分绑定(Partial bind)的核心方法。DiffUtil
是利用这个getChangePayload()
方法的返回值,做为第三个参数,回调ListUpdateCallback
接口里的void onChanged(int position, int count, Object payload);
方法,最终回调adapter.notifyItemRangeChanged(position, count, payload);
方法,再往下就走到Adapter的三参数onBindViewHolder(VH holder, int position, List<Object> payloads)
方法,也就是咱们部分绑定所操做的地方了,不太明白的能够去看DiffUtil详解.
除此以外,耶不用传新旧数据集进来了,里面的每一个方法都是直接传入ItemData进行比较。
那么咱们的SortedList的Callback以下编写:
public class SortedListCallback extends SortedListAdapterCallback<TestSortBean> {
public SortedListCallback(RecyclerView.Adapter adapter) {
super(adapter);
}
/** * 把它当成equals 方法就好 */
@Override
public int compare(TestSortBean o1, TestSortBean o2) {
return o1.getId() - o2.getId();
}
/** * 和DiffUtil方法一致,用来判断 两个对象是不是相同的Item。 */
@Override
public boolean areItemsTheSame(TestSortBean item1, TestSortBean item2) {
return item1.getId() == item2.getId();
}
/** * 和DiffUtil方法一致,返回false,表明Item内容改变。会回调mCallback.onChanged()方法; */
@Override
public boolean areContentsTheSame(TestSortBean oldItem, TestSortBean newItem) {
//默认相同 有一个不一样就是不一样
if (oldItem.getId() != newItem.getId()) {
return false;
}
if (oldItem.getName().equals(newItem.getName())) {
return false;
}
if (oldItem.getIcon() != newItem.getIcon()) {
return false;
}
return true;
}
}复制代码
Activity的编写也没啥大变化,区别以下:
public class SortedListActivity extends AppCompatActivity {
/** * 数据源替换为SortedList, * 之前可能会用ArrayList。 */
private SortedList<TestSortBean> mDatas;
private RecyclerView mRv;
private SortedAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sorted_list);
mRv = (RecyclerView) findViewById(R.id.rv);
mRv.setLayoutManager(new LinearLayoutManager(this));
//★之前构建Adapter时,通常会将data也一块儿传入,如今有变化
mAdapter = new SortedAdapter(this, null);
mRv.setAdapter(mAdapter);
initData();
mAdapter.setDatas(mDatas);
}
private void initData() {
//★SortedList初始化的时候,要将Adapter传进来。因此先构建Adapter,再构建SortedList
mDatas = new SortedList<>(TestSortBean.class, new SortedListCallback(mAdapter));
mDatas.add(new TestSortBean(10, "Android", R.drawable.pic1));
//★注意这里有一个重复的字段 会自动去重的。
mDatas.add(new TestSortBean(10, "Android重复", R.drawable.pic1));
mDatas.add(new TestSortBean(2, "Java", R.drawable.pic2));
mDatas.add(new TestSortBean(30, "背锅", R.drawable.pic3));
mDatas.add(new TestSortBean(4, "手撕产品", R.drawable.pic4));
mDatas.add(new TestSortBean(50, "手撕测试", R.drawable.pic5));
}复制代码
代码写到这里,界面就能够正常显示了。效果如Gif图。
能够看到虽然咱们add进去的数据 是有重复的,顺序也是乱序的。
可是列表界面依然按照id的升序显示。
到这就完了吗,尚未说到自动定向刷新呢。
和DiffUtil两步完成定向刷新比,SortedList这一点真的是很强。0步完成自动定向刷新。
在上述代码的基础上,若是此时查询数据库,发现有一条新的IM聊天信息,那么直接add()
进来便可:
add 内部会自动调用 mCallback.onInserted(index, 1)
->notifyItemRangeInserted(index,1)
也就是说咱们add一次 它就notify一次,没有batch操做,有点low
mDatas.add(new TestSortBean(26, "温油对待产品", R.drawable.pic6));//模拟新增
mDatas.add(new TestSortBean(12, "小马能够来点赞了", R.drawable.pic6));//模拟新增
mDatas.add(new TestSortBean(2, "Python", R.drawable.pic6));//add进去 重复的会自动修改复制代码
若是是一坨消息,能够用addAll()
,查看源码,它内部会自动作Batch操做,beginBatchedUpdates();
和endBatchedUpdates();
。因此若是想batch,就必须用addAll()操做,感受这算一个限制。
//addAll 也分两种
//第一种 以可变参数addAll
//mDatas.addAll(new TestSortBean(26, "帅", R.drawable.pic6),new TestSortBean(27, "帅", R.drawable.pic6));
//第二种 集合形式
List<TestSortBean> temp = new ArrayList<>();
temp.add(new TestSortBean(26, "帅", R.drawable.pic6));
temp.add(new TestSortBean(28, "帅", R.drawable.pic6));
mDatas.addAll(temp);复制代码
而若是是刷新的场景,可能就不太适用了,刷新时,服务器给咱们的通常都是一个List,直接addAll 要先clear, 会闪屏:
List<TestSortBean> newDatas = new ArrayList<>();
for (int i = 0; i < mDatas.size(); i++) {
try {
newDatas.add(mDatas.get(i).clone());//clone一遍旧数据 ,模拟刷新操做
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
newDatas.add(new TestSortBean(29, "帅", R.drawable.pic6));//模拟新增数据
newDatas.get(0).setName("Android+");
newDatas.get(0).setIcon(R.drawable.pic7);//模拟修改数据
TestSortBean testBean = newDatas.get(1);//模拟数据位移
newDatas.remove(testBean);
newDatas.add(testBean);
mDatas.clear();
mDatas.addAll(newDatas);复制代码
查看源码,SortedList是在每次add()
、addAll()
、clear()
.....等对数据集进行增删改查的函数里,都会进行一遍排序和去重。这排序和去重显然是个耗时操做。那么我想说能不能用异步处理呢?丢在子线程中。
因而我以下写:
//每次add都会计算一次 想放在子线程中
new Thread(new Runnable() {
@Override
public void run() {
mDatas.add(new TestSortBean(26, "帅", R.drawable.pic6));//模拟新增数据
mDatas.add(new TestSortBean(27, "帅", R.drawable.pic6));//模拟新增数据
}
}).start();
}复制代码
然而这是确定不行的,上文提过,每次add 会自动 mAdapter.notifyItemRangeInserted(position, count);
在线程中操做UI,会android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
这一点就不如DiffUtil啦。
它们两真的很像,并且不论是DiffUtil
计算出Diff后,仍是SortedList
修改过数据后,内部持有的回调接口都是同一个:android.support.v7.util.ListUpdateCallback
:
/** * An interface that can receive Update operations that are applied to a list. * <p> * This class can be used together with DiffUtil to detect changes between two lists. */
public interface ListUpdateCallback {
void onInserted(int position, int count);
void onRemoved(int position, int count);
void onMoved(int fromPosition, int toPosition);
void onChanged(int position, int count, Object payload);
}复制代码
我就不解析源码了,很是简单,大体流程就是:DiffUtil
计算出Diff
或者SortedList
察觉出数据集有改变后,在合适的时机,回调ListUpdateCallback
接口的这四个方法,DiffUtil
和SortedList
提供的默认Callback实现中,都会通知Adapter完成定向刷新。
这就是自动定向刷新的原理。
总结一下它们的异同吧:
使用SortedList的话,Adapter的保存数据集的变量类型要改变。
对代码有侵入性,没有热插拔的快感。
在项目中有各类BaseAdapter的前提下,可能要扩展一种BaseSortedListAdater更方便使用。
只不过它的目的不是在定向刷新,而是维护数据集的 有序 & 去重 。
顺带有一个定向刷新的功能。
而DiffUtil主打的就是 比较集合的差别,更是帮咱们自动完成定向刷新。
因此SortedList 不适用于 服务器给全部数据过来的,下拉刷新状况。此时不如使用普通的List。
它的亮点和核心,仍是在于 有序 & 去重 。
且它也不支持 部分绑定(Partial bind)。
但它在特定场景下,例如 数据集每次更新时是增量更新,且须要维持一个排序规则的时候,就像城市列表界面,仍是给咱们带来了必定的便利之处的。
转载请标明出处:
gold.xitu.io/post/58416f…
本文出自:【张旭童的稀土掘金】(gold.xitu.io/user/56de21…)
代码传送门:喜欢的话,随手点个star。多谢
github.com/mcxtzhang/S…