A First Glance at Android’s RecyclerView

2023-12-11 16:38

本文主要是介绍A First Glance at Android’s RecyclerView,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

August 14th, 2014  By Wolfram Rittmeyer  41 Comments

At this year’s Google I/O, Google released a preview to the upcoming Android version. The so called L Developer Preview. This is a very big improvement over previous releases and I really love that Google is doing this. I think we all benefit by this decision. As developers and as consumers alike!

Part of this preview are two new views: RecyclerView and CardView. This post gives you an introduction to the RecyclerView, it’s many internal classes and interfaces, how they interact and how you can use them.

Let me start with the good news: RecyclerView is part of the support library. So you can use it right now. Ok: You can use it as soon as the final support lib accompanying the L release gets released. So better to familiarize yourself with it right away :-)

Sample project

The screenshots and the video at the end of the post show the sample project for this post in action. You can find the source of this sample at github. Keep in mind that the RecyclerView API is not yet finalized. Google might still change things that could break the sample when they release the final version of Android L.

What’s with this odd name? Why RecyclerView?

This is how Google describes RecyclerView in the API documentation of the L preview release:

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

So RecyclerView is the appropriate view to use when you have multiple items of the same type and it’s very likely that your user’s device cannot present all of those items at once. Possible examples are contacts, customers, audio files and so on. The user has to scroll up and down to see more items and that’s when the recycling and reuse comes into play. As soon as a user scrolls a currently visible item out of view, this item’s view can be recycled and reused whenever a new item comes into view.

The following screenshots of the sample app illustrate this. On the left is the sample app after the initial start. When you scroll the view up, some views become eligible for recycling. The red area on the right screenshot, for example, highlights two invisible views. The recycler can now put these views into a list of candidates to be reused should a new view be necessary.

These two screens show what recycling means. On the right two views are eligible for reuseThese two screens show what recycling means. On the right two views are eligible for reuse

Recycling of views is a very useful approach. It saves CPU resources in that you do not have to inflate new views all the time and it saves memory in that it doesn’t keep plenty of invisible views around.

Now, you might say: That’s nothing new. And you’re right! We had that withListView for a very long time. The concept of recycling views itself it not new. But while you previously had a ListView where the appearance, recycling and everything was tightly coupled, Google now follows a much better, a much more flexible approach with the new RecyclerView. I really like the approach Google has taken here!

RecyclerView doesn’t care about visuals

Here’s the thing: While with Listview we had tight coupling, Google now uses an approach where the RecyclerView itself doesn’t care about visuals at all. It doesn’t care about placing the elements at the right place, it doesn’t care about separating any items and not about the look of each individual item either. To exaggerate a bit: All RecyclerView does, is recycle stuff. Hence the name.

Anything that has to do with layout, drawing and so on, that is anything that has to do with how your data set is presented, is delegated to pluggable classes. That makes the new RecyclerView API extremely flexible. You want another layout? Plug in another LayoutManager. You want different animations? Plug in an ItemAnimator. And so on.

Here’s the list of the most important classes that RecyclerView makes use of to present the data. All these classes are inner classes of the RecyclerView:

The most important classes of the RecyclerView API
Class Usage
Adapter Wraps the data set and creates views for individual items
ViewHolder Holds all sub views that depend on the current item’s data
LayoutManager Places items within the available area
ItemDecoration Draws decorations around or on top of each item’s view
ItemAnimator Animates items when they are added, removed or reordered

In the next paragraphs I will briefly describe what each class or interface is about and how RecyclerView uses it. In future posts I will revisit some of these classes, write about them in detail and show you how to customize them for your project’s needs.

ViewHolder

ViewHolders are basically caches of your View objects. The Android team has been recommending using the ViewHolder pattern for a very long time, but they never actually enforced the use of it. Now with the new Adapter you finally have to use this pattern.

It’s a bit weird that Google waited so long to enforce the usage of the ViewHolder pattern, but better late than never. If you do not know about the ViewHolder pattern, have a look at this Android training session. It uses the old Adapter, but the pattern itself hasn’t changed.

