RecyclerView使用方法举例(1)
RecyclerView是在Android L中新加入的ViewGroup,可以通过Support Library在更早的版本的Android上使用。文档中对RecyclerView的概述只有一行“A flexible view for providing a limited window into a large data set”,Google希望用RecyclerView来替代之前的ListView和GridView。
在ListView和GridView中,数据的获取和显示等功能是紧耦合的,所以用了两个类来分别实现列表和网格的显示。而RecyclerView只关心“Recycle”,至于里面的各个item如何摆放、列表如何显示,统统委托给了其他类来负责。这使得RecyclerView在带来更加灵活丰富的功能的同时,使用上也变得更加复杂,很多ListView和GridView中集成的功能,都需要手动实现。使用RecyclerView,通常离不开一下几个类:
- Adapter:和ListView和GridView类似,用于包装数据,并负责单个item的创建。Adapter通过ViewHolder获得当前item中需要更新的View。
- ViewHolder:持有当前item的所有子View,RecyclerView的Adapter强制使用View Holder的模式。
- LayoutManager:负责摆放item。
- ItemDecoration:在item周围绘制装饰,如分隔线。
- ItemAnimator:在对item进行添加、移除、重新排列操作时添加动画效果。
下面通过一个例子介绍RecyclerView的简单使用方法。这个例子从TMDb上获取若干流行电影的信息,使用RecyclerView以网格的形式显示出来。
Contents
1. 原始数据获取
例子中使用的数据来自TMDb,详情请参考Retrofit使用方法举例。后续内容可以接着Retrofit使用方法举例中提供的例子继续进行。
2. 添加Layout
2.1. 添加RecyclerView
使用Support Library版本的RecyclerView可以获得向前兼容。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/movie_grid" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>
完整文件在这里。
2.2. 添加Item
下面定义RecyclerView中每一个item的布局,包含一个用于显示海报的ImageView和一个用于显示电影名称的TextView。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" tools:layout_width="200dp" tools:layout_height="300dp" > <ImageView android:id="@+id/movie_poster" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:adjustViewBounds="true" tools:src="@drawable/placeholder_poster" /> <TextView android:id="@+id/movie_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="16sp" android:singleLine="true" tools:text="Jurassic World" /> </LinearLayout>
完整文件在这里。
3. 实现Adapter
前面定义类item的布局,但RecyclerView还不知道如何把数据填到item里面去,这是Adapter的工作。RecyclerView的Adapter强制使用ViewHolder的模式,由ViewHolder持有item中的所有子View。定义MovieAdapter 如下,它继承了RecyclerView.Adapter<MovieAdapter.ViewHolder> ,包含一个Movie的List作为数据集:
public class MovieAdapter extends RecyclerView.Adapter<MovieAdapter.ViewHolder> { private List<Movie> mMovies; public MovieAdapter(List<Movie> movies) { mMovies = movies; } }
要实现MovieAdapter ,需要实现MovieAdapter.ViewHolder ,并重写onCreateViewHolder() 、onBindViewHolder() 和getItemCount() 三个方法。
3.1. 实现ViewHolder
下面来实现MovieAdapter.ViewHolder ,这里以内部类的形式实现:
public class MovieAdapter extends RecyclerView.Adapter<MovieAdapter.ViewHolder> { ... public static class ViewHolder extends RecyclerView.ViewHolder { public ImageView mPoster; public TextView mTitle; public ViewHolder(View itemView) { super(itemView); mPoster = (ImageView) itemView.findViewById(R.id.movie_poster); mTitle = (TextView) itemView.findViewById(R.id.movie_title); } } }
ViewHolder 构造器中,在itemView 下面找到用于显示海报的movie_poster 和movie_title ,保存为成员变量。
3.2. 重写onCreateViewHolder()
onCreateViewHolder() 用于创建单个item,获取LayoutInflater后,inflate之前定义的item的布局item_movie ,并以此实例化ViewHolder。
@Override public MovieAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { Context context = parent.getContext(); LayoutInflater inflater = LayoutInflater.from(context); View contactView = inflater.inflate(R.layout.item_movie, parent, false); ViewHolder viewHolder = new ViewHolder(contactView); return viewHolder; }
在inflater.inflate(R.layout.item_movie, parent, false) 中的最后一个参数false 为”attachToRoot“,如果为true,由此inflate得到的View会自动加到parent中,作为parent的子View。
3.3. 重写onBindViewHolder()
onBindViewHolder() 负责将数据填入item。
@Override public void onBindViewHolder(MovieAdapter.ViewHolder holder, int position) { Movie movie = mMovies.get(position); holder.mTitle.setText(movie.getTitle()); String key = movie.getPosterPath(); String url = ImageUtility.getImageUrl(key); Picasso.with(holder.itemView.getContext()) .load(url) .error(R.drawable.placeholder_poster_white) .placeholder(R.drawable.placeholder_poster_white) .into(holder.mPoster); }
由position 从数据集中取出对应元素,填入ViewHolder的对应View中。这里将电影名称填入mTitle ,并使用Picasso将海报图片下载并加载到mPoster 。Picasso是一个Android上的图片下载和缓存库,使用方法十分简单,这里从url 下载图片并显示在holder.mPoster 中,在下载完成之前,使用R.drawable.placeholder_poster_white 显示在holder.mPoster 里作为占位符,如果下载失败,依然显示R.drawable.placeholder_poster_white 。注意从TMDb得到的海报链接是一个key,需要经过处理得到完整链接,详情可以参考这里的“Image Languages”一节。
3.4. 重写getItemCount()
getItemCount() 用于返回当前数据集中数据元素的个数。这里使用的是List,获取其长度十分简单。
@Override public int getItemCount() { return mMovies.size(); }
以上完整文件可以参考这里。
4. 设置RecyclerView
首先找到RecyclerView:
mRecyclerView = (RecyclerView) rootView.findViewById(R.id.movie_grid);
然后进行设置:
private MovieAdapter mMovieAdapter; private List<Movie> mMovies = new ArrayList<>(); ... private void setupRecyclerView(@NonNull RecyclerView recyclerView) { mMovieAdapter = new MovieAdapter(mMovies); recyclerView.setAdapter(mMovieAdapter); GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 2); recyclerView.setLayoutManager(layoutManager); recyclerView.setHasFixedSize(true); }
首先将mMovies 传入MovieAdapter作为数据集,并使用recyclerView.setAdapter() 将mMovieAdapter 作为RecyclerView的Adapter。然后设置布局,这里使用GridLayoutManager 获得网格的布局,new GridLayoutManager(getContext(), 2) 中的2 表示有两列,使用recyclerView.setLayoutManager() 配置到RecyclerView。最后recyclerView.setHasFixedSize(true) 表示RecyclerView中的每一个item都有固定的尺寸(RecyclerView支持显示各种不同尺寸的item),如果每一个item大小都一样,可借此来优化显示效率。
完整文件在这里。
5. 通知数据更新
前面mMovieAdapter = new MovieAdapter(mMovies); 的时候,mMovies 中可能还是空的,在从TMDb获得电影数据,填充mMovies 后,需要通知MovieAdapter有数据更新。
mMovies.addAll(movies); mMovieAdapter.notifyItemInserted(0);
mMovies 初始是空的,这里将新数据加入,最后使用notifyItemInserted(0) 通知mMovieAdapter 在位置0添加了数据。RecyclerView.Adapter还提供了很多其他的方法用于通知数据更新,可以参考这里,其中notifyDataSetChanged() 最为万能,不需要任何参数,但效率也最低,应作为最后手段。
完整文件在这里。
6. 完整代码
完整代码可以在这里找到。运行效果如图1所示。