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所示。