Also searching for ViewHolder should yield plenty of hits to further blog posts. For example this post by Antoine Merle about ListView optimizations.

One thing that is specific to any RecyclerView.ViewHolder subclass is that you can always access the root view of your ViewHolder by accessing the public member itemView. So there’s no need to store that within your ViewHoldersubclass.

And should you decide to override toString() have a look at the base class. ItstoString() implementation prints some useful information you should consider to use for your log messages as well.

Here’s the code for the ViewHolder of the sample project. The ViewHolder is an inner class of the sample project’s Adapter:

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

Adapters fulfill two roles: They provide access to the underlying data set and they are responsible for creating the correct layout for individual items. Adapters always were part of Android and were used in many places. ListView,AutoCompleteTextViewSpinner and more all made use of adapters. All those classes inherit from AdapterView. But not so RecyclerView.

For the new RecyclerView Google has decided to replace the old Adapter interface with a new RecyclerView.Adapter base class. So say good bye to things likeSimpleCursorAdapterArrayAdapter and the like. At least in their current incarnation.

Currently there is no default implementation of RecyclerView.Adapter available. Google might add some later on, but I wouldn’t bet on this. For Animations to work properly, cursors and arrays aren’t the best fit, so porting the current Adapter implementations might not make too much sense.

Since RecyclerView.Adapter is abstract you will have to implement these three methods:

  • public VH onCreateViewHolder(ViewGroup parent, int viewType)
  • public void onBindViewHolder(VH holder, int position)
  • public int getItemCount()

The VH in the method signatures above is the generic type parameter. You specify the concrete type to use when you subclass the RecyclerView.Adapter. You can see this in line 3 of the next code sample.

The most basic adapter for the sample layout looks like this:

public class RecyclerViewDemoAdapter extendsRecyclerView.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;}@Overridepublic ListItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_demo_01,viewGroup,false);return new ListItemViewHolder(itemView);}@Overridepublic 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);}@Overridepublic int getItemCount() {return items.size();}public final static class ListItemViewHolderextends RecyclerView.ViewHolder {// … shown above in the ViewHolder section}
}

RecyclerView.LayoutManager

The LayoutManager is probably the most interesting part of the RecyclerView. This class is responsible for the layout of all child views. There is one default implementation available: LinearLayoutManager which you can use for vertical as well as horizontal lists.

You have to set a LayoutManager for your RecyclerView otherwise you will see an exception at Runtime:

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)

Only one method of LayoutManager is currently abstract:

  • public LayoutParams generateDefaultLayoutParams()

But there is another one where the code states that you should overrride it since it’s soon going to be abstract:

public void scrollToPosition(int position) {if (DEBUG) {Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract");}
}

That’s very weird! Why not make it abstract right away? Anyway: Better you override this one to be on the safe side for when Google releases the final version of L.

But only overriding those two methods won’t get you very far. After all theLayoutManager is responsible for positioning the items you want to display. Thus you have to override onLayoutChildren() as well.

This method also contains a log statement stating “You must override onLayoutChildren(Recycler recycler, State state)”. Ok, then make it abstract :-) Luckily there’s still plenty (?) of time to change that into a proper abstract method for the final release of L. We all make mistakes. After all, my “Stupid stuff devs make” series is all about blunders that I made. So don’t get me wrong. No hard feelings here!

LinearlayoutManager

The LinearLayoutManager is currently the only default implementation ofLayoutManager. You can use this class to create either vertical or horizontal lists.

The implementation of LinearLayoutManager is rather complex and I only had a look at some key aspects. I will return to this implementation in my post about custom LayoutManagers.

To use the LinearLayoutManager you simply have to instantiate it, tell it which orientation to use and you are done:

LinearLayoutManager layoutManager = new LinearLayoutManager(context);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
layoutManager.scrollToPosition(currPos);
recyclerView.setLayoutManager(layoutManager);

LinearLayoutManager also offers some methods to find out about the first and last items currently on screen:

  • findFirstVisibleItemPosition()
  • findFirstCompletelyVisibleItemPosition()
  • findLastVisibleItemPosition()
  • findLastCompletelyVisibleItemPosition()

