我的recycleView技术大全

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你必须关心的大神

  • 张旭童-CSDN 真正的RV大神,RV的问题基本都能在他的博客里找到答案,不可多得!!!
  • drawkeet 他的multiType是不可多得的研习精品

核心的必看文章

涉及知识点

  • 基类,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中的坐标值不一样啊,详细去看下面的链接,讲的很详细的

itemAnimator


SparseArray/ArrayMap

  1. 他俩都是替代的HashMap的,SparseArray是key为int类型的map集合,ArrayMap的key没有类型要求
  2. 优势在于在千条数据之下的性能大大优于传统的java api HashMap。他俩在查询时 使用二分法查找的,比传统的HashMap遍历数据要快的多,内部使用2个数组来维护数据。
  3. 详细的请查看 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篇文章:

各种小技巧

  1. 线性布局和网格布局嵌套

    适用场景是:在同一个列表中,既有线性布局的列表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() {
        // ...
    }
}
  1. 使用扁平数据处理化来减少RV的嵌套层级

    其实很多时候我们的列表中嵌套的列表都可以使用卞清华处理去除嵌套层级的,乔套层级多了的话,我想写过的孩子还很头疼的呀~~

    扁平化已经有很多人探索过了,我哦放一个经典的帖子上来,大家看看

    1. 玩转订单详情三层数据只需要一层RecyclerView,并不需要嵌套View
    2. 玩转购物车界面和逻辑只需要一层Recyclerview,一个二层for循环和三个属性

    详细说来,扁平化就是把由嵌套的多层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 用于实现策划删除和拖拽

复杂布局
  1. 使用 gridLayoutManage 的 span 来做
效果
  1. 差动效果
bug 修复

LinearSnapHelper 有时会吃上下滚动事件

嵌套滑动

最开始只设置了RecyclerView.setNestedScrollingEnabled(false)

高版本机型滑动无问题 但是 手上有个4.4 然后手贱 跑了一下。。依旧卡顿

终极方案

LinearLayoutManager.setSmoothScrollbarEnabled(true)
LinearLayoutManager.setAutoMeasureEnabled(true)
RecyclerView.setHasFixedSize(true)
RecyclerView.setNestedScrollingEnabled(false)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,540评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,640评论 2 374
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,643评论 0 326
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,672评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,525评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,387评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,809评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,450评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,743评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,787评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,560评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,406评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,824评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,048评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,335评论 1 253
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,784评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,997评论 2 337

推荐阅读更多精彩内容