深入解析Android中的RecyclerView组件

前些日子,组里为了在目前的Android程序里实现基于ListView子项的动画效果,希望将最新的RecyclerView引入到程序中,于是我便花了一些时间研究了一下RecyclerView的基本情况。本文算是对这些日子里了解的内容做一些汇总。

在网上关于RecyclerView的基本使用方式已经有了比较详细介绍,而且其设计结构也类似于ListView,所以本文将不重点介绍如何使用,在文末的引用中都可以相关内容。这里主要是介绍RecyclerView的基本功能、设计理念,以及系统提供API的情况。

什么是RecyclerView
RecyclerView是在Android L(也就是后来的Lollipop)中新加入的一种ViewGroup。但因为它使以support-v7库的形式加入到Android系统中,所以不仅仅是Lollipop版本以后的Android系统可以使用,只要系开发项目中引入这个库就在任意API级别中使用。不过查阅其文档,可以感受到RecyclerView是如此的强烈的“还在完善中”的感觉,因为对它的介绍只有短短的一句话:

A flexible view for providing a limited window into a large data set.

单看这句话,其实已经被经常使用的ListView和GridView基本也是满足这句描述的。而且从单呈现效果来看RecyclerView和ListView、GridView并没有大多的差别。

不过它的“flexible”并不单单指它可以在ListView和GridView之间随意互相切换,更在于它可以创造出更多的复杂的可滚动的视图,比如水平方向的ListView,或者是Web上很流行的瀑布流式布局(Masonry)。只是大部分布局系统都没有提供,必须由开发者自己实现。

所以RecyclerView的“flexible”:什么都可以做,但什么都要自己做。

RecyclerView和ListView的主要区别
只要用Android提供的LieanerLayoutManager并配以VERTICAL模式,RecyclerView就可以完美达到ListView的基本效果。两者的设计结构也都是数据(Dataset)与视图(View)分离,然后通过适配器(Adapter)来连接的方式。

但RecyclerView相对ListView来说有以下几大提升:

强制使用ViewHolder
在ListView性能优化方面,Android就推荐使用ViewHolder来减少findViewById()的使用以提高效率。不过对于ListView上的ViewHolder,Android只是建议而非强制使用。不过因为使用ViewHolder模式太有意义了,所以在RecyclerView中ViewHolder就变成了必须使用的模式,Adapter要求返回的也从普通的View变成了ViewHolder。不过如果实现时没有自定义的一些View实际变量,ViewHolder也依然失去其意义。

没有OnItemClickListener
ListView从它的父类AdapterView直接继承了对子项目点击的响应,开发者可以定义自己的OnItemClickListner来接受点击事件。但这个设计也造成了一些问题,比如子项内部视图如果设置了OnClickListener,那么子项目视图本身并不会知道,从而可能会导致视图点击状态没有同步等问题。ReyclerView没有提供简便的响应子项目被点击的监听器,虽然它有一个OnItemTouchListener,但在这个接口方法中没有任何关于那个子项目被点击的信息,该接口只是帮助开发者截获触摸事件,对于如何处理,检测被触摸目标对象都留给了开发者去完成

视图与布局分离
ListView做到了数据和视图的分离,RecyclerView在视图和布局之间再进一步分离,于是便有了LayoutManager。RecyclerView负责管理视图的重复利用,然后将布局方式全权交给了LayoutManager,通过配置或者切换LayoutManager就可以获得不同的布局效果。不像ListView被限制在垂直滚动布局。同时RecyclerView还提供了ItemDecoration,在已有的子视图基础上还可以添加额外的视图。比如做一条分割线,在ListView就需要额外占用一个ViewType来提供视图,现在则不需要在Adapter中加入这些与实际逻辑业务无关的辅助内容。

支持子项目层次的动画效果
ListView也可以支持子项目层次的动画效果,在Android Developers的DevBytes频道里有很多关于这方面的介绍,不过在看过其实现之后就会发现其解决方案是多么的丑陋而冗长。很多时候都是在计算和分析子视图的位置状态。RecyclerView则带来了非常简洁的ItemAnimator接口。当Adapter中的数据发生“增删改移”变化,通过调用Adapter相应的方法就可以激活动画的产生。当然开发者还需要自己实现具体的ItemAnimator对象来完成所需的动画效果,但是其清晰的结构和接口已比ListView有极大的进步。

RecyclerView的现状
上边提到RecyclerView的套件已经加入了豪华Support Library v7,而且是以单独的库放入,所以只要在Android Studio项目的Gradle编译文件的dependencies下加入下边的这句就可以开始使用RecyclerView了:

compile 'com.android.support:recyclerview-v7:21.0.+'
不过看过库中提供的那些自带对象实现,体现基本操作流程,就会发现RecyclerView能做的看起来很多,但是已经做到的实在太少。

