目标
本篇是RecyclerView的重构之路系列文章的第四篇, 讲解的是IDouban项目 使用RecyclerView。展示豆瓣 影院热映 电影。读完之后,您会知道RecyclerView,CardView的使用。
总体套路
熟悉ListView, GridView同学知道,要使用ListView等控件,需要以下几个步骤:
- 布局添加
ListView - 构建
Adapter, 设置setAdapter - 构建数据集
List - 处理OnItemClickListener
想要使用灵活性,基本会选择BaseAdapter用于扩展,里面的核心就是四个重写的方法:
getCount, getItem, getItemId, getView, 而getView又是重中之重。其中还会涉及到优化的问题, 我曾在课堂上直观演示过不使用优化的ListView,内存消耗直线上升。
但是,Google使用support包的方式发布了RecyclerView, 在使用之前觉得,也没有什么好用的,写得代码还要多些,但是随着深入了解,发现Google里的老司机,真是厉害,带我装逼,带我飞。
初识RecyclerView
这不是详细讲解RecyclerView的地方,网上一堆堆的内容,随便你看。简书里 就有好多文章介绍以及使用RV(RecyclerView)。
这里提供几个网页:
- https://developer.android.com/training/material/lists-cards.html (中文的哦!)
- https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html (api文档)
- https://guides.codepath.com/android/using-the-recyclerview (强烈推荐实践, 而且有新大陆)
一句话总结 什么是RecyvlerView: 它是集ListView, GridView...于一体的,能实现横排,竖排,以及瀑布流形式的效果,在默认情况下启用增添与删除项目的动画并可扩展动画。
如何使用RecyclerView
- 布局添加
RecyclerView - 构建
RecyclerView.Adapter, 设置setAdapter - 构建数据集
List - 自己实现ItemClick事件
实现步骤
重点来讲解具体的使用吧,让你们久等了。
CardView
俗称 卡片, 这个也是Google使用support包的形式新增加的。需要添加 依赖:
compile 'com.android.support:cardview-v7:23.4.0' 这是我当前的版本
xml文件引入需要前缀: android.support.v7.widget.CardView
CardView现阶段,我使用在MoviesFragment, 以及RecyclerView 的布局文件中。
布局添加RecyclerView

构建RecyclerView.Adapter
Item布局

自定义ViewHolder
RecyclerView强制要求使用ViewHolder, 并且 RecyclerView.Adapter.onCreateViewHolder返回就是ViewHolder类型。So,
static class MoviesViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
ImageView mMovieImage;
TextView mMovieTitle;
RatingBar mMovieStars;
TextView mMovieRatingAverage;
Movies movie;
public MoviesViewHolder(View itemView) {
super(itemView);
mMovieImage = (ImageView) itemView.findViewById(R.id.movie_cover);
mMovieTitle = (TextView) itemView.findViewById(R.id.movie_title);
mMovieStars = (RatingBar) itemView.findViewById(R.id.rating_star);
mMovieRatingAverage = (TextView) itemView.findViewById(R.id.movie_average);
itemView.setOnClickListener(this);
}
public void updateMovie(Movies movie) {
if (movie == null) return;
this.movie = movie;
Context context = itemView.getContext();
Picasso.with(context)
.load(movie.getImages().getLarge())
.placeholder(context.getResources().getDrawable(R.mipmap.ic_launcher))
.into(mMovieImage);
mMovieTitle.setText(movie.getTitle());
final double average = movie.getRating().getAverage();
if (average == 0.0) {
mMovieStars.setVisibility(View.GONE);
mMovieRatingAverage.setText(context.getResources().getString(R.string.string_no_note));
} else {
mMovieStars.setVisibility(View.VISIBLE);
mMovieRatingAverage.setText(String.valueOf(average));
mMovieStars.setStepSize(0.5f);
mMovieStars.setRating((float) (movie.getRating().getAverage() / 2));
}
}
@Override
public void onClick(View v) {
Log.e(HomeActivity.TAG, "==> onClick....Item");
if (movie == null) return;
if (itemView == null) return;
Context context = itemView.getContext();
if (context == null) return;
Intent intent = new Intent(context, MovieDetailActivity.class);
intent.putExtra("movie", movie);
if (context instanceof Activity) {
Activity activity = (Activity) context;
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, mMovieImage, "cover").toBundle();
ActivityCompat.startActivity(activity, intent, bundle);
}
}
}
说明:
1 MoviesViewHolder构造方法 用于保存itemView, 并初始化其中的控件。
2 updateMovie方法用于Adapter更新数据, 这里独立成一个方法, 也为onClick方法提供数据支持。
3 onClick方法,重写View.OnClickListener的监听事件, 用于跳转到电影详情界面。
自定义Adapter
static class MoviesAdapter extends RecyclerView.Adapter<MoviesViewHolder> {
private List<Movies> movies;
private Context context;
@LayoutRes
private int layoutResId;
public MoviesAdapter(Context context, @NonNull List<Movies> movies, @LayoutRes int layoutResId) {
this.movies = movies;
this.layoutResId = layoutResId;
this.context = context;
}
@Override
public MoviesViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(context).inflate(layoutResId, parent, false);
return new MoviesViewHolder(itemView);
}
@Override
public void onBindViewHolder(MoviesViewHolder holder, int position) {
if (holder == null) return;
holder.updateMovie(movies.get(position));
}
@Override
public int getItemCount() {
return movies.size();
}
public void setData(List<Movies> movies) {
this.movies = movies;
notifyDataSetChanged();
}
}
说明:
Adapter是RecyclerView和数据的媒介, 所以Adapter中需要有List<Movies>, 还需要设备上下文Context (后续优化,不需要), 以及外界传入的itemView 的布局文件, 以上基本就是MoviesAdapter构造方法的用途。
继承RecyclerView.Adapter的类,需要重写其中的三个方法:
-
onCreateViewHolder, 执行一次,用于加载itemView布局 -
onBindViewHolder, 频繁执行,类似ListView中的getView -
getItemCount, 用户统计有多少item
这里还增加一个数据添加方法setData,用于外界更新数据
RecyclerView设定
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (mRecyclerView != null) {
mRecyclerView.setHasFixedSize(true);
final GridLayoutManager layoutManager = new GridLayoutManager(getActivity().getApplicationContext(), 3);
mRecyclerView.setLayoutManager(layoutManager);
mMovieAdapter = new MoviesAdapter(getContext(), mMoviesList, R.layout.recyclerview_movies_item);
mRecyclerView.setAdapter(mMovieAdapter);
}
}
RecyclerView的设定动作,我是放在onActivityCreated方法中。
final GridLayoutManager layoutManager = new GridLayoutManager(getActivity().getApplicationContext(), 3);
mRecyclerView.setLayoutManager(layoutManager);
RV 就是使用LayoutManger来显示 竖直,还是横排,还是瀑布流效果。IDouban我是使用GridLayoutManger效果,有3列。
假如要ListView形式的,采用LinearLayoutManager, 瀑布流形式的 采用 StaggeredGridLayoutManager
其他代码基本跟ListView相似!
构建数据集List
上一篇文章详细解答了这个问题,使用Retrofit获取到网络数据,然后保存到List<Movies>中, 何时存放在AdapterList中,只需要在获得数据的时刻,通过setData方法把Movies列表传入即可。
实现ItemClick事件
有点不方便的是 RV并没提供OnItemClickListener接口之类的, 这个在知乎上有人分析过。也给出一些实现方法。
我这里采用 在ViewHolder中实现View.OnClickListener 并没有在Adapter的onBindViewHolder中每次去new匿名内部类方式。
利用其中的itemView来实现点击Item响应事件。