目标
本篇是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>
中, 何时存放在Adapter
List中,只需要在获得数据的时刻,通过setData
方法把Movies列表传入即可。
实现ItemClick事件
有点不方便的是 RV并没提供OnItemClickListener
接口之类的, 这个在知乎上有人分析过。也给出一些实现方法。
我这里采用 在ViewHolder
中实现View.OnClickListener
并没有在Adapter
的onBindViewHolder
中每次去new匿名内部类方式。
利用其中的itemView
来实现点击Item响应事件。