RecyclerView.Adapter

RecyclerView提供了一个抽象Adapter类,然后就没有了。没有任何可以直接使用的子类,像ListAdapter那样的ArrayAdapter、SimpleCursorAdapter现成的类都没有。一切都留给开发者自己去实现定义。

仔细想想这也很挺正常,相信应该很少在实际产品中有使用ArrayAdapter的,因为大部分列表都不会是简单的一行文字。对于CursorAdapter使用也往往会实现不同的继承类来提供子视图。再者RecyclerView的Adapter和ListAdapter在理念上还是一样的,所以想实现个RecyclerViewCursorAdapter,直接从CursorAdapter取材便可。

RecyclerView的Adapter相对ListAdapter在接口上有几处变化也值得注意。

首先其将getView()方法分拆成了createViewHolder()和bindViewHolder()两个。不过这个没有什么好紧张的,在CursorAdapter里就已经有见到过这个更加合理的设计。另外返回对象也从View变成了ViewHolder只需提一下。

最关键的注意点在于createViewHolder(ViewGroup parent, int viewType)第二参数虽然是整形,但是它并不是以往的当前子项的位置(position),而是调用getItemViewType()获得的的子项的类别。似乎在创建ViewHolder时,RecyclerView故意在隐藏子项的详细信息,希望开发者完全只依赖其类别来创建相应的View以及ViewHolder。

其次,RecyclerView的Adapter除了和ListAdapter一样有notifyDataSetChanged()方法外,还有一堆会触发动画效果的通知数据改变的方法:

final void notifyItemChanged(int position); final void notifyItemInserted(int position); final void notifyItemMoved(int fromPosition, int toPosition); final void notifyItemRangeChanged(int positionStart, int itemCount); final void notifyItemRangeInserted(int positionStart, int itemCount); final void notifyItemRangeRemoved(int positionStart, int itemCount); final void notifyItemRemoved(int position);

调用这些方法就会激发ItemAnimator上对应的用于产生动画的方法。

RecyclerView.ViewHolder

在ListView的年代里,其实已经在使用ViewHolder,只是那时的方法看起来比较讨巧,要隐藏在View的tag里。现在RecyclerView强制使用ViewHolder,并且ViewHolder除了有对子视图的引用,还有诸如ItemViewType和Position这些信息。

RecyclerView.LayoutManager

LayoutManager相对于ListView来说是一个新部分,通过继承该类来实现自定义的布局方式,而不像ListView只有固定的布局方式。Support库提供了两个现成的子类:LinearLayoutManager和StaggeredGridLayoutManager。前者可以获得和ListView一样的布局,还可以是水平方向的;后者则提供了形如GridView的布局。所以应用程序中的基本日常所以都可以被满足。

如果需要实现自定义的LayoutManager,就比较麻烦了,需要理解Recycle、Scrap、Dirty这些关于子项目视图状态的概念。本人还没有尝试过创建一个自定义的LayoutManager,但在文末的引用文档中有部分介绍实现方式的。

RecyclerView.ItemAnimator

通过实现继承实现ItemAnimator,然后创建对象设置到RecyclerView上就可以得到基于子项目的动画效果。不过如何正确合理地创建一个ItemAnimator子类,却没有详细的描述指南。

窥探库中提供的唯一一个可用的子类DefaultItemAnimator,可以看出它的动画效果是简单的alpha渐变。同时也会发现其实现是如此的复杂,有很多对于动画步骤的操作,还得注意动画在中途被打断的处理,在结束时也要重置视图状态以便重用。这也反过来说明ItemAnimator基本没有提供任何关于如何实现和管理动画的信息。另一方面因为DefaultItemAnimator的实现过于具体,因此它并不是合适作为自定义ItemAnimator的父类。

相信当RecyclerView越来越多的被应用到程序中时,更多关于这方面的合理设计会被提出来。目前在Github上也有不少参考了DefaultItemAnimator的实现方式,比如这个,还有这个。

关于ItemAnimator的使用,有几点值得提醒的是:如果没有提供ItemAnimator,RecyclerView默认会创建一个DefaultItemAnimator用于动画,所以不需要显示地设置DefaultItemAnimator对象到RecyclerView上;添加(add)和删除(remove)是默认起效的,但是修改(change)的效果需要调用setSupportsChangeAnimations(boolean)来指定是否启用,其默认是没有修改的动画。

总体而言,RecyclerView的功能非常强大,其结构设计也十分开放,这也造成它的上手使用相对比较困难。随着越来越多的人开始尝试使用这个部件,也会有越来越深刻的理解和设计实现。另外,阅读RecyclerView的源码可以帮助对其设计思想的了解,在以后设计其它的复用视图时可以有更好的参照。