Surprisingly these methods are not part of the source code in the SDK folder, but you can use them as they are part of the binaries. As I cannot imagine those being removed, I’m sure you’ll find these in the final L release as well.

Other methods help you get the orientation of the layout or the current scroll state. Others will compute the scroll offset. And finally you can reverse the ordering of the items.

Since I’m going to write an extra post about LayoutManagers this should suffice for now.

RecyclerView.ItemDecoration

With an ItemDecoration you can add an offset to each item and modify the item so that items are separated from each other, highlighted or, well, decorated.

You do not have to use an ItemDecoration. If, for example, you use a CardViewfor each item, there’s no need for an ItemDecoration.

On the other hand you can add as many ItemDecorations as you like. TheRecyclerView simply iterates over all ItemDecorations and calls the respective drawing methods for each of them in the order of the decoration chain.

The abstract base class contains these three methods:

  • public void onDraw(Canvas c, RecyclerView parent)
  • public void onDrawOver(Canvas c, RecyclerView parent)
  • public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)

Anything you paint in onDraw() might be hidden by the content of the item views but anything that you paint in onDrawOver() is drawn on top of the items. If you simply create a bigger offset and, for example, use this offset to paint dividers, this of course is of no importance. But if you really want to add decorations, you have to use onDrawOver().

The LayoutManager calls the getItemOffset() method during the measurement phase to calculate the correct size of each item’s views. The outRect parameter might look a bit odd at first. Why not use a return value instead? But it really makes a lot of sense, since this allows RecyclerView to reuse one Rect object for all children and thus save resources. Not necessarily nice — but efficient.

One thing I didn’t expect considering the name of the class is that theonDraw()/onDrawOver() methods are not called for each item, but just once for every draw operation of the RecyclerView. You have to iterate over all child views of the RecyclerView yourself.

I will explain this in more detail in a follow-up post about writing your ownItemDecorations.

RecyclerView.ItemAnimator

The ItemAnimator class helps the RecyclerView with animating individual items.ItemAnimators deal with three events:

  • 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

Luckily there exists a default implementation aptly named DefaultItemAnimator. If you do not set a custom ItemAnimatorRecyclerView uses an instance ofDefaultItemAnimator.

Obviously for animations to work, Android needs to know about changes to the dataset. For this Android needs the support of your adapter. In earlier versions of Android you would call notifyDataSetChanged() whenever changes occured, this is no longer appropriate. This method triggers a complete redraw of all (visible) children at once without any animation. To see animations you have to use more specific methods.

The RecyclerView.Adapter class contains plenty of notifyXyz() methods. The two most specific are:

  • public final void notifyItemInserted(int position)
  • public final void notifyItemRemoved(int position)

The following video shows the result of an addition as well as a removal of an item in the sample app:

Video Player
00:00
00:10

A short video showing the default animations for the removal and addition of elements

Listeners

RecyclerView also offers some rather generic listeners. Once again you can safely forget everything you used to use up to now. There is noOnItemClickListener or OnItemLongClickListener. But you can use anRecyclerView.OnItemTouchListener in combination with gesture detection to identify those events. A bit more work and more code to achieve the same result. I still hope for Google to add those Listeners in the final release. But whether those Listeners will be added is as an open question.

Combining all classes

You combine the classes either in a fragment or an activity. For the sake of simplicity my sample app uses activities only.

First of all here’s the layout file containing the RecyclerView:

<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.RecyclerViewandroid:id="@+id/recyclerView"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"tools:listitem="@layout/item_demo_01"/><ImageButtonandroid: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>

As you can see, nothing special here. You do not define the orientation or stuff like that on the RecyclerView. Actually RecyclerView itself makes no use of the attributes, it passes them on to the parent (which is ViewGroup) and that’s it.

There is one place within RecyclerView where an AttributeSet is used and that is in the generateLayoutParams() method:

@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {if (mLayout == null) {throw new IllegalStateException("RecyclerView has no LayoutManager");}return mLayout.generateLayoutParams(getContext(), attrs);
}

