[转]RecyclerView初探
原文地址:http://www.grokkingandroid.com/first-glance-androids-recyclerview/
RecyclerView是去年谷歌I/O大会上随Android L预览一起发布的,接下来的几篇译文将会从各个方面对RecyclerView做一个全面的介绍:
本文示例代码项目地址:https://github.com/writtmeyer/recyclerviewdemo
为什么叫RecyclerView?
谷歌在Android L预览版API文档中是这样描述的:
一个非常灵活的用于在有限的窗口范围内显示大量数据的控件。
所以RecyclerView适用于那些有大量同类的View但是不能同时在屏幕中显示的情况,比如联系人、用户列表、音乐文件列表等等。想看到更多信息需要滚动试图,同时对离开屏幕的视图进行回收和重用。
以下图片解释了这个过程:左边是应用程序的初始状态,当向上滚动视图的时候,一些子View准备被回收,比如右边的红框部分中的两个不可见的View。现在回收器可以把他们放入准备重用的View的列表以便在适当的时候重用。
对View的回收重用是非常有用的,由于不用每次都重新填充布局,这样可以节约CPU资源;同时由于不用保存大量的不可见的视图,也有效的节省了内存资源。
看到这里可能有人要说,这个概念早就有了,ListView就是使用这样的机制。是的没错,但是在ListView中,控件外观、回收过程和其他操作是紧耦合的,缺乏灵活性,这就是为什么谷歌要开发RecyclerView来取代它的意义所在。
RecyclerView本身不关心视图相关的问题
由于ListView的紧耦合问题,谷歌的改进就是RecyclerView本身不参与任何视图相关的问题。它不关心如何将子View放在合适的位置,也不关心如何分割这些子View,更不关心每个子View各自的外观。进一步来说,RecyclerView只负责回收和重用的工作,这就是它名字的由来。
所有关于布局、绘制和其他相关的问题,也就是跟数据展示相关的所有问题,都被委派给了一些”插件化”的类来处理。这使得RecyclerView的API变得非常灵活。你需要一个新的布局么?接入另一个LayoutManager就可以了!你想要不同的动画么?接入一个新的ItemAnimator就可以了,诸如此类。
以下是RecyclerView中用于数据展示的一些重要的类,他们都是RecyclerView的内部类:
- Adapter:包装数据集合并且为每个条目创建视图。
- ViewHolder:保存用于显示每个数据条目的子View。
- LayoutManager:将每个条目的视图放置于适当的位置。
- ItemDecoration:在每个条目的视图的周围或上面绘制一些装饰视图。
- ItemAnimator:在条目被添加、移除或者重排序时添加动画效果。
下面将对这些类进行分别介绍:
RecyclerView.ViewHolder
ViewHolder的基本作用是缓存视图对象,Android官方很早钱就推荐使用ViewHolder的编程模式,但是现在使用这种模式将是强制性的。
RecyclerView.ViewHolder的子类都可以通过ViewHolder的public的成员变量itemView来访问每个条目的根视图,所以ViewHolder子类中不需要在保存这个视图。
以下是ViewHolder的示例代码:
public final static class ListItemViewHolder extends RecyclerView.ViewHolder {
TextView label;
TextView dateTime;
public ListItemViewHolder(View itemView) {
super(itemView);
label = (TextView)itemView.findViewById(R.id.txt_label_item);
dateTime = (TextView)itemView.findViewById(R.id.txt_date_time);
}
}
RecyclerView.Adapter
Adapter充当两个角色:访问数据集合以及负责为每个条目创建正确的视图。Adapter在Android中经常出现,比如ListView、AutoCompleteTextView、Spinner等,他们都继承自AdapterView,但是RecyclerView并没有这样做。
对于RecyclerView,谷歌决定使用新的RecyclerView.Adapter基类来取代旧的Adapter接口。所以,SimpleCursorAdapter、ArrayAdapter都将成为历史,或者至少不会是他们现在的这种使用方式。
目前RecyclerView.Adapter还没有默认实现,以后可能会添加。由于RecyclerView.Adapter是一个抽象类,所以必须要实现以下三个方法:
- public VH onCreateViewHolder(ViewGroup parent, int viewType)
- public void onBindViewHolder(VH holder, int position)
- public int getItemCount()
其中VH是泛型类,当继承RecyclerView.Adapter时需要使用具体的类来替换。Adapter的示例代码如下:
public class RecyclerViewDemoAdapter extends RecyclerView.Adapter <RecyclerViewDemoAdapter.ListItemViewHolder> {
private List<DemoModel> items;
RecyclerViewDemoAdapter(List<DemoModel> modelData) {
if (modelData == null) {
throw new IllegalArgumentException(
"modelData must not be null");
}
this.items = modelData;
}
@Override
public ListItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View itemView = LayoutInflater.
from(viewGroup.getContext()).
inflate(R.layout.item_demo_01,
viewGroup,
false);
return new ListItemViewHolder(itemView);
}
@Override
public void onBindViewHolder(ListItemViewHolder viewHolder, int position) {
DemoModel model = items.get(position);
viewHolder.label.setText(model.label);
String dateStr = DateUtils.formatDateTime(
viewHolder.label.getContext(),
model.dateTime.getTime(),
DateUtils.FORMAT_ABBREV_ALL);
viewHolder.dateTime.setText(dateStr);
}
@Override
public int getItemCount() {
return items.size();
}
public final static class ListItemViewHolder
extends RecyclerView.ViewHolder {
// ... shown above in the ViewHolder section
}
}
RecyclerView.LayoutManager
LayoutManager可能是RecyclerView中最有趣的部分了,该类负责放置所有的子View到适当位置。该类有一个默认的实现:LinearLayoutManager,当要实现水平或垂直列表时可以使用该类。
必须要给RecyclerView设置一个LayoutManager,否则会抛出异常:
08-01 05:00:00.000 2453 2453 E AndroidRuntime: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v7.widget.RecyclerView$LayoutManager.onMeasure(android.support.v7.widget.RecyclerView$Recycler, android.support.v7.widget.RecyclerView$State, int, int)' on a null object reference
08-01 05:00:00.000 2453 2453 E AndroidRuntime: at android.support.v7.widget.RecyclerView.onMeasure(RecyclerView.java:1310)
目前LayoutManager只有一个抽象方法:
public LayoutParams generateDefaultLayoutParams()
但是还有一个方法需要重写,因为这个方法很快就要变成抽象的了:
public void scrollToPosition(int position) {
if (DEBUG) {
Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract");
}
}
但是只重写以上两个方法还远远不够,毕竟LayoutManager的责任是放置所有子View到适当的位置上,所以还需要重写onLayoutChildren()方法:
public void onLayoutChildren(Recycler recycler, State state) {
Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}
LinearlayoutManager
LinearLayoutManager是目前LayoutManager唯一的默认实现,可以用来创建水平或者垂直列表。
LinearLayout非常复杂,这里只简单谈谈其中一些关键部分。LinearLayoutManager的用法是非常简单的:初始化它,然后设置一个方向(水平/垂直)就可以了:
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
layoutManager.scrollToPosition(currPos);
recyclerView.setLayoutManager(layoutManager);
LinearLayoutManager还提供了一些方法用来查找当前屏幕上的第一个和最后一个条目:
- findFirstVisibleItemPosition()
- findFirstCompletelyVisibleItemPosition()
- findLastVisibleItemPosition()
- findLastCompletelyVisibleItemPosition()
RecyclerView.ItemDecoration
使用ItemDecoration可以给每个条目添加偏移量、可以给条目间添加间距或者高亮条目,添加其他修饰效果等等。
ItemDecoration并不是必须使用的,例如可以使用CardView作为每个条目的视图。
另外,可以添加任意数量的ItemDecoration,RecyclerView会遍历所有的ItemDecoration然后按适当顺序调用每一个的绘制方法
ItemDecoration虚基类包含以下三个方法:
- public void onDraw(Canvas c, RecyclerView parent)
- public void onDrawOver(Canvas c, RecyclerView parent)
- public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)
onDraw中绘制的内容可能会被条目内容所覆盖,如果要在条目上绘制内容需要使用onDrawOver()方法。如果需要条目间距来绘制分割线,那么使用以上两个方法的差别不大,但是如果真的要添加装饰效果,还是需要使用onDrawOver()。
LayoutManager在测量阶段会调用getItemOffset()方法来正确计算每个条目视图的大小。outRect参数可能第一眼看来有些奇怪,为什么不使用一个返回值呢?但是这样的参数设置确实是有意义的,它使得对于所有的子视图RecyclerView可以重用同一个Rect对象,节省了资源开销。这并不是必须的,但确实是很有效率的。
RecyclerView.ItemAnimator
ItemAnimator可以为每个条目添加动画效果,它主要处理以下三种事件:
- An item gets added to the data set
- An item gets removed from the data set
- An item moves as a result of one or more of the previous two operations
ItemAnimator有一个默认实现:DefaultItemAnimator,如果没有设置自定义的ItemAnimator,RecyclerView就会使用默认的DefaultItemAnimator。
显然如果要让动画生效,需要知道数据集合的变化,这需要Adapter的帮助。在之前的版本中,当变化发生改变时,需要调用notifyDataSetChanged()方法。但这是不合适的,因为这个方法会触发对于所有可视子View的重绘,而且没有动画效果。要看到动画效果需要使用更加特殊的方法:
RecyclerView.Adapter类包含很多notifyXXX()类型的方法,两个最常用的是:
- public final void notifyItemInserted(int position)
- public final void notifyItemRemoved(int position)
监听器
RecyclerVIew提供了一些非常通用的监听器。之前常见的那些使用方式现在都不适用了,RecyclerView没有提供OnItemClickListener或者OnItemLongClickListener。但是可以使用RecyclerView.OnItemTouchListener结合手势监听来处理这些事件。需要的编码工作必之前达到同样的效果要多。
把以上所有类结合起来
布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".RecyclerViewDemoActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
tools:listitem="@layout/item_demo_01"/>
<ImageButton
android:id="@+id/fab_add"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_width="@dimen/fab_size"
android:layout_height="@dimen/fab_size"
android:layout_gravity="bottom|right"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/ripple"
android:stateListAnimator="@anim/anim"
android:src="@drawable/ic_action_add"
android:elevation="1dp"/>
</RelativeLayout>
RecyclerView的AttributeSet会在generateLayoutParams()方法中使用:
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
if (mLayout == null) {
throw new IllegalStateException("RecyclerView has no LayoutManager");
}
return mLayout.generateLayoutParams(getContext(), attrs);
}
程序代码部分很简单:
setContentView(R.layout.activity_recyclerview_demo);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
layoutManager.scrollToPosition(0);
recyclerView.setLayoutManager(layoutManager);
// allows for optimizations if all item views are of the same size:
recyclerView.setHasFixedSize(true);
// For the sake of simplicity I misused the Application subclass as a DAO
List<DemoModel> items = RecyclerViewDemoApp.getDemoData();
adapter = new RecyclerViewDemoAdapter(items);
recyclerView.setAdapter(adapter);
RecyclerView.ItemDecoration itemDecoration =
new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST);
recyclerView.addItemDecoration(itemDecoration);
// this is the default;
// this call is actually only necessary with custom ItemAnimators
recyclerView.setItemAnimator(new DefaultItemAnimator());
// onClickDetection is done in this Activity's OnItemTouchListener
// with the help of a GestureDetector;
// Tip by Ian Lake on G+ in a comment to this post:
// https://plus.google.com/+LucasRocha/posts/37U8GWtYxDE
recyclerView.addOnItemTouchListener(this);
gesturedetector =
new GestureDetectorCompat(this, new RecyclerViewDemoOnGestureListener());
主要步骤如下:
- 获取RecyclerView的引用
- 创建一个LayoutManager并添加
- 创建一个Adapter并添加
- 如果需要的话,创建1个或多个ItemDecoration并添加
- 如果需要的话,创建ItemAnimator并添加
- 如果需要的话,创建1个或多个监听器并添加
当然这只是初步的代码,真正有趣的部分在于RecyclerView有很多内部类,可以用来继承并且实现各种自定义的效果,这才是真正需要投入大量工作的地方。
[转]RecyclerView初探的更多相关文章
- Android RecyclerView初探
今天研究了一下RecyclerView,RecyclerView比ListView的效率更高而且可以横向滑动,所以现在许多Android项目更倾向与使用RecyclerView. 下面是一个Recyc ...
- Android开发学习之路-RecyclerView使用初探
在进行一些MaterialDesign规范开发的时候,比如之前说到的CoordinateLayout实现的向上折叠效果的时候,如果依然使用ListView,那么这种效果是做不出来的,因为ListVie ...
- Android开发学习之路-RecyclerView滑动删除和拖动排序
Android开发学习之路-RecyclerView使用初探 Android开发学习之路-RecyclerView的Item自定义动画及DefaultItemAnimator源码分析 Android开 ...
- RecyclerView使用大全
RecylerView介绍 RecylerView是support-v7包中的新组件,是一个强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,这一点从它的名字recyler ...
- 带你实现开发者头条APP(五)--RecyclerView下拉刷新上拉加载
title: 带你实现开发者头条APP(五)--RecyclerView下拉刷新上拉加载 tags: -RecyclerView,下拉刷新,上拉加载更多 grammar_cjkRuby: true - ...
- 安卓易学,爬坑不易——腾讯老司机的RecyclerView局部刷新爬坑之路
针对手游的性能优化,腾讯WeTest平台的Cube工具提供了基本所有相关指标的检测,为手游进行最高效和准确的测试服务,不断改善玩家的体验.目前功能还在免费开放中. 点击地址:http://wetest ...
- Android Studio开发RecyclerView遇到的各种问题以及解决(二)
开发RecyclerView时候需要导入别人的例子,我的是从github导入的,下载下github的压缩包之后解压看你要导入的文件是priject还是Module.(一般有app文件夹的大部分是pro ...
- Android Studio开发RecyclerView遇到的各种问题以及解决(一)
以前一直在用ListView,,,最近才看RecyclerView发现好强大.RecyclerView前提是Android版本在5.0以上,本人以前用的是eclipse只支持到4.4.索性就安装一个A ...
- Android的Kotlin秘方(II):RecyclerView 和 DiffUtil
作者:Antonio Leiva 时间:Sep 12, 2016 原文链接:http://antonioleiva.com/recyclerview-diffutil-kotlin/ 如你所知,在[支 ...
随机推荐
- hdu 2211
题意: 中文题目,自己看.............. 递归调用.... 没什么难度,注意下long long就行........ AC代码: #include <iostream> #de ...
- Wcf资料收集
1.简介 http://www.tuicool.com/articles/mqYB32 使用规范 http://blog.51cto.com/zt/219 2.教程系列 http://www.cnbl ...
- Android开发手记(12) Menu的使用
Android中的Menu分为三种,分别为:OptionsMenu(选项菜单).ContextMenu(上下文菜单).SubMenu(子菜单). 1.OptionsMenu 按Menu键就会显示,用于 ...
- Java直接插入算法
直接插入算法是将N个带排序的元素看做成一个有序表和一个无序表. 每次从无序表中取一个元素和有序表比较,重复N-1次完成排序. 直接上代码: package test; public class Tes ...
- 【推荐】Java工程师如何从普通成为大神值得一读
本文源自 http://www.hollischuang.com/archives/489 一点感悟 java作为一门编程语言,在各类编程语言中作为弄潮儿始终排在前三的位置,这充分肯定了java语言的 ...
- geotools导入shp文件到Oracle数据库时表名带下划线的问题解决
问题: 最近在做利用geotools导入shp文件到Oracle表中,发现一个问题Oracle表名带下划线时导入失败,问题代码行: dsOracle.getFeatureWriterAppend(or ...
- poj1182 并查集
题目连接:http://poj.org/problem?id=1182 基础并查集,需要维护与根节点关系,解析见代码: /* poj 1182 并查集 思路分析:让你分析这些话里面多少假的 只需要用 ...
- js十进制等互相转换
//十进制转其他 var x=110; alert(x); alert(x.toString(8)); alert(x.toString(32)); alert(x.toString(16)); / ...
- json格式初涉
json用{}表示json对象,[]表示数组,里面以值对的方式来存储信息 var jsondata='{"staff": [{"name":"jim& ...
- DIRECTORY_SEPARATOR
定义 php的内置变量DIRECTORY_SEPARATOR是一个显示系统分隔符的命令,DIRECTORY_SEPARATOR是php的内部常量,不需要任何定义与包含即可直接使用. 2说明 路径分 ...