为RecyclerView打造通用Adapter 让RecyclerView更加好用

原文出处: 张鸿洋 (Granker,@鸿洋_ )html

1、概述java

记得很久之前针对ListView类控件写过一篇打造万能的ListView GridView 适配器,现在RecyclerView异军突起,其Adapter的用法也与ListView相似,那么咱们也能够一步一步的为其打造通用的Adapter,使下列用法书写更加简单:android

  • 简单的数据绑定(单种Item)git

  • 多种Item Type 数据绑定github

  • 增长onItemClickListener , onItenLongClickListener缓存

  • 优雅的添加分类headeride

2、使用方式和效果图

在一步一步完成前,咱们先看下使用方式和效果图:布局

(1)简单的数据绑定

首先看咱们最经常使用的单种Item的书写方式:post

 

 

 

 

 

Javathis

 

1

2

3

4

5

6

7

8

mRecyclerView.setAdapter(new CommonAdapter(thisR.layout.item_listmDatas)

{

    @Override

    public void convert(ViewHolder holderString s)

    {

        holder.setText(R.id.id_item_list_titles);

    }

});

是否是至关方便,在convert方法中完成数据、事件绑定便可。

(2)多种ItemViewType

多种ItemViewType,正常考虑下,咱们须要根据Item指定ItemType,而且根据ItemType指定相应的布局文件。咱们经过MultiItemTypeSupport完成指定:

 

 

 

 

Java

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

MultiItemTypeSupport  multiItemSupport new MultiItemTypeSupport()

{

    @Override

    public int getLayoutId(int itemType)

    {

       //根据itemType返回item布局文件id

    }

 

    @Override

    public int getItemViewType(int postionChatMessage msg)

    {

       //根据当前的bean返回item type

    }

}

剩下就简单了,将其做为参数传入到MultiItemCommonAdapter便可。

 

 

 

 

 

Java

 

1

2

3

4

5

6

7

8

mRecyclerView.setAdapter(new SectionAdapter(thismDatasmultiItemSupport)

{

    @Override

    public void convert(ViewHolder holderString s)

    {

        holder.setText(R.id.id_item_list_titles);

    }

});

贴个效果图:

(3)添加分类header

其实属于多种ItemViewType的一种了,只是比较经常使用,咱们就简单封装下。

依赖正常考虑下,这种方式须要额外指定header的布局,以及布局中显示标题的TextView了,以及根据Item显示什么样的标题。咱们经过SectionSupport对象指定:

 

 

 

 

Java

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

SectionSupport sectionSupport new SectionSupport()

{

    @Override

    public int sectionHeaderLayoutId()

    {

        return R.layout.header;

    }

 

    @Override

    public int sectionTitleTextViewId()

    {

        return R.id.id_header_title;

    }

 

    @Override

    public String getTitle(String s)

    {

        return s.substring(01);

    }

};

3个方法,一个指定header的布局文件,一个指定布局文件中显示title的TextView,最后一个用于指定显示什么样的标题(根据Adapter的Bean)。

接下来就很简单了:

 

 

 

 

 

Java

 

1

2

3

4

5

6

7

8

mRecyclerView.setAdapter(new SectionAdapter(thisR.layout.item_listmDatassectionSupport)

{

    @Override

    public void convert(ViewHolder holderString s)

    {

        holder.setText(R.id.id_item_list_titles);

    }

});

这样就完了,效果图以下:

ok,看完上面简单的介绍,相信你已经基本了解了,没错,和我上篇ListView万能Adapter的使用方式基本同样,而且已经封装到同一个库了,连接为:https://github.com/hongyangAndroid/base-adapter,此外还提供了ItemClick,ItemLongClick,添加EmptyView等支持。

说了这么多,下面进入正题,看咱们如何一步步完成整个封装的过程。

3、通用的ViewHolder

RecyclerView要求必须使用ViewHolder模式,通常咱们在使用过程当中,都须要去创建一个新的ViewHolder而后做为泛型传入Adapter。那么想要创建通用的Adapter,必须有个通用的ViewHolder。

首先咱们肯定下ViewHolder的主要的做用,其实是经过成员变量存储对应的convertView中须要操做的字View,避免每次findViewById,从而提高运行的效率。

那么既然是通用的View,那么对于不一样的ItemType确定没有办法肯定建立哪些成员变量View,取而代之的只能是个集合来存储了。

那么代码以下:

 

 

 

 

 

Java

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

public class ViewHolder extends RecyclerView.ViewHolder

{

    private SparseArray mViews;

    private View mConvertView;

    private Context mContext;

 

    public ViewHolder(Context contextView itemViewViewGroup parent)

    {

        super(itemView);

        mContext context;

        mConvertView itemView;

        mViews new SparseArray();

    }

 

    public static ViewHolder get(Context contextViewGroup parentint layoutId)