DEMO

说了这么多,我们来看一个RecyclerVIew实现图片文字按钮的混排的demo:

首先还是看我的工程结构吧。

首先还是贴出我的main_acitivy.xml

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <!-- A RecyclerView with some commonly used attributes --> <android.support.v7.widget.RecyclerView android:id="@+id/my_recycler_view" android:scrollbars="vertical" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>

其他几个xml就不用贴了,很简单的放了写TextVIew,ImgeView之类。

然后我们就来看看代码,首先是Bean里面的代码。

package com.androidl.bob; public class Bean { public static final int Y_TYPE = 0; //view类型0 public static final int X_TYPE = 1; //view类型2 public static final int Z_TYPE = 2;//view 类型3 private int type; private String text; public Bean(int type, String text) { super(); this.type = type; this.text = text; } public int getType() { return type; } public void setType(int type) { this.type = type; } public String getText() { return text; } public void setText(String text) { this.text = text; } }

然后是Adapter里面的代码:

package com.androidl.bob; import java.util.List; import com.example.androidl.R; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; /** * Date : 2014/7/15 * * @author edsheng * */ public class RecycleAdapter extends RecyclerView.Adapter<ViewHolder> { private List<Bean> beans; public RecycleAdapter(List<Bean> beans) { super(); this.beans = beans; } /** * 内部TextHoler * * @author edsheng * */ public class TextHoler extends RecyclerView.ViewHolder { public TextView textView; public TextHoler(View textview) { super(textview); this.textView = (TextView) textview.findViewById(R.id.mytext); } } /** * iamgeHolder * * @author edsheng * */ public class ImageHoler extends RecyclerView.ViewHolder { public ImageView Imageview; public ImageHoler(View textview) { super(textview); this.Imageview = (ImageView) textview.findViewById(R.id.myiamge); } } /** * 按钮的holder * * @author edsheng * */ public class ButtonHolder extends RecyclerView.ViewHolder { public Button button; public ButtonHolder(View textview) { super(textview); this.button = (Button) textview.findViewById(R.id.mybutton); } } @Override public int getItemCount() { // TODO Auto-generated method stub return beans.size(); } /** * 获取消息的类型 */ @Override public int getItemViewType(int position) { // TODO Auto-generated method stub return beans.get(position).getType(); } /** * 创建VIewHolder */ @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewtype) { // TODO Auto-generated method stub View v = null; ViewHolder holer = null; switch (viewtype) { case Bean.X_TYPE: v = LayoutInflater.from(parent.getContext()).inflate( R.layout.recylce_item_x, null); holer = new TextHoler(v); break; case Bean.Y_TYPE: v = LayoutInflater.from(parent.getContext()).inflate( R.layout.recylce_item_y, null); holer = new ButtonHolder(v); break; case Bean.Z_TYPE: v = LayoutInflater.from(parent.getContext()).inflate( R.layout.recylce_item_z, null); holer = new ImageHoler(v); break; } return holer; } /** * 绑定viewholder */ @Override public void onBindViewHolder(ViewHolder holder, int position) { // TODO Auto-generated method stub switch (getItemViewType(position)) { case Bean.X_TYPE: TextHoler textholer = (TextHoler) holder; textholer.textView.setText(beans.get(position).getText()); break; case Bean.Y_TYPE: ButtonHolder buttonHolder = (ButtonHolder) holder; buttonHolder.button.setText(beans.get(position).getText()); break; case Bean.Z_TYPE: ImageHoler imageHoler = (ImageHoler) holder; // imageHoler.Imageview.setImageResource(android.R.drawable.checkbox_on_background); break; } } }

最后是activity的代码。

package com.androidl.bob; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import com.example.androidl.R; public class Mainactivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view); // // improve performance if you know that changes in content // // do not change the size of the RecyclerView // mRecyclerView.setHasFixedSize(true); //创建布局管理器 LinearLayoutManager mLayoutManager = new LinearLayoutManager(this); mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); mRecyclerView.setLayoutManager(mLayoutManager); //初始化数据 List<Bean> myDataset = new ArrayList<Bean>(); myDataset.add(new Bean(Bean.Z_TYPE, "图片")); myDataset.add(new Bean(Bean.X_TYPE, "文字")); myDataset.add(new Bean(Bean.Y_TYPE, "按钮")); myDataset.add(new Bean(Bean.Z_TYPE, "图片")); myDataset.add(new Bean(Bean.X_TYPE, "shit")); myDataset.add(new Bean(Bean.X_TYPE, "我擦")); myDataset.add(new Bean(Bean.Z_TYPE, "图片")); myDataset.add(new Bean(Bean.Y_TYPE, "按钮")); myDataset.add(new Bean(Bean.Y_TYPE, "按钮")); myDataset.add(new Bean(Bean.X_TYPE, "文字")); //创建Adapter RecycleAdapter mAdapter = new RecycleAdapter(myDataset); mRecyclerView.setAdapter(mAdapter); } }

时间: 2024-08-01 23:08:10

深入解析Android中的RecyclerView组件的相关文章

深入解析Android中的RecyclerView组件_Android

前些日子,组里为了在目前的Android程序里实现基于ListView子项的动画效果,希望将最新的RecyclerView引入到程序中,于是我便花了一些时间研究了一下RecyclerView的基本情况.本文算是对这些日子里了解的内容做一些汇总. 在网上关于RecyclerView的基本使用方式已经有了比较详细介绍,而且其设计结构也类似于ListView,所以本文将不重点介绍如何使用,在文末的引用中都可以相关内容.这里主要是介绍RecyclerView的基本功能.设计理念,以及系统提供API的情况

xml-如何解析 android 中的 XML?

问题描述 如何解析 android 中的 XML? 我需要把下面的 xml 解析 为输出: String id="3" String name="str1" String path="/mnt/sdcard/path2" String type="2" String desc="des3)" 在 android中如何实现呢? <xmldump> <mfs id="3"

Android中使用RecyclerView实现下拉刷新和上拉加载_Android

推荐阅读:使用RecyclerView添加Header和Footer的方法                       RecyclerView的使用之HelloWorld RecyclerView 是Android L版本中新添加的一个用来取代ListView的SDK,它的灵活性与可替代性比listview更好.本文给大家介绍如何为RecyclerView添加下拉刷新和上拉加载,过去在ListView当中添加下拉刷新和上拉加载是非常方便的利用addHeaderView和addFooterVie

Android中使用RecyclerView实现下拉刷新和上拉加载

推荐阅读:使用RecyclerView添加Header和Footer的方法 RecyclerView的使用之HelloWorld RecyclerView 是Android L版本中新添加的一个用来取代ListView的SDK,它的灵活性与可替代性比listview更好.本文给大家介绍如何为RecyclerView添加下拉刷新和上拉加载,过去在ListView当中添加下拉刷新和上拉加载是非常方便的利用addHeaderView和addFooterView,RecyclerView的刷新同样也是需

Android 中使用RecyclerView实现底部翻页

RecyclerView 是Android L版本中新添加的一个用来取代ListView的SDK,它的灵活性与可替代性比listview更好.接下来通过一系列的文章讲解如何使用RecyclerView,彻底抛弃ListView. 最近在做pad端的app,需要一个像网页一样效果,之前使用addView方式,页码少的时候还可以,能实现效果,但是碰到了一个1000多页的界面,就GG了,页码半天显示不出来,于是使用RecyclerView作为容器,主要是看中RecyclerView的复用,不说了,看代

Android中的RecyclerView新组件初步上手指南

介绍 RecyclerView是support-v7包中的新组件,是一个强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,但是直接把viewholder的实现封装起来,用户只要实现自己的viewholder就可以了,该组件会自动帮你回收复用每一个item. 它不但变得更精简,也变得更加容易使用,而且更容易组合设计出自己需要的滑动布局. RecyclerView与ListView原理是类似的:都是仅仅维护少量的View并且可以展示大量的数据集.RecyclerView用

Android中对RecyclerView Adapter封装解析

前言 关于adapter的封装,网上有很多开源库,开发的时候可以直接拿来用,省了很多事. 最近闲来无事,想着自己动手封装一个adapter. 问题 1.通常我们封装的时候,可以简化到这一步: BaseRecyclerViewAdapter adapter = new BaseRecyclerViewAdapter() { private static final int TYPE_FIR = 1; private static final int TYPE_SEC = 2; private st

从源码解析Android中View的容器ViewGroup_Android

 这回我们是深入到ViewGroup内部\,了解ViewGroup的工作,同时会阐述更多有关于View的相关知识.以便为以后能灵活的使用自定义空间打更近一步的基础.希望有志同道合的朋友一起来探讨,深入Android内部,深入理解Android. 一.ViewGroup是什么?       一个ViewGroup是一个可以包含子View的容器,是布局文件和View容器的基类.在这个类里定义了ViewGroup.LayoutParams类,这个类是布局参数的子类.        其实ViewGrou

从源码剖析Android中的Intent组件_Android

我们知道,Intent主要用来激活安卓几大组件,那么它具体是怎样来激活的?激活时是否可以携带java对象?为何要将对象序列化后才能传递? 一.Intent官网解释Intent可以被startActivity用来加载Activity,也可以被broadcastIntent发送给指定的BroadReceiver组件, 或者被startService.bingService来与后台service通信. Intent最主要作用就是加载Activity,好比Activity之间的胶水. Intent数据结