转载

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

以下内容为原创,转载请注明:

来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/4242541.html

以前写过一篇“ [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

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

使用的方式如下:

 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:设置滑动到顶部或底部的监听器,然后一旦滑动到底部使用SwipeRefreshLayout来刷新。

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.ab__id_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.ab__id_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()来执行其他类型的逻辑。

至于ABRecyclerViewTypeExtraRender(https://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

实现的原理大同小异了。

然后分析下 ABRecyclerViewTypeExtraHolder ( https://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 }

它继承了 ABRecyclerViewHolder https://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() + "岁");
正文到此结束
Loading...