recycleView实在太重要了,在一个app中需要使用的列表的地方太多了,需要实现的样式也是千奇百怪,recycleView本身有非常的灵活,所以需要我们灵活掌握的知识点非常多,这里一一记录下来
RecyclerView 必知必会
Android内存优化(使用SparseArray和ArrayMap代替HashMap)
鸿洋大神的RecyclerView 的入门教程)
一个RV的系列教程,必看,有侧滑,城市列表
[李诗雨]
(http://blog.csdn.net/cjm2484836553?viewmode=contents)
有几篇RN的文章说的很和我胃口
Android中使用RecyclerView + SnapHelper实现类似ViewPager效果
GitHub上开源的几个 SnapHelper:RecyclerViewSnap
使用 RecyclerView 实现 Gallery 画廊效果,并控制 Item 停留位置
LinearSnapHelper 的 一个坑
snapHelper中评有个 bug,item 是第一个或是 i最后一个时其实是不会自动到中间的,这就造成了 RV 一直是易懂状态,这时会吃掉所有的触摸事件的,bug 就在这里
学RV你必须关心的大神
核心的必看文章
-
Android 复杂的列表视图新写法
这个是基于github上很火的一个开源框架:multiType,提供了非常友好,简单的api可以快速实现RV多item类型的实现 - free4600同学的多类型的 adapter支持库,借鉴了 multiType
- 深入理解 RecyclerView 系列之一:ItemDecoration 详解准确的介绍了decoration分割线的实现
涉及知识点
- 基类,baseAdapter/baseViewHolder
- 新的数据容器,SparseArray/ArrayMap
- layoutManage 布局管理
- itemDecoration 分割线
- itemAnimator 展示,添加,删除数据动画
- multiType 列表多类型展示
- header、footer 头和尾视图的优雅添加,方式由好几种呢
- Diffutil 官方在7.0中新添加的特性,简称列表数据的增量更新,和动画配合的很好,让列表更新数据的交互非常优雅
- supper25.0.1中新添加的 snapHelper,帮助 RV 实现 VP 的效果,在卡片式浏览和照片浏览时有更好的体验,本质是帮助 RV 实现 item 的对齐方式
**RecyclerVie常用设置如下 **
mRecyclerView = findView(R.id.id_recyclerview);
//设置布局管理器
mRecyclerView.setLayoutManager(layout);
//设置adapter
mRecyclerView.setAdapter(adapter)
//设置Item增加、移除动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//添加分割线
mRecyclerView.addItemDecoration(new DividerItemDecoration(
getActivity(), DividerItemDecoration.HORIZONTAL_LIST));
从类名上看,我们就可以了解RecyclerView代表的意义,我只管Recycler View,也就是说RecyclerView只管回收与复用View,其他的你可以自己去设置。可以看出其高度的解耦,给予你充分的定制自由(所以你才可以轻松的通过这个控件实现ListView,GirdView,瀑布流等效果)。
封装adapter基类
现在有很多将RV接力封装的文章,仁者见仁,智者见智,我这里就按照自己的喜好来了。
我的习惯是封装adapter基类,就不再对viewholder进行封装了,有些文章对viewholder进行封装,提供根据view的id和view.class获取view并赋值,我不喜欢这样,这样写我觉得不灵活。其实本质上还是findViewByID,省不下多少代码,主要是我看着乱~~
好了言归正传,看下代码,这是快捷创建adapter的代码
public class ZBaseClassAdapter extends ZBaseAdapter<String> {
private static final int TYPE1 = 1;
private static final int TYPE2 = 2;
public ZBaseClassAdapter(List<String> data) {
super(data);
}
@Override
public int getItemViewType(int position) {
if (position % 2 == 0) {
return TYPE1;
}
return TYPE2;
}
@Override
protected RecyclerView.ViewHolder getViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case TYPE1:
return new BaseClassViewHolder1(getView(parent, R.layout.layout_item_baseclassviewholder1));
case TYPE2:
return new BaseClassViewHolder2(getView(parent, R.layout.layout_item_baseclassviewholder2));
}
return null;
}
@Override
protected void setData(RecyclerView.ViewHolder holder, int position, String s) {
if (TYPE1 == getItemViewType(position)) {
((BaseClassViewHolder1) holder).tv_name.setText(s);
return;
}
if (TYPE2 == getItemViewType(position)) {
((BaseClassViewHolder2) holder).tv_name.setText(s);
return;
}
}
}
那么这个是封装的adapter的基类,功能大伙可以自己网上添加,这里只做教学,抛砖引玉,给大伙带个思路,不要拿搬砖拍我啊~~
public abstract class ZBaseAdapter<T extends Object> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
protected List<T> data;
/**
* 公共方法,根据id获取view对象
*
* @param parent
* @param layoutId
* @return
*/
public static View getView(ViewGroup parent, int layoutId) {
return LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
}
public ZBaseAdapter(List<T> data) {
this.data = data;
}
public List<T> getData() {
return data;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return getViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
setData(holder, position, data.get(position));
}
@Override
public int getItemCount() {
return data == null ? 0 : data.size();
}
/**
* 返回viewholder对象,支持多类型item
*
* @param parent
* @param viewType
* @return
*/
protected abstract RecyclerView.ViewHolder getViewHolder(ViewGroup parent, int viewType);
/**
* 设置数据,支持多类型item
*
* @param holder
* @param position
* @param t
*/
protected abstract void setData(RecyclerView.ViewHolder holder, int position, T t);
我的adapter抽象类是支持多类型item的,在实际开发中多类型item占据列表开发量的比重是很多的,快速实现一个支持多类型item的adapter只需要实现3个方法:
- 获取item类型,这是系统的方法,单类型item的列表就不用写了
@Override
public int getItemViewType(int position) {
// 简单写下,实际开发是根据data.get(position)的数据
if (position % 2 == 0) {
return TYPE1;
}
return TYPE2;
}
- 根据item类型,返回viewholder对象,getView()是封装的工具方法
@Override
protected RecyclerView.ViewHolder getViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case TYPE1:
return new BaseClassViewHolder1(getView(parent, R.layout.layout_item_baseclassviewholder1));
case TYPE2:
return new BaseClassViewHolder2(getView(parent, R.layout.layout_item_baseclassviewholder2));
}
return null;
}
- 设置数据
@Override
protected void setData(RecyclerView.ViewHolder holder, int position, String s) {
if (TYPE1 == getItemViewType(position)) {
((BaseClassViewHolder1) holder).tv_name.setText(s);
return;
}
if (TYPE2 == getItemViewType(position)) {
((BaseClassViewHolder2) holder).tv_name.setText(s);
return;
}
}
代码其实很简单,没什么好说的,大伙都能看的明白。时间很匆忙,代码写的烂,大家见量!
decoration 分割线
对于分割线是比较残念的,官方不提供便捷实现了,而是改成提供实现接口,自由是自由了,但是也为大家带来了不变,总结了下,目前我了解的有4种实现decoration分割线的思路
- 在item布局中设置外边距
这个不细说了,大伙应该都试过,刚做项目时我就是这么干的,啊哈哈哈~~ - 使用官方提供的默认实现DividerItemDecoration
这个有个问题是,没一个item都会绘制分割线,手动控制不了 - 给RV设置背景+实现itemDecoration的设置边距方法[getItemOffsets()]
这个好实现,看看被人写的一下就能明白吗,一般用的挺多的,没见过uI把列表的分割线设计成变态颜色的,一般都是白灰2色 - 自己用提供的canvas画
这种实现就有些麻烦了,对于一些新手来说,使用canvas和计算坐标都是挺麻烦的事,看别人代码也不好动,但是这样是最灵活的
具体的代码我就不贴了,太多了,大家去我的项目代码里面看吧,我写的都很简单,甚至都简单的过分了
不过还是要简单科普一下:
我们要实现自己的decoration,需要写一个类集成官方提供的RecyclerView.ItemDecoration 抽象类
这个类包含 三个方法 :
onDraw(Canvas c, RecyclerView parent, State state)
onDrawOver(Canvas c, RecyclerView parent, State state)
getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
执行顺序是 先执行onDraw,再绘制childView,再执行onDrawOver
onDraw和onDrawOver都是用canvas来自己画的,getItemOffsets是设置每个item边距的
重点说一下,getItemOffsets中的参数outRect可以设置top,buttom,left,right4个属性,分别设置的列表中每个item四周的内边距,举个例子:我现在 outRect.top = 20 ,那么表示我要把分割线展示在每个item的上面,宽是20px。切记这里的 top,buttom,left,right4个属性和canvas中的坐标值不一样啊,详细去看下面的链接,讲的很详细的
- 深入理解 RecyclerView 系列之一:ItemDecoration 原理介绍的很清晰,先重点看懂这个
- 鸿扬大神的 看完上面,再看鸿扬大神的这篇,decoration基本就没问题了,当然也欢迎看我的项目代码~~
itemAnimator
-
深入理解 RecyclerView 系列之二:ItemAnimator
讲解原理的,留着很有用的
SparseArray/ArrayMap
- 他俩都是替代的HashMap的,SparseArray是key为int类型的map集合,ArrayMap的key没有类型要求
- 优势在于在千条数据之下的性能大大优于传统的java api HashMap。他俩在查询时 使用二分法查找的,比传统的HashMap遍历数据要快的多,内部使用2个数组来维护数据。
- 详细的请查看 Android内存优化(使用SparseArray和ArrayMap代替HashMap)
DiffUtil 列表的增量更新
先简单抄一段描述。DiffUtil是recyclerview support library v7 24.2.0版本中新增的类,根据Google官方文档的介绍,DiffUtil的作用是比较两个数据列表并能计算出一系列将旧数据表转换成新数据表的操作。这个概念比较抽象,换一种方式理解,DiffUtil是一个工具类,当你的RecyclerView需要更新数据时,将新旧数据集传给它,它就能快速告知adapter有哪些数据需要更新。
那么相比直接调用adapter.notifyDataSetChange()方法,使用DiffUtil有什么优势呢?它能在收到数据集后,提高UI更新的效率,而且你也不需要自己对新老数据集进行比较了。
顾名思义,凡是数据集的比较DiffUtil都能做,所以用处并不止于更新RecyclerView。DiffUtil也提供了回调让你可以进行其他操作。本文只讨论使用DiffUtil更新RecyclerView。
必看的2篇文章:
各种小技巧
-
线性布局和网格布局嵌套
适用场景是:在同一个列表中,既有线性布局的列表item,也有网格布局的列表item,那么技巧就在于,对于网格布局来说,可以设置个亿item占用的网格数,若是一个item占用一行中的全部网格,那么对于这个item来说,就是这个item自己独占一行了,那么就是线性布局了。从代码上来说网格布局就是继承自线性布局的
上代码:
public class MultiGridActivity extends MenuBaseActivity {
private final static int SPAN_COUNT = 5;
private MultiTypeAdapter adapter;
private Items items;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_multi_grid);
items = new Items();
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
final GridLayoutManager layoutManager = new GridLayoutManager(this, SPAN_COUNT);
/* 关键内容:通过 setSpanSizeLookup 来告诉布局,你的 item 占几个横向单位,
如果你横向有 5 个单位,而你返回当前 item 占用 5 个单位,那么它就会看起来单独占用一行 */
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return (items.get(position) instanceof Category) ? SPAN_COUNT : 1;
}
});
recyclerView.setLayoutManager(layoutManager);
adapter = new MultiTypeAdapter(items);
adapter.applyGlobalMultiTypePool();
adapter.register(Square.class, new SquareViewProvider());
assertAllRegistered(adapter, items);
recyclerView.setAdapter(adapter);
loadData();
}
private void loadData() {
// ...
}
}
-
使用扁平数据处理化来减少RV的嵌套层级
其实很多时候我们的列表中嵌套的列表都可以使用卞清华处理去除嵌套层级的,乔套层级多了的话,我想写过的孩子还很头疼的呀~~
扁平化已经有很多人探索过了,我哦放一个经典的帖子上来,大家看看
详细说来,扁平化就是把由嵌套的多层item变成单层item,这样每个item都是同级的,这就是扁平化,思路就是使用RV的可以支持多种类型item的特性,自己组织,添加列表 数据
假设:你的 Post 是这样的:
public class Post {
public String content;
public List<Comment> comments;
}
假设:你的 Comment 是这样的:
public class Comment {
public String content;
}
假设:你服务端返回的 JSON 数据是这样的:
[
{
"content":"I have released the MultiType v2.2.2",
"comments":[
{"content":"great"},
{"content":"I love your post!"}
]
}
]
那么你的 JSON 转成 Java Bean 之后,你拿到手应该是个 List<Post> posts 对象,现在我们写一个扁平化处理的方法:
private List<Object> flattenData(List<Post> posts) {
final List<Object> items = new ArrayList<>();
for (Post post : posts) {
/* 将 post 加进 items,Provider 内部拿到它的时候,
* 我们无视它的 comments 内容即可 */
items.add(post);
/* 紧接着将 comments 拿出来插入进 items,
* 评论就能正好处于该条 post 下面 */
items.addAll(post.comments);
}
return items;
}
11.28 添加
对 recycleView 点设涉及比较多,但是实现很简单
对 recycleView API 介绍非常详细
Scrollbars
默认是没有滚动条的,下面这样设置就有了
android:scrollbars="vertical"
动画
官方提供了默认动画 DefaultItemAnimator
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
查找 API
- findFirstCompletelyVisibleItemPosition()
- findFirstVisibleItemPosition()
- findLastCompletelyVisibleItemPosition()
-
findLastVisibleItemPosition()
RecyclerViewPool
Recycler 也有view 的回收池可以用 RecyclerViewPool ,比如上个 fragment 回收了,里面有列表,新的 fragment 进来,里面的列表可以通过同一个 RecyclerViewPool 支持复用之前列表的 view
详情请看:
GridLayoutManager 需要开启优化
//官方建议说,如果延用默认的 getSpanIndxe() 的实现逻辑的话,那么建议调用下述方法来进行优化,否则每次布局计算时会很耗性能。
gridLayoutManager.getSpanSizeLookup().setSpanIndexCacheEnabled(true);
mRecyclerView.setLayoutManager(gridLayoutManager);
adapter 回调
- onViewAttachedFromWindow()
- onViewDetachedFromWindow()
item 移除,进入屏幕时这2个方法一定会调
addOnItemTouchListener
public interface OnItemTouchListener {
boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e);
void onTouchEvent(RecyclerView rv, MotionEvent e);
void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept);
}
recycleView 的 addOnItemTouchListener 是用来做事件分发处理的
setHasFixedSize()
在我们确信 notifyitem 时不会改变列表的宽高时,先 setHasFixedSize(),再 notifyitem ,RecyclerView 的 onMeasure(), onLayout() 就不会被调用了,而是直接调用 LayoutManager 的 onMeasure()
setLayoutFrozen()
setLayoutFrozen() 方法会关闭 recycleView 的刷新,不会再测量,布局,
setItemViewCacheSize
recycleView 2级 view 缓存:mCachedViews 和 mRecyclerPool,mCachedViews 默认2个,可以过上上面的方法调整,是一级缓存,优先级高。
mRecyclerPool 以 item 的 type 分类缓存,最多5个,可以用 setRecycledViewPool() 修改
ItemDecoration
分隔符 DividerItemDecoration 系统提供的默认实现,只能用于 LinearLayoutManager
DividerItemDecoration itemDecoration = new DividerItemDecoration(mContext, LinearLayoutManager.HORIZONTAL);
itemDecoration.setDrawable(getResources().getDrawable(R.drawable.divider_space));
mRecyclerView.addItemDecoration(itemDecoration);
ItemTouchHelper
ItemTouchHelper 用于实现策划删除和拖拽
复杂布局
- 使用 gridLayoutManage 的 span 来做
效果
- 差动效果
bug 修复
LinearSnapHelper 有时会吃上下滚动事件
嵌套滑动
最开始只设置了RecyclerView.setNestedScrollingEnabled(false)
高版本机型滑动无问题 但是 手上有个4.4 然后手贱 跑了一下。。依旧卡顿
终极方案
LinearLayoutManager.setSmoothScrollbarEnabled(true)
LinearLayoutManager.setAutoMeasureEnabled(true)
RecyclerView.setHasFixedSize(true)
RecyclerView.setNestedScrollingEnabled(false)