[Android]使用RecyclerView替代ListView(二)


以前写过一篇“[Android]使用AdapterTypeRender对不同类型的item数据到UI的渲染http://www.cnblogs.com/tiantianbyconan/p/3992843.html)”,用于在有很多不同类型不同布局的item的时候怎么去较好的进行view的绑定和数据的渲染,但是这个是针对ListView写的。这次我针对RecyclerView也重新实现了一遍。

接下来演示下怎么去渲染不同类型的item,并且使它支持下拉刷新,滚动到底部显示加载进度条显示。

以下所有的封装都在AndroidBucket项目中:https://github.com/wangjiegulu/AndroidBucket/tree/master/src/com/wangjie/androidbucket/support/recyclerview

 

使用的方式如下:


 1 final View footerView = LayoutInflater.from(context).inflate(R.layout.recycler_view_item_type_footer, null);
2 // 不知道为什么在xml设置的“android:layout_width=”match_parent””无效了,需要在这里重新设置
3 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
4 footerView.setLayoutParams(lp);
5
6 recyclerView.setHasFixedSize(true);
7
8 final ABaseLinearLayoutManager layoutManager = new ABaseLinearLayoutManager(context);
9 layoutManager.setOnRecyclerViewScrollLocationListener(recyclerView, new OnRecyclerViewScrollLocationListener() {
10 @Override
11 public void onTopWhenScrollIdle(RecyclerView recyclerView) {
12 Logger.d(TAG, “onTopWhenScrollIdle…”);
13 }
14
15 @Override
16 public void onBottomWhenScrollIdle(RecyclerView recyclerView) {
17 Logger.d(TAG, “onBottomWhenScrollIdle…”);
18 footerView.setVisibility(View.VISIBLE);
19 ThreadPool.go(new Runtask<Object, Object>() {
20 @Override
21 public Object runInBackground() {
22 ABThreadUtil.sleep(3000);
23 return null;
24 }
25
26 @Override
27 public void onResult(Object result) {
28 super.onResult(result);
29 refreshLayout.setRefreshing(false);
30 footerView.setVisibility(View.GONE);
31 }
32 });
33 }
34 });
35 layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
36 layoutManager.getRecyclerViewScrollManager().addScrollListener(recyclerView, new OnRecyclerViewScrollListener() {
37 @Override
38 public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
39 }
40
41 @Override
42 public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
43 refreshLayout.setEnabled(layoutManager.findFirstCompletelyVisibleItemPosition() == 0);
44 }
45 });
46 recyclerView.setLayoutManager(layoutManager);
47
48 initData();
49
50 adapter = new PersonTypeAdapter(context, personList, null, footerView);
51 adapter.setOnRecyclerViewListener(this);
52 recyclerView.setAdapter(adapter);
53
54 refreshLayout.setColorSchemeColors(Color.BLUE, Color.RED, Color.YELLOW, Color.GREEN);
55 refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
56 @Override
57 public void onRefresh() {
58 ThreadPool.go(new Runtask<Object, Object>() {
59 @Override
60 public Object runInBackground() {
61 ABThreadUtil.sleep(3000);
62 return null;
63 }
64
65 @Override
66 public void onResult(Object result) {
67 super.onResult(result);
68 refreshLayout.setRefreshing(false);
69 footerView.setVisibility(View.GONE);
70 }
71 });
72 }
73 });

 

如上述代码:

Line1:从布局文件中inflate出一个View实例,这个View实例,下面会被用于作为下载提示的footer。

Line8:生成一个ABaseLinearLayoutManager实例,显然这个类是继承LinearLayoutManager之后扩展的,详见:https://github.com/wangjiegulu/AndroidBucket/blob/master/src/com/wangjie/androidbucket/support/recyclerview/layoutmanager/ABaseLinearLayoutManager.java,这个类对滑动的监听进行了扩展,可以监听滑动到顶部或者底部的事件

Line9~34:设置滑动到顶部或底部的监听器,然后一旦滑动到底部则加载更多数据。

Line36~45:也是设置滑动监听器,滑动过程中如果不是处在第一个item,如果是,则就可以下拉使用SwipeRefreshLayout进行刷新,如果不是则,仅用SwipeRefreshLayout。之所以需要做这个处理,是因为Google事件处理的一个bug--。

Line50:使用了一个PersonTypeAdapter,这个类继承了ABRecyclerViewTypeExtraViewAdapter这个类继承RecyclerView.Adapter实现了对不同type渲染数据的封装。

 

接下来重点分析下ABRecyclerViewTypeExtraViewAdapter这个类(这个类在平常使用时不需要关注):


  1 /
