Android RecyclerView控件是在2014年6月Google的I/O大会上推出的Android 5.0时特有的特性,该控件的功能非常强大,它代替了原来的ListView和GridView的控件,它除了能实现线性布局、网格布局样式外还可以实现瀑布流布局的样式。而且在RecyclerView在源码中实现了复用机制的功能,使在渲染界面和填充数据方面都比原来的ListView还要好,在滑动的过程中也比较顺畅。
那么下面就来看看如何使用RecyclerView吧,本文将介绍RecyclerView的线性布局样式、网格布局样式、瀑布流布局样式以及嵌套布局样式的使用。
在使用RecyclerView控件之前先在AS项目的build.gradle里引入相关的依赖包。
implementation 'com.android.support:recyclerview-v7:28.0.0'
从依赖包中可以看到,该依赖包是在v7包下的,所在layout目录下xml布局文件中使用该布局时要写全引用的路径:android.support.v7.widget.RecyclerView。布局如下:
在该布局中的控件com.scwang.smartrefresh.layout.SmartRefreshLayout是使用目前在GitHub上比较火的对RecyclerView进行实现下拉刷新上拉加载分页功能的开源库,具体的使用的方式可以去该开源的地址去看看,该开源库的地址为:https://github.com/scwang90/SmartRefreshLayout 。
在layout目录下创建一个xml布局,该布局是用来适配RecyclerView的线性布局加载的item条目的布局。该布局的样式如下:
接着创建一个Adapter来绑定视图、渲染和填充数据以及设置RecyclerView item条目的点击事件的监听接口。代码如下:
public class RecyclerViewAdapter extends RecyclerView.Adapter {
private Context mContext;
private List mList;
private MovieDataModel data;
private OnItemClikListener mOnItemClikListener;
public RecyclerViewAdapter(Context context, List mList) {
this.mContext = context;
this.mList = mList;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_movie_layout, parent, false);
ViewHolder holder = new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
data = mList.get(position);
RequestOptions options = new RequestOptions()
.placeholder(R.drawable.img_default_movie)
.error(R.drawable.img_default_movie);
Glide.with(mContext).load(data.getDownimgurl()).apply(options).into(holder.home_item_movie_list_pic);
holder.home_item_movie_list_title.setText(data.getDownLoadName());
holder.home_item_movie_list_director.setText(data.getDirector());
holder.home_item_movie_list_Starring.setText(data.getStarring());
holder.home_item_movie_list_type.setText(data.getType());
holder.home_item_movie_list_regions.setText(data.getRegions());
if (mOnItemClikListener != null) {
// 设置item条目短按点击事件的监听
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.getLayoutPosition();
mOnItemClikListener.onItemClik(holder.itemView, pos);
}
});
// 设置item条目长按点击事件的监听
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int pos = holder.getLayoutPosition();
mOnItemClikListener.onItemLongClik(holder.itemView, pos);
return false;
}
});
}
}
@Override
public int getItemCount() {
return mList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private ImageView home_item_movie_list_pic;
private TextView home_item_movie_list_title;
private TextView home_item_movie_list_director;
private TextView home_item_movie_list_Starring;
private TextView home_item_movie_list_type;
private TextView home_item_movie_list_regions;
public ViewHolder(View itemView) {
super(itemView);
home_item_movie_list_pic = (ImageView) itemView.findViewById(R.id.home_item_movie_list_pic);
home_item_movie_list_title = (TextView) itemView.findViewById(R.id.home_item_movie_list_title);
home_item_movie_list_director = (TextView) itemView.findViewById(R.id.home_item_movie_list_director);
home_item_movie_list_Starring = (TextView) itemView.findViewById(R.id.home_item_movie_list_Starring);
home_item_movie_list_type = (TextView) itemView.findViewById(R.id.home_item_movie_list_type);
home_item_movie_list_regions = (TextView) itemView.findViewById(R.id.home_item_movie_list_regions);
}
}
// 定义item条目点击事件接口
public interface OnItemClikListener {
void onItemClik(View view, int position);
void onItemLongClik(View view, int position);
}
public void setItemClikListener(OnItemClikListener mOnItemClikListener) {
this.mOnItemClikListener = mOnItemClikListener;
}
}
最后在Activity或者Fragment中进行对数据的解析和设置。代码如下:
/**
* RecyclerView线性布局样式
*/
public class RecyclerViewActivity extends BaseActivity {
@BindView(R.id.mTitleBar)
TitleBar mTitleBar;
@BindView(R.id.loading_view_ll)
LinearLayout loading_view_ll;
@BindView(R.id.loading_view)
ImageView mLoadingView;
@BindView(R.id.refreshLayout)
RefreshLayout refreshLayout;
@BindView(R.id.rvMovieList)
RecyclerView rvMovieList;
private boolean refreshType;
private int page;
private int oldListSize;
private int newListSize;
private int addListSize;
private String viewType;
private RecyclerViewAdapter adapter;
private List mList = new ArrayList<>();
@Override
protected int getLayoutId() {
return R.layout.activity_recycler_view;
}
@Override
protected void initView() {
ButterKnife.bind(this);
Intent intent = getIntent();
viewType = intent.getStringExtra("ViewType");
mTitleBar.setOnTitleBarListener(new OnTitleBarListener() {
@Override
public void onLeftClick(View v) {
finish();
}
@Override
public void onTitleClick(View v) {
}
@Override
public void onRightClick(View v) {
}
});
AnimationDrawable anim = (AnimationDrawable) mLoadingView.getDrawable();
anim.start();
}
@Override
protected void initData() {
// 开启自动加载功能(非必须)
refreshLayout.setEnableAutoLoadMore(true);
refreshLayout.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh(@NonNull final RefreshLayout refreshLayout) {
refreshLayout.getLayout().postDelayed(new Runnable() {
@Override
public void run() {
refreshType = true;
page = 1;
parsingMovieListJson();
refreshLayout.finishRefresh();
refreshLayout.resetNoMoreData();//setNoMoreData(false);
}
}, 2000);
}
});
refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadMore(@NonNull final RefreshLayout refreshLayout) {
refreshLayout.getLayout().postDelayed(new Runnable() {
@Override
public void run() {
refreshType = false;
if (page > 2) {
ToastUtil.showToast("暂无更多的数据啦");
// 将不会再次触发加载更多事件
refreshLayout.finishLoadMoreWithNoMoreData();
return;
}
parsingMovieListJson();
refreshLayout.setEnableLoadMore(true);
refreshLayout.finishLoadMore();
}
}, 2000);
}
});
//触发自动刷新
refreshLayout.autoRefresh();
}
private void parsingMovieListJson() {
try {
// 从assets目录中获取json数据,在真实的项目开发中需要通过网络请求从服务器json数据
String jsonData = BaseTools.getAssetsJson(this, "movie" + page + ".json");
if (refreshType && mList != null) {
mList.clear();
oldListSize = 0;
} else {
oldListSize = mList.size();
}
// 使用Google的Gson开始解析json数据
Gson gson = new Gson();
MovieBaseModel movieBaseModel = gson.fromJson(jsonData, MovieBaseModel.class);
List movieDataModelList = movieBaseModel.getData();
for (MovieDataModel movieDataModel : movieDataModelList) {
MovieDataModel data = new MovieDataModel();
data.setMovClass(movieDataModel.getMovClass());
data.setDownLoadName(movieDataModel.getDownLoadName());
data.setDownimgurl(movieDataModel.getDownimgurl());
data.setDownLoadUrl(movieDataModel.getDownLoadUrl());
data.setMvdesc(movieDataModel.getMvdesc());
OtherBaseModel otherModelDesc = gson.fromJson(movieDataModel.getMvdesc(), OtherBaseModel.class);
List headerList = otherModelDesc.getHeader();
data.setDirector(headerList.get(1));
data.setStarring(headerList.get(2));
data.setType(headerList.get(3));
data.setRegions(headerList.get(4));
mList.add(data);
}
} catch (Exception e) {
e.printStackTrace();
}
newListSize = mList.size();
addListSize = newListSize - oldListSize;
if (refreshType) {
// 设置RecyclerView样式为竖直线性布局
rvMovieList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
adapter = new RecyclerViewAdapter(this, mList);
if (viewType.equals("NoDividingLine")) {
mTitleBar.setTitle("线性布局样式");
} else {
mTitleBar.setTitle("线性布局(有分割线)样式");
// 设置分割线
rvMovieList.addItemDecoration(new CustomDecoration(
this, LinearLayoutManager.VERTICAL, R.drawable.divider_mileage, 15));
}
rvMovieList.setAdapter(adapter);
} else {
adapter.notifyItemRangeInserted(mList.size() - addListSize, mList.size());
adapter.notifyItemRangeChanged(mList.size() - addListSize, mList.size());
}
page++;
rvMovieList.setVisibility(View.VISIBLE);
loading_view_ll.setVisibility(View.GONE);
// item条目的点击事件回调
adapter.setItemClikListener(new RecyclerViewAdapter.OnItemClikListener() {
// 短按点击事件回调
@Override
public void onItemClik(View view, int position) {
String videoTitle = mList.get(position).getDownLoadName();
ToastUtil.showToast(videoTitle);
}
// 长按点击事件回调
@Override
public void onItemLongClik(View view, int position) {
}
});
}
}
从以上代码可以看到,refreshLayout.setOnRefreshListener(new OnRefreshListener()方法是实现下拉刷新数据的功能,refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener()是实现上拉加载分页数据的功能。而 rvMovieList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false))这段代码是设置RecyclerView样式为竖直线性布局。 rvMovieList.addItemDecoration(new CustomDecoration(this, LinearLayoutManager.VERTICAL, R.drawable.divider_mileage, 15))这段代码是实现RecyclerView item条目之间的分割线的效果,而RecyclerView与ListView不同是RecyclerView默认是没有分割线的,如果开发者需要实现分割线功能的话只能自己定义。最后adapter.setItemClikListener(new RecyclerViewAdapter.OnItemClikListener()该回调的接口是实现RecyclerView item条目的点击事件回调。
在Activity里模拟以网络请求的方式从服务端获取json数据并解析设置到视图界面上,json的数据结构如下:
{
"Code":200,
"Msg":"数据获取成功",
"Data":[
{
"movClass":"科幻片",
"downLoadName":"海市蜃城",
"downLoadUrl":"",
"mvdesc":"{"header": ["别名:", "导演:孙田", "主演:刘端端,王滢,高强,刘依琳", "类型:科幻片 悬疑 科幻", "地区:大陆", "语言:国语", "上映:2019", "片长:91", "更新:2019-05-20 00:42:56", "总播放量:", "今日播放量:0", "总评分数:0", "评分次数:0"], "desc": "阿可是一个游戏公司的CEO兼游戏设计师,最近一直在设计一款即将上市的游戏《魔界通缉》,这款游戏为玩家提供了更加真实的体验,在最后的一次玩家测试时,一位玩家差点因为游戏丢了性命,且一直未查明原因。压力很大的阿可开始生活中经常出现幻觉。同时眼睛也出了问题。游戏设计如期完成,期间阿可一直阻止公司召开游戏发布会,希望能查清游戏的安全性,但被一个神秘人阻挠。游戏发布会如期召开,引起粉丝疯狂捧场,神秘人在发布会上也代替了阿可的发言。游戏上线后阿可发现游戏开始慢慢控制玩家,同时发生了越来越多的不可思议的事情。当阿可想删除游戏源代码时,所有玩家对阿可开始了真正的通缉……"}",
"downimgurl":"https://tupian.tupianzy.com/pic/upload/vod/2019-05-20/201905201558283504.jpg",
"mv_update_time":"2019-05-20",
"downdtitle":"zuidam3u8,zuidall,迅雷下载",
"id":"10313",
"mv_md5_id":"4b406e86b4eaeea68fb7fc7c0608c348",
"type":"00000000001"
}
]
}
界面运行效果图如下:
其中的第一张图是RecyclerView线性布局样式默认没有分割线的运行效果图,第二种是自定义有分割线的运行效果图。
RecyclerView网格布局样式的实现只需要修改item布局以及部分Adapter、Activity的代码。
item布局的xml布局代码如下:
修改以下Activity中的代码:
// 设置RecyclerView样式为网格布局,一行显示3列
rvMovieList.setLayoutManager(new GridLayoutManager(this, 3));
adapter = new GridLayoutRVAdapter(this, mList);
rvMovieList.setAdapter(adapter);
可以看到,GridLayoutManager是设置RecyclerView样式为网格布局,其中的第二个参数设置一行显示3列item条目。
界面运行效果图如下:
RecyclerView瀑布流布局样式可能在实际的项目开发中用的比较少,比较常用的是前两种的线性布局和网格布局,不过瀑布流样式在电商的App中用的比较多,比如淘宝的Android端App的首页就用到RecyclerView瀑布流布局样式。如下图:
RecyclerView瀑布流布局样式可以在网格布局样式的基础来修改实现,首先修改item条目的布局样式,布局代码如下:
从布局文件代码中可以看到,瀑布流的代码和网格的布局有两处的不同,就是把图像的控件ImageView的高度修改为"wrap_content"值,而网格是改为固定值,还有一处就是把在瀑布流的图像控件ImageView添加了android:adjustViewBounds="true"属性,这个属性的作用就是使显示图片能够自适合来显示,使瀑布流样式的效果更好看。
接着修改Activity中的代码,修改的代码如下:
// 设置RecyclerView样式为瀑布流布局,一行显示2列
rvMovieList.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
adapter = new StaggeredGridRVAdapter(this, mList);
rvMovieList.setAdapter(adapter);
代码中的StaggeredGridLayoutManager是设置RecyclerView样式为网格布局,其中的第一个参数设置一行显示2列item条目,第二个参数设置竖直显示。
界面运行效果图如下:
在实现RecyclerView嵌套布局样式之前先看一下嵌套布局的json数据结构:
{
"reason":"成功的返回",
"data":[
{
"title":"高清电影",
"content":[
{
"name":"异类侵袭",
"tag":"地区:大陆",
"url":"",
"thumbnail_pic_s":"https://tupian.tupianzy.com/pic/upload/vod/2019-05-19/201905191558252942.png"
}
]
},
{
"title":"热门剧集",
"content":[
{
"name":"筑梦情缘[DVD版]",
"tag":"地区:大陆",
"url":"",
"thumbnail_pic_s":"https://tupian.tupianzy.com/pic/upload/vod/2019-05-10/201905101557455675.jpg"
}
]
}
],
"error_code":0
}
从json的数据结构中可以看出,"data"字段是一个数组字段,数组字段中包含若干个的集合字段,可以把数组字段当作是一个父布局的RecyclerView,若干个集合字段当作是一个子布局的RecyclerView。也就是说嵌套布局是由一个RecyclerView布局嵌套一个或多个RecyclerView的。
而嵌套布局样式主要是Adapter设置数据绑定时比较麻烦,首次定义一个父Adapter,在由父Adapter去映射到子Adapter。父Adapter代码如下:
public class MovieTypesAdapter extends RecyclerView.Adapter {
private Context mContext;
private List mList;
private MovieTypesDataModel data;
private OnItemClikListener mOnItemClikListener;
public MovieTypesAdapter(Context context, List mList) {
this.mContext = context;
this.mList = mList;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.movie_types_title, parent, false);
ViewHolder holder = new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
data = mList.get(position);
holder.movieTypesTitle.setText(data.getTitle());
MovieTypesChildAdapter childAdapter = (MovieTypesChildAdapter) holder.movieTypesChild.getAdapter();
//适配器复用
if (childAdapter == null) {
RecyclerView.LayoutManager manager = new GridLayoutManager(mContext, 3);
holder.movieTypesChild.setLayoutManager(manager);
holder.movieTypesChild.setAdapter(new MovieTypesChildAdapter(mContext, data.getContent(), position));
} else {
childAdapter.setData(data.getContent()); //重新设置数据
childAdapter.notifyDataSetChanged();
}
if (mOnItemClikListener != null) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.getLayoutPosition();
mOnItemClikListener.onItemClik(holder.itemView, pos);
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int pos = holder.getLayoutPosition();
mOnItemClikListener.onItemLongClik(holder.itemView, pos);
return false;
}
});
}
}
@Override
public int getItemCount() {
return mList == null ? 0 : mList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private TextView movieTypesTitle;
private RecyclerView movieTypesChild;
public ViewHolder(View itemView) {
super(itemView);
movieTypesTitle = (TextView) itemView.findViewById(R.id.movieTypesTitle);
movieTypesChild = (RecyclerView) itemView.findViewById(R.id.movieTypesChild);
}
}
public interface OnItemClikListener {
void onItemClik(View view, int position);
void onItemLongClik(View view, int position);
}
public void setItemClikListener(OnItemClikListener mOnItemClikListener) {
this.mOnItemClikListener = mOnItemClikListener;
}
}
子Adapter代码如下:
public class MovieTypesChildAdapter extends RecyclerView.Adapter {
private Context mContext;
private List childList;
private int mPosition;
private MovieTypesDataModel.MovieTypesContentModel data;
private OnItemClikListener mOnItemClikListener;
public MovieTypesChildAdapter(Context context, List childList) {
this.mContext = context;
this.childList = childList;
}
public MovieTypesChildAdapter(Context context, List childList, int position) {
this.mContext = context;
this.childList = childList;
this.mPosition = position;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_movie_grid, parent, false);
ViewHolder holder = new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
data = childList.get(position);
RequestOptions options = new RequestOptions()
.placeholder(R.drawable.img_default_movie)
.error(R.drawable.img_default_movie);
Glide.with(mContext).load(data.getThumbnail_pic_s()).apply(options).into(holder.iv_photo);
holder.tv_name.setText(data.getName());
holder.tv_regions.setText(data.getTag());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.getLayoutPosition();
NestRVActivity.instance.OnClickListener(mPosition, pos);
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return false;
}
});
}
@Override
public int getItemCount() {
return childList == null ? 0 : childList.size();
}
public void setData(List childList) {
this.childList = childList;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private ImageView iv_photo;
private TextView tv_name;
private TextView tv_regions;
public ViewHolder(View itemView) {
super(itemView);
iv_photo = (ImageView) itemView.findViewById(R.id.iv_photo);
tv_name = (TextView) itemView.findViewById(R.id.tv_name);
tv_regions = (TextView) itemView.findViewById(R.id.tv_regions);
}
}
public interface OnItemClikListener {
void onItemClik(View view, int position);
void onItemLongClik(View view, int position);
}
public void setItemClikListener(OnItemClikListener mOnItemClikListener) {
this.mOnItemClikListener = mOnItemClikListener;
}
}
界面运行效果图如下:
可以扫描以下二维码进行下载安装,或者点击以下链接 https://fukaimei.top/apk/RecyclerViewTest.apk 进行下载安装体验。
———————— The end ————————
Demo程序源码下载地址一(GitHub)
Demo程序源码下载地址二(码云)