    {

 

        View itemView LayoutInflater.from(context).inflate(layoutIdparent,

                false);

        ViewHolder holder new ViewHolder(contextitemViewparentposition);

        return holder;

    }

 

    /**

     * 经过viewId获取控件

     *

     * @param viewId

     * @return

     */

    public  getView(int viewId)

    {

        View view mViews.get(viewId);

        if (view == null)

        {

            view mConvertView.findViewById(viewId);

            mViews.put(viewIdview);

        }

        return (Tview;

    }

}

代码很简单,咱们的ViewHolder继承自RecyclerView.ViewHolder,内部经过SparseArray来缓存咱们itemView内部的子View,从而获得一个通用的ViewHolder。每次须要建立ViewHolder只须要传入咱们的layoutId便可。

ok,有了通用的ViewHolder以后,咱们的通用的Adapter分分钟就出来了。

4、通用的Adapter

咱们的每次使用过程当中,针对的数据类型Bean确定是不一样的,那么这里确定要引入泛型表明咱们的Bean,内部经过一个List表明咱们的数据,ok,剩下的看代码:

 

 

 

 

 

Java

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

package com.zhy.base.adapter.recyclerview;

 

import android.content.Context;

import android.support.v7.widget.RecyclerView;

import android.view.LayoutInflater;

import android.view.View;

import android.view.ViewGroup;

 

import com.zhy.base.adapter.ViewHolder;

 

import java.util.List;

 

/**

* Created by zhy on 16/4/9.

*/

public abstract class CommonAdapter extends RecyclerView.Adapter

{

    protected Context mContext;

    protected int mLayoutId;

    protected List mDatas;

    protected LayoutInflater mInflater;

 

    public CommonAdapter(Context contextint layoutIdList datas)

    {

        mContext context;

        mInflater LayoutInflater.from(context);

        mLayoutId layoutId;

        mDatas datas;

    }

 

    @Override

    public ViewHolder onCreateViewHolder(final ViewGroup parentint viewType)

    {

        ViewHolder viewHolder ViewHolder.get(mContextparentmLayoutId);

        return viewHolder;

    }

 

    @Override

    public void onBindViewHolder(ViewHolder holderint position)

    {

        holder.updatePosition(position);

        convert(holdermDatas.get(position));

    }

 

    public abstract void convert(ViewHolder holdert);

 

    @Override

    public int getItemCount()

    {

        return mDatas.size();

    }

}

继承自RecyclerView.Adapter,须要复写的方法仍是比较少的。首先咱们使用过程当中传输咱们的数据集mDatas,和咱们item的布局文件layoutId。

onCreateViewHolder时,经过layoutId便可利用咱们的通用的ViewHolder生成实例。

onBindViewHolder这里主要用于数据、事件绑定,咱们这里直接抽象出去,让用户去操做。能够看到咱们修改了下参数,用户能够拿到当前Item所须要的对象和viewHolder去操做。

那么如今用户的使用是这样的:

 

 

 

 

 

Java

 

1

2

3

4

5

6

7

8

9

mRecyclerView.setAdapter(new CommonAdapter(thisR.layout.item_listmDatas)

{

    @Override

    public void convert(ViewHolder holderString s)

    {

        TextView tv holder.getView(R.id.id_item_list_title);

        tv.setText(s);

    }

});

看到这里,爽了不少,目前咱们仅仅写了不多的代码,可是咱们的通用的Adapter感受已经初步完成了。

能够看到咱们这里经过viewholder根据控件的id拿到控件,而后再进行数据绑定和事件操做,咱们还能作些什么简化呢?

恩,咱们能够经过一些辅助方法简化咱们的代码,因此继续往下看。

5、进一步封装ViewHolder

咱们的Item实际上使用的控件较多时候可能都是TextView,ImageView等,咱们通常在convert方法都是去设置文本,图片什么的,那么咱们能够在ViewHolder里面,写上以下的一些辅助方法:

 

 

 

 

Java

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

class ViewHolder extends RecyclerView.AdapterViewHolder>

{

    //...

    public ViewHolder setText(int viewIdString text)

    {

        TextView tv getView(viewId);

        tv.setText(text);

        return this;

    }

 

    public ViewHolder setImageResource(int viewIdint resId)

    {

        ImageView view getView(viewId);

        view.setImageResource(resId);

        return this;

    }

 

    public ViewHolder setOnClickListener(int viewId,

                                         View.OnClickListener listener)

    {

        View view getView(viewId);

        view.setOnClickListener(listener);

        return this;

    }

}

固然上面只给出了几个方法,你能够把经常使用控件的方法都写进去,而且在使用过程当中不断完善便可。

有了一堆辅助方法后,咱们的操做更加简化了一步。

 

 

 

 

 

Java

 

1

2

3

4

5

6

7

8

9

10

mRecyclerView.setAdapter(new CommonAdapter(thisR.layout.item_listmDatas)

{

    @Override

    public void convert(ViewHolder holderString s)

    {

        //TextView tv = holder.getView(R.id.id_item_list_title);

        //tv.setText(s);

        holder.setText(R.id.id_item_list_title,s);

    }

});

ok,到这里,咱们的针对单种ViewItemType的通用Adapter就完成了,代码很简单也不多,可是简化效果很是明显。

ok,接下来咱们考虑多种ItemViewType的状况。

6、多种ItemViewType

多种ItemViewType,通常咱们的写法是:

  • 复写getItemViewType,根据咱们的bean去返回不一样的类型

  • onCreateViewHolder中根据itemView去生成不一样的ViewHolder

若是你们还记得,咱们的ViewHolder是通用的,惟一依赖的就是个layoutId。那么上述第二条就变成,根据不一样的itemView告诉我用哪一个layoutId便可,生成viewholder这种事咱们通用adapter来作。

因而,引入一个接口:

 

 

 

 

Java

 

1

2

3

4

5

6

public interface MultiItemTypeSupport

{

    int getLayoutId(int itemType);

 

    int getItemViewType(int positiont);

}

能够很清楚的看到,这个接口实际就是完成咱们上述的两条工做。用户在使用过程当中,经过实现上面两个方法,指明不一样的Bean返回什么itemViewType,不一样的itemView所对应的layoutId.

ok,有了上面这个接口,咱们的参数就够了,下面开始咱们的MultiItemCommonAdapter的编写。

 

 

 

 

 

Java

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

public abstract class MultiItemCommonAdapterTextends CommonAdapterT>

{

    protected MultiItemTypeSupport mMultiItemTypeSupport;

 

    public MultiItemCommonAdapter(Context contextList datas,

                                  MultiItemTypeSupport multiItemTypeSupport)

    {

        super(context-1datas);

        mMultiItemTypeSupport multiItemTypeSupport;

    }

 

    @Override

    public int getItemViewType(int position)

    {

        return mMultiItemTypeSupport.getItemViewType(positionmDatas.get(position));

    }

 

    @Override

    public ViewHolder onCreateViewHolder(ViewGroup parentint viewType)

    {

        int layoutId mMultiItemTypeSupport.getLayoutId(viewType);

        ViewHolder holder ViewHolder.get(mContextparentlayoutId;

        return holder;

    }

 

}

几乎没有几行代码,感受简直不须要消耗脑细胞。getItemViewType用户的传入的MultiItemTypeSupport.getItemViewType完成,onCreateViewHolder中根据MultiItemTypeSupport.getLayoutId返回的layoutId,去生成ViewHolder便可。

ok,这样的话,咱们的多种ItemViewType的支持也就完成了,一路下来感受仍是蛮轻松的~~~

最后,咱们还有个添加分类的header,为何想起来封装这个呢,这个是由于我看到了这么个项目:https://github.com/ragunathjawahar/simple-section-adapter,这个项目给了种相似装饰者模式的方法,为ListView添加了header,有兴趣能够看下。我想咱们的RecylerView也来个吧,不过咱们这里直接经过继承Adapter完成。

7、添加分类Header

话说添加分类header,其实就是咱们多种ItemViewType的一种,那么咱们须要知道哪些参数呢?

简单思考下,咱们须要:

  1. header所对应的布局文件

  2. 显示header的title对应的TextView

  3. 显示的title是什么(通常确定根据Bean生成)

ok,这样的话,咱们依然引入一个接口,用于提供上述3各参数

 

 

 

 

Java

 

1

2

3

4

5

6

7

8

public interface SectionSupport

{

    public int sectionHeaderLayoutId();

 

    public int sectionTitleTextViewId();

 

    public String getTitle(t);

}

方法名应该很明确了,这里引入泛型,对应咱们使用时的数据类型Bean。

刚才也说了咱们的分类header是多种ItemViewType的一种,那么直接继承MultiItemCommonAdapter实现。

 

 

 

 

 

Java

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

public abstract class SectionAdapter extends MultiItemCommonAdapter

{

    private SectionSupport mSectionSupport;

    private static final int TYPE_SECTION 0;

    private LinkedHashMap mSections;

 

    private MultiItemTypeSupport headerItemTypeSupport new MultiItemTypeSupport()

    {

        @Override

        public int getLayoutId(int itemType)

        {

            if (itemType == TYPE_SECTION)

                return mSectionSupport.sectionHeaderLayoutId();

            else

                return mLayoutId;

        }

        @Override

        public int getItemViewType(int positiono)

        {

             return mSections.values().contains(position?

                    TYPE_SECTION :

                    1;

        }

    };

 

    @Override

    public int getItemViewType(int position)

    {

        return mMultiItemTypeSupport.getItemViewType(positionnull);

    }

 

    final RecyclerView.AdapterDataObserver observer new RecyclerView.AdapterDataObserver()

    {

        @Override

        public void onChanged()

        {

            super.onChanged();

            findSections();

        }

    };

 

    public SectionAdapter(Context contextint layoutIdList datasSectionSupport sectionSupport)

    {

        super(contextdatasnull);

        mLayoutId layoutId;

        mMultiItemTypeSupport headerItemTypeSupport;

        mSectionSupport sectionSupport;

        mSections new LinkedHashMap();

        findSections();

        registerAdapterDataObserver(observer);

    }

 

    @Override

    protected boolean isEnabled(int viewType)

    {

        if (viewType == TYPE_SECTION)

            return false;

        return super.isEnabled(viewType);

    }

 

    @Override

    public void onDetachedFromRecyclerView(RecyclerView recyclerView)

    {

        super.onDetachedFromRecyclerView(recyclerView);

        unregisterAdapterDataObserver(observer);

    }

 

    public void findSections()

    {

        int mDatas.size();

        int nSections 0;

        mSections.clear();

 

        for (int 0get(i));

 

            if (!mSections.containsKey(sectionName))

            {

                mSections.put(sectionNamenSections);

                nSections++;

            }

        }

 

    }

 

    @Override

    public int getItemCount()

    {

        return super.getItemCount(mSections.size();

    }

 

    public int getIndexForPosition(int position)

    {

        int nSections 0;

 

        SetentrySet mSections.entrySet();

        for (Map.Entry entry entrySet)

        {

            if (entry.getValue(return position nSections;

    }

 

    @Override

    public void onBindViewHolder(ViewHolder holderint position)

    {

        position getIndexForPosition(position);

        if (holder.getItemViewType(== TYPE_SECTION)

        {

            holder.setText(mSectionSupport.sectionTitleTextViewId()mSectionSupport.getTitle(mDatas.get(position)));

            return;

        }

        super.onBindViewHolder(holderposition);

    }

}

根据咱们以前的代码,使用MultiItemCommonAdapter,须要提供一个MultiItemTypeSupport,咱们这里固然也不例外。能够看到上述代码,咱们初始化了成员变量headerItemTypeSupport,分别对getLayoutIdgetItemViewType进行了实现。

  • getLayoutId若是type是header类型,则返回mSectionSupport.sectionHeaderLayoutId();不然则返回mLayout.

  • getItemViewType根据位置判断,若是当前是header所在位置,返回header类型常量;不然返回1.

ok,能够看到咱们构造方法中调用了findSections(),主要为了存储咱们的title和对应的position,经过一个MapmSections来存储。

那么对应的getItemCount()方法,咱们多了几个title确定总数会增长,因此须要复写。

onBindViewHolder中咱们有一行重置position的代码,由于咱们的position变大了,因此在实际上绑定咱们数据时,这个position须要还原,代码逻辑见getIndexForPosition(position)

最后一点就是,每当咱们的数据发生变化,咱们的title集合,即mSections就可能会发生变化,因此须要从新生成,原本准备复写notifyDataSetChanged方法,在里面从新生成,没想到这个方法是final的,因而利用了registerAdapterDataObserver(observer);,在数据发生变化回调中从新生成,记得在onDetachedFromRecyclerView里面对注册的observer进行解注册。

ok,到此咱们的增长Header就结束了~~

恩,上面是针对普通的Item增长header的代码,若是是针对多种ItemViewType呢?其实也很简单,这种方式须要传入MultiItemTypeSupport。那么对于headerItemTypeSupport中的getItemViewType等方法,不是header类型时,交给传入的MultiItemTypeSupport便可,大体的代码以下:

 

 

 

 

Java

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

headerItemTypeSupport new MultiItemTypeSupport()

{

    @Override

    public int getLayoutId(int itemType)

    {

        if (itemType == TYPE_SECTION)

            return mSectionSupport.sectionHeaderLayoutId();

        else

            return multiItemTypeSupport.getLayoutId(itemType);

    }

 

    @Override

    public int getItemViewType(int positiono)

    {

        int positionVal getIndexForPosition(position);

        return mSections.values().contains(position?

                TYPE_SECTION :

                multiItemTypeSupport.getItemViewType(positionValo);

    }

};

那么这样的话,今天的博客就结束了,有几点须要说明下:

原本是想接着之前的万能Adapter后面写,可是为了本文的独立和完整性,仍是尽量没有去依赖上篇博客的内容了。

此外,文章最后给出的开源代码与上述代码存在些许的差别,由于开源部分源码整合了ListView,RecyclerView等,而本文上述代码彻底针对RecyclerView进行编写。

QQ技术交流群290551701 http://cxy.liuzhihengseo.com/537.html

相关文章
相关标签/搜索