2 Author: wangjie
3 Email: tiantian.china.2@gmail.com
4 Date: 1/22/15.
5 /
6 public abstract class ABRecyclerViewTypeExtraViewAdapter extends RecyclerView.Adapter<ABRecyclerViewTypeExtraHolder> {
7 private static final int TYPE_HEADER_VIEW = 0x7683;
8 private View headerView;
9 private static final int TYPE_FOOTER_VIEW = 0x7684;
10 private View footerView;
11 private int extraCount;
12
13 protected ABRecyclerViewTypeExtraViewAdapter(View headerView, View footerView) {
14 this.headerView = headerView;
15 this.footerView = footerView;
16 extraCount += hasHeaderView() ? 0 : 1;
17 extraCount += hasFooterView() ? 0 : 1;
18 }
19
20 public boolean hasHeaderView() {
21 return null != headerView;
22 }
23
24 public boolean hasFooterView() {
25 return null != footerView;
26 }
27
28 public int innerPositionToRealItemPosition(int innerPosition) {
29 return hasHeaderView() ? innerPosition - 1 : innerPosition;
30 }
31
32 @TargetApi(Build.VERSION_CODES.DONUT)
33 @Override
34 public ABRecyclerViewTypeExtraHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
35 ABAdapterTypeRender<ABRecyclerViewTypeExtraHolder> render = getAdapterTypeRender(viewType);
36 ABRecyclerViewTypeExtraHolder holder = render.getReusableComponent();
37 holder.itemView.setTag(R.id.abid_adapter_item_type_render, render);
38 render.fitEvents();
39 return holder;
40 }
41
42 @TargetApi(Build.VERSION_CODES.DONUT)
43 @Override
44 public void onBindViewHolder(ABRecyclerViewTypeExtraHolder holder, int innerPosition) {
45 ABAdapterTypeRender<ABRecyclerViewTypeExtraHolder> render = (ABAdapterTypeRender<ABRecyclerViewTypeExtraHolder>) holder.itemView.getTag(R.id.abid_adapter_item_type_render);
46 /
47 计算该item在list中的index(不包括headerView和footerView)
48 /
49 int realItemPosition = innerPositionToRealItemPosition(innerPosition);
50 render.fitDatas(realItemPosition);
51 /
52 重新设置item在list中的index(不包括headerView和footerView)
53 /
54 holder.setRealItemPosition(realItemPosition);
55 }
56
57 /
58 通过类型获得对应的render(不包括headerView和footerView)
59
60 @param type
61 @return
62 /
63 public abstract ABAdapterTypeRender<ABRecyclerViewTypeExtraHolder> getAdapterTypeRenderExcludeExtraView(int type);
64
65 /**
66 获取item的数量(不包括headerView和footerView)
67
68 @return
69 /
70 public abstract int getItemCountExcludeExtraView();
71
72 /**
73 通过realItemPosition得到该item的类型(不包括headerView和footerView)
74
75 @param realItemPosition
76 @return
77 /
78 public abstract int getItemViewTypeExcludeExtraView(int realItemPosition);
79
80 public ABAdapterTypeRender<ABRecyclerViewTypeExtraHolder> getAdapterTypeRender(int type) {
81 switch (type) {
82 case TYPE_HEADER_VIEW:
83 return new ABRecyclerViewTypeExtraRender(headerView);
84 case TYPE_FOOTER_VIEW:
85 return new ABRecyclerViewTypeExtraRender(footerView);
86 default:
87 return getAdapterTypeRenderExcludeExtraView(type);
88 }
89 }
90
91 @Override
92 public int getItemCount() {
93 return getItemCountExcludeExtraView() + extraCount;
94 }
95
96 @Override
97 public int getItemViewType(int innerPosition) {
98 if (null != headerView && 0 == innerPosition) { // header
99 return TYPE_HEADER_VIEW;
100 } else if (null != footerView && getItemCount() - 1 == innerPosition) { // footer
101 return TYPE_FOOTER_VIEW;
102 }
103 return getItemViewTypeExcludeExtraView(innerPositionToRealItemPosition(innerPosition));
104 }
105 }

如上述代码所示:

因为我们的需求是需要添加“加载进度条”,所以需要像ListView那样,添加一个FooterView。可是坑爹的是,RecyclerView不提供addheaderView()和addFooterView()方法,所以只能我们自己去实现了,方法当然是使用不同type来区分类型。虽然headerView这里没有用到,但是也顺带实现下好了。

这里我们使用的Holder是ABRecyclerViewTypeExtraHolder,这个类待会分析。

headerView和footerView这里使用构造方法传入,并缓存headerView和footerView。在onCreateViewHolder中,我们通过不同type,生成相应的render。并把render绑定到holder的itemView上面,因为既然现在复用的是holder,那我的render也要实现复用的话,也绑定在holder里面吧。然后调用render的fitEvents方法,来实现render里面的事件绑定。

onBindViewHolder方法中,通过holder,取出render,然后注意Line49~54,里面执行了innerPositionToRealItemPosition()方法对innerPosition到RealItemPosition的转换,这里的innerPosition代表内部的position,因为这里可能会添加了headerView,一旦添加了headerView,那position跟数据源List中的index就不匹配了,这样的话绑定点击事件后,通过holder.getPositon()得到的position就不是index了,所以不能写“list.get(position)…”了。这里的realItemPosition就代表数据源对应的index 。所以我们要把innerPosition转换为realItemPosition,方法很简单,innerPosition-1=realItemPosition(这里的减去1实际上就是减去了headerView)。其实holder中本来就缓存了当前使用了这个holder的item的position,但是因为有了headerView的存在,position就不等于realItemPosition了,所以我们还需要缓存realItemPosition。所以代码Line46~54诞生了。

getItemCountExcludeExtraView()方法需要子类实现,返回数据源中的数据数量,然后再加上extraCount即是getItemCount的值。

getItemViewType()方法先执行了header类型和footer类型的逻辑,然后再让自类去实现getItemViewTypeExcludeExtraView()来执行其他类型的逻辑。

至于ABRecyclerViewTypeExtraRenderhttps://github.com/wangjiegulu/AndroidBucket/blob/master/src/com/wangjie/androidbucket/support/recyclerview/adapter/extra/ABRecyclerViewTypeExtraRender.java

部分的实现可以查看

[Android]使用AdapterTypeRender对不同类型的item数据到UI的渲染http://www.cnblogs.com/tiantianbyconan/p/3992843.html

实现的原理大同小异了。

 

然后分析下ABRecyclerViewTypeExtraHolderhttps://github.com/wangjiegulu/AndroidBucket/blob/master/src/com/wangjie/androidbucket/support/recyclerview/adapter/extra/ABRecyclerViewTypeExtraHolder.java这个类,代码如下:


 1 /
2 Author: wangjie
3 Email: tiantian.china.2@gmail.com
4 Date: 1/22/15.
5 /
6 public class ABRecyclerViewTypeExtraHolder extends ABRecyclerViewHolder {
7 public ABRecyclerViewTypeExtraHolder(View itemView) {
8 super(itemView);
9 }
10
11 /
12 保存当前position(list index,不包括headerView和footerView)
13 /
14 private int realItemPosition;
15
16 public int getRealItemPosition() {
17 return realItemPosition;
18 }
19
20 protected void setRealItemPosition(int realItemPosition) {
21 this.realItemPosition = realItemPosition;
22 }
23
24 }

它继承了ABRecyclerViewHolderhttps://github.com/wangjiegulu/AndroidBucket/blob/master/src/com/wangjie/androidbucket/support/recyclerview/adapter/ABRecyclerViewHolder.java),只是保存了一个刚刚讲到的realItemPosition对象。

所以我们再贴下ABRecyclerViewHolder的代码:


 1 /
2 Author: wangjie
3 Email: tiantian.china.2@gmail.com
4 Date: 1/19/15.
5 /
6 public class ABRecyclerViewHolder extends RecyclerView.ViewHolder {
7 private static final String TAG = ABRecyclerViewHolder.class.getSimpleName();
8 private SparseArray<View> holder = null;
9
10 public ABRecyclerViewHolder(View itemView) {
11 super(itemView);
12 }
13
14 /
15 获取一个缓存的view
16
17 @param id
18 @param <T>
19 @return
20 /
21 public <T extends View> T obtainView(int id) {
22 if (null == holder) {
23 holder = new SparseArray<>();
24 }
25 View view = holder.get(id);
26 if (null != view) {
27 return (T) view;
28 }
29 view = itemView.findViewById(id);
30 if (null == view) {
31 Logger.e(TAG, “no view that id is “ + id);
32 return null;
33 }
34 holder.put(id, view);
35 return (T) view;
36 }
37
38 /*
39 获取一个缓存的view,并自动转型
40
41 @param id
42 @param <T>
43 @return
44 */
45 public <T> T obtainView(int id, Class<T> viewClazz) {
46 View view = obtainView(id);
47 if (null == view) {
48 return null;
49 }
50 return (T) view;
51 }
52
53 }

然后发现,它的作用是在于使用SparseArray来缓存findViewById后的控件。这样做的好处是,这个holder可以适用于任何的RecyclerView.Adapter中。只要通过obtainView()方法就能得到itemView中的具体的view对象,如下代码所示:


1 Person person = adapter.getList().get(position);
2 holder.obtainView(R.id.recycler_view_test_item_person_name_tv, TextView.class).setText(person.getName());=
3 holder.obtainView(R.id.recycler_view_test_item_person_age_tv, TextView.class).setText(person.getAge() + “岁”);

 

示例代码:

https://github.com/wangjiegulu/RecyclerViewSample

 

[Android]使用RecyclerView替代ListView(一)

http://www.cnblogs.com/tiantianbyconan/p/4232560.html

 

[Android]使用RecyclerView替代ListView(三) 

http://www.cnblogs.com/tiantianbyconan/p/4268097.html

 



来源博客:Wang Jie's Blog's Blog
本文链接:https://blog.wangjiegulu.com/2015/01/22/Android-使用RecyclerView替代ListView(二)/
版权声明:本博客所有文章除特别声明外,均采用 CC BY 4.0 CN协议 许可协议。转载请注明出处。