In this snippet the RecyclerView passes the AttributeSet on to theLayoutManager.

The Java code is also pretty simple:

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());

Connecting all those elements together roughly consists of these steps:

  1. Get a reference to your RecyclerView
  2. Create a LayoutManager and add it
  3. Create an Adapter and add it
  4. Create zero or more ItemDecorations as needed and add them
  5. Create an ItemAnimator if needed and add it
  6. Create zero or more listeners as needed and add them

All in all about 30 lines of code.

Now of course this is misleading. That’s only the glue code. The really interesting stuff is in RecyclerView's many inner classes which you can subclass and tweak to your needs. That’s where the real work is done.

But the separation of concerns Google created helps you stick to one task within one implementation and it should make reuse easier to achieve. That’s why I like RecyclerView and its ecosystem. I’m not afraid to criticize big G, but that’s well done, Google!

Gradle integration

To use RecyclerView you have to add it to your gradle file. Adding the support library alone is not enough:

dependencies {//…compile ‘com.android.support:recyclerview-v7:+//…
}

Is that the final API?

Of course I do not know if the concrete implementations that the preview contains will be in the final release of Android L. But I guess so. And I expect some additions as well as minor changes to the API, based on bug reports and developer feedback.

Google itself gives one hint in the current API documentation about more stuff to come. The documentation for the RecyclerView.LayoutManager class contains this nugget:

Several stock layout managers are provided for general use.

So we can expect more LayoutManagers. Which, of course, is good. Furthermore I expect at least one default ItemDecoration as well. After all the support library’s sample project contains a DividerItemDecoration, which works well with the LinearLayoutManager.

I’m more skeptical about adapters. While an ArrayAdapter (or better yet,ListAdapter) is very well possible, I am more doubtful about a CursorAdaptersince cursors do not lend themself easily to the new addition and removal notifications within the Adapter.

Lucas Rocha’s TwoWayView to simplify your life

I strongly recommend to have a look at Lucas Rocha’s TwoWayView project. He has updated his project to work with RecyclerView and has done a great deal to make using RecyclerView a lot easier. For many use cases the default layouts he provides should suffice. And he also provides support for custom LayoutManagers. Which are simpler to write using his framework than with the base RecyclerView.

Take a look at his project and check out if it covers all you need. Using it helps you get rid of some of RecyclerView‘s complexity.

For more information about his project see his blog post about how TwoWayView extends and simplifies RecyclerView.

To learn about news about this project follow Lucas Rocha on Google plus orTwitter.

I will cover TwoWayView in this series as well – so stay tuned :-)

Report bugs!

We currently have a developer preview. The first time Google does this for Android. Really nice. But of course this preview is not free of bugs. To help us all get a more stable final release, give feedback or issue bug reports, if you encounter anything that bothers you or is a bug. There is a special L preview issue tracker.

And that’s it for today

I started this post as part of the preparation for my talk at the July meetup of the Dutch Android User Group.

I had much fun presenting about this topic at the July meetup of the Dutch Android User Group. And I had fun digging into this topic – and still have. Reading the source of RecyclerView and its many inner classes is really interesting. Thanks to the organizers for giving me the opportunity to speak about this topic and for forcing me to dig into this stuff :-)

At Utrecht I had 15-20 minutes for my talk. How I managed to get it done in time is still a mystery to me. As you can see there’s a lot to talk about – and this post is only the beginning.

Until next time!

这篇关于A First Glance at Android’s RecyclerView的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/481365

相关文章

Eclipse+ADT与Android Studio开发的区别

下文的EA指Eclipse+ADT,AS就是指Android Studio。 就编写界面布局来说AS可以边开发边预览(所见即所得,以及多个屏幕预览),这个优势比较大。AS运行时占的内存比EA的要小。AS创建项目时要创建gradle项目框架,so,创建项目时AS比较慢。android studio基于gradle构建项目,你无法同时集中管理和维护多个项目的源码,而eclipse ADT可以同时打开

android 免费短信验证功能

没有太复杂的使用的话,功能实现比较简单粗暴。 在www.mob.com网站中可以申请使用免费短信验证功能。 步骤: 1.注册登录。 2.选择“短信验证码SDK” 3.下载对应的sdk包,我这是选studio的。 4.从头像那进入后台并创建短信验证应用,获取到key跟secret 5.根据技术文档操作(initSDK方法写在setContentView上面) 6.关键:在有用到的Mo

android一键分享功能部分实现

为什么叫做部分实现呢,其实是我只实现一部分的分享。如新浪微博,那还有没去实现的是微信分享。还有一部分奇怪的问题:我QQ分享跟QQ空间的分享功能,我都没配置key那些都是原本集成就有的key也可以实现分享,谁清楚的麻烦详解下。 实现分享功能我们可以去www.mob.com这个网站集成。免费的,而且还有短信验证功能。等这分享研究完后就研究下短信验证功能。 开始实现步骤(新浪分享,以下是本人自己实现

Android我的二维码扫描功能发展史(完整)

最近在研究下二维码扫描功能,跟据从网上查阅的资料到自己勉强已实现扫描功能来一一介绍我的二维码扫描功能实现的发展历程: 首页通过网络搜索发现做android二维码扫描功能看去都是基于google的ZXing项目开发。 2、搜索怎么使用ZXing实现自己的二维码扫描:从网上下载ZXing-2.2.zip以及core-2.2-source.jar文件,分别解压两个文件。然后把.jar解压出来的整个c

android 带与不带logo的二维码生成

该代码基于ZXing项目,这个网上能下载得到。 定义的控件以及属性: public static final int SCAN_CODE = 1;private ImageView iv;private EditText et;private Button qr_btn,add_logo;private Bitmap logo,bitmap,bmp; //logo图标private st

Android多线程下载见解

通过for循环开启N个线程,这是多线程,但每次循环都new一个线程肯定很耗内存的。那可以改用线程池来。 就以我个人对多线程下载的理解是开启一个线程后: 1.通过HttpUrlConnection对象获取要下载文件的总长度 2.通过RandomAccessFile流对象在本地创建一个跟远程文件长度一样大小的空文件。 3.通过文件总长度/线程个数=得到每个线程大概要下载的量(线程块大小)。

时间服务器中,适用于国内的 NTP 服务器地址,可用于时间同步或 Android 加速 GPS 定位

NTP 是什么?   NTP 是网络时间协议(Network Time Protocol),它用来同步网络设备【如计算机、手机】的时间的协议。 NTP 实现什么目的?   目的很简单,就是为了提供准确时间。因为我们的手表、设备等,经常会时间跑着跑着就有误差,或快或慢的少几秒,时间长了甚至误差过分钟。 NTP 服务器列表 最常见、熟知的就是 www.pool.ntp.org/zo

高仿精仿愤怒的小鸟android版游戏源码

这是一款很完美的高仿精仿愤怒的小鸟android版游戏源码,大家可以研究一下吧、 为了报复偷走鸟蛋的肥猪们,鸟儿以自己的身体为武器,仿佛炮弹一样去攻击肥猪们的堡垒。游戏是十分卡通的2D画面,看着愤怒的红色小鸟,奋不顾身的往绿色的肥猪的堡垒砸去,那种奇妙的感觉还真是令人感到很欢乐。而游戏的配乐同样充满了欢乐的感觉,轻松的节奏,欢快的风格。 源码下载

Android SurfaceFlinger——图形内存分配器(十一)

前面的文章中的图层合成器(HWC),这里我们接着看一下 SurfaceFlinger 中的另一个重要服务——图形内存分配器。 一、简介         android.hardware.graphics.allocator@2.0 是 Android 系统中硬件抽象层(HAL)的一个组件,专门用于图形内存的分配和管理。它是 SurfaceFlinger 在处理图形数据时所依赖的

设置android返回键,保存和取得最高分

1.在.h中声明一些方法 virtual void keyBackClicked();           //Android返回键 bool isHaveSaveFile(); void getHighestHistoryScore(); 在.cpp中实现这个几个方法 void WelcomeLayer::keyBackClicked(