推荐:
Android RecyclerView 使用完全解析 体验艺术般的控件
高仿各大商城首页—使用分类型的RecyclerView来实现
RecyclerView是Android 5.0提出的新UI控件,可以用来代替传统的ListView、GridView。它是support-v7包中的新组件,是一个强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,这一点从它的名字recylerview即回收view也可以看出。
为什么会出现RecyclerView?
RecyclerView并不会完全替代ListView(这点从ListView没有被标记为@Deprecated可以看出),两者的使用场景不一样。但是RecyclerView的出现会让很多开源项目被废弃,例如横向滚动的ListView, 横向滚动的GridView, 瀑布流控件,因为RecyclerView能够实现所有这些功能。
比如有一个需求是屏幕竖着的时候的显示形式是ListView,屏幕横着的时候的显示形式是2列的GridView,此时如果用RecyclerView,则通过设置LayoutManager一行代码实现替换。
ListView vs RecyclerView
ListView相比RecyclerView,有一些优点:
- addHeaderView(), addFooterView()添加头视图和尾视图。
- 通过”android:divider”设置自定义分割线。
- setOnItemClickListener()和setOnItemLongClickListener()设置点击事件和长按事件。
- 这些功能在RecyclerView中都没有直接的接口,要自己实现(虽然实现起来很简单),因此如果只是实现简单的显示功能,ListView无疑更简单。RecycleView 和listView的一个区别就是本身不处理点击事件,点击事件应该绑在ViewHolder里面,可以直接写也可以通过接口绑在Adapter里面来实现。onCreateviewHolder函数和onBindViewHolder实现了ListView里面getView的工作,分别为找到控件和控件赋值。
RecyclerView相比ListView,有一些明显的优点:
- 默认已经实现了View的复用,不需要类似if(convertView == null)的实现,而且回收机制更加完善。RecylerView封装了viewholder的回收复用,也就是说RecylerView标准化了ViewHolder,编写Adapter面向的是ViewHolder而不再是View了,复用的 逻辑被封装了,写起来更加简单。
- 默认支持局部刷新。
notifyItemRemoved(position);// 删除item
notifyItemInserted(position);// 添加item - 容易实现添加item、删除item的动画效果。
mRecyclerView.setItemAnimator(new DefaultItemAnimator()); - 容易实现拖拽、侧滑删除等功能。
- RecyclerView是一个插件式的实现,对各个功能进行解耦,从而扩展性比较好。提供了一种插拔式的体验,高度的解耦,异常的灵活,针对一个Item的显示RecylerView专门抽取出了相应的类,来控制Item的显示,使其的扩展性非常强。例如:你想控制横向或者纵向滑动列表效果可以通过LinearLayoutManager这个类来进行控制(与GridView效果对应的是GridLayoutManager,与瀑布流对应的还有StaggeredGridLayoutManager等),也就是说RecylerView不再拘泥于ListView的线性展示方式,它也可以实现GridView的效果等多种效果。你想控制Item的分隔线,可以通过继承RecylerView的ItemDecoration这个类,然后针对自己的业务需求去抒写代码。
mRecyclerView.setLayoutManager(new GridLayoutManager(this, 4));
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(4,
StaggeredGridLayoutManager.HORIZONTAL));
导入依赖:
compile 'com.android.support:recyclerview-v7:24.1.0'
方法介绍:
RecyclerView提供了notifyItemInserted(),notifyItemRemoved(),notifyItemChanged()等API更新单个或某个范围的Item视图。ListView只提供了notifyDataSetChanged()更新整个视图。
RecyclerView的Adapter与ListView的Adapter还是有点区别的,RecyclerView.Adapter,需要实现3个方法:
onCreateViewHolder()
这个方法主要生成为每个Item inflater出一个View,但是该方法返回的是一个ViewHolder。该方法把View直接封装在ViewHolder中,然后我们面向的是ViewHolder这个实例,当然这个ViewHolder需要我们自己去编写。直接省去了当初的convertView.setTag(holder)和convertView.getTag()这些繁琐的步骤。
onBindViewHolder()
这个方法主要用于适配渲染数据到View中。方法提供给你了一个viewHolder,而不是原来的convertView。
getItemCount()
这个方法就类似于BaseAdapter的getCount方法了,即总共有多少个条目。
一般在adapter的构造方法中传入上下文和数据源,并创建填充器:
public MyRecyclerAdapter(Context context, List<String> datas){
this. mContext=context;
this. mDatas=datas;
inflater=LayoutInflater. from(mContext);
}
viewholder类中,找到itemview的各个控件
class MyViewHolder extends ViewHolder{
TextView tv;
public MyViewHolder(View view) {
super(view);
tv=(TextView) view.findViewById(R.id. tv_item);
}
}
复杂一点不同item布局的需要重写getItemViewType(int position),创建不同viewtype的viewholder。通过不同的viewtype在onCreateViewHolder填充不同的布局,在onBindViewHolder设置对应的数据。点击事件可以在viewholder中写,也可以在onBindViewHolder中写,也可以使用接口暴露出去。
RecyclerView的四大组成是:
1、Adapter:为Item提供数据。
一般使用:
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
/**
* RecyclerView将ListView中getView()的功能拆分成了onCreateViewHolder()和onBindViewHolder()。
*
* Created by xuqiang on 2017/1/12 10:08.
*/
public class RvAdapter extends RecyclerView.Adapter<RvAdapter.VHolder> {
private List<String> mData;
public RvAdapter(List<String> mData) {
this.mData = mData;
}
/*
* 映射Item Layout Id,创建VH并返回。
* */
@Override
public VHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_main, parent, false);//要写成这样
// View view=LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_main,null);//不可写成这种
return new VHolder(view);
}
/*
* 为holder设置指定数据。
* */
@Override
public void onBindViewHolder(RvAdapter.VHolder holder, int position) {
holder.mTextView.setText(mData.get(position));
holder.mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//item的点击事件
}
});
}
@Override
public int getItemCount() {
return mData.size();
}
public class VHolder extends RecyclerView.ViewHolder {
private TextView mTextView;
public VHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(R.id.mTextView);
}
}
}
推荐使用:
https://github.com/JoanZapata/base-adapter-helper
依赖为:
compile 'com.joanzapata.android:base-adapter-helper:1.1.11'
相关阅读:
Android base-adapter-helper 源码分析与扩展
2、Layout Manager:Item的布局。
recyclerView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false));//线性
recyclerView.setLayoutManager(new GridLayoutManager(this,2,LinearLayoutManager.VERTICAL,false));//网格
recyclerView.setLayoutManager(new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL));//流式
3、Item Animator:添加、删除Item动画。
RecyclerView能够通过mRecyclerView.setItemAnimator(ItemAnimator animator)设置添加、删除、移动、改变的动画效果。RecyclerView提供了默认的ItemAnimator实现类:DefaultItemAnimator。
DefaultItemAnimator继承自SimpleItemAnimator,SimpleItemAnimator继承自ItemAnimator。
推荐使用:
https://github.com/wasabeef/recyclerview-animators
导入依赖:
compile 'jp.wasabeef:recyclerview-animators:2.2.5'
4、Item Decoration:Item之间的Divider。
RecyclerView通过addItemDecoration()方法添加item之间的分割线。Android并没有提供实现好的Divider,因此任何分割线样式都需要自己实现。
方法是:创建一个类并继承RecyclerView.ItemDecoration,重写以下两个方法:
- onDraw(): 绘制分割线。
- getItemOffsets(): 设置分割线的宽、高。
添加点击事件:
RecyclerView默认没有像ListView一样提供setOnItemClickListener()接口。
把点击事件的实现写在Adapter的onBindViewHolder()中,不暴露出来。
@Override
public void onBindViewHolder(RvAdapter.VHolder holder, int position) {
holder.mTextView.setText(mData.get(position));
holder.mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//item的点击事件
}
});
}
添加HeaderView和FooterView
RecyclerView默认没有提供类似addHeaderView()和addFooterView()的API,因此这里介绍如何优雅地实现这两个接口。
如果你已经实现了一个Adapter,现在想为这个Adapter添加addHeaderView()和addFooterView()接口,则需要在Adapter中添加几个Item Type,然后修改getItemViewType(),onCreateViewHolder(),onBindViewHolder(),getItemCount()等方法,并添加switch语句进行判断。
在不破坏原有Adapter实现的情况下完成添加addHeaderView()和addFooterView()
添加setEmptyView
ListView提供了setEmptyView()设置Adapter数据为空时的View视图。RecyclerView虽然没提供直接的API,但是也可以很简单地实现。
创建一个继承RecyclerView的类,记为EmptyRecyclerView。
通过getRootView().addView(emptyView)将空数据时显示的View添加到当前View的层次结构中。
通过AdapterDataObserver监听RecyclerView的数据变化,如果adapter为空,那么隐藏RecyclerView,显示EmptyView。
拖拽、侧滑删除
Android提供了ItemTouchHelper类,使得RecyclerView能够轻易地实现滑动和拖拽,此处我们要实现上下拖拽和侧滑删除。首先创建一个继承自ItemTouchHelper.Callback的类,并重写以下方法:
- getMovementFlags():
设置支持的拖拽和滑动的方向,此处我们支持的拖拽方向为上下,滑动方向为从左到右和从右到左,内部通过makeMovementFlags()设置。 - onMove(): 拖拽时回调。
- onSwiped(): 滑动时回调。
- onSelectedChanged():
状态变化时回调,一共有三个状态,分别是ACTION_STATE_IDLE(空闲状态),ACTION_STATE_SWIPE(滑动状态),ACTION_STATE_DRAG(拖拽状态)。此方法中可以做一些状态变化时的处理,比如拖拽的时候修改背景色。 - clearView(): 用户交互结束时回调。此方法可以做一些状态的清空,比如拖拽结束后还原背景色。
- isLongPressDragEnabled(): 是否支持长按拖拽,默认为true。如果不想支持长按拖拽,则重写并返回false。
RecyclerView回收机制
RecyclerView和ListView的回收机制非常相似,但是ListView是以View作为单位进行回收,RecyclerView是以ViewHolder作为单位进行回收。Recycler是RecyclerView回收机制的实现类,他实现了四级缓存:
- mAttachedScrap: 缓存在屏幕上的ViewHolder。
- mCachedViews: 缓存屏幕外的ViewHolder,默认为2个。ListView对于屏幕外的缓存都会调用getView()。
- mViewCacheExtensions: 需要用户定制,默认不实现。
- mRecyclerPool: 缓存池,多个RecyclerView共用。
参考:
Android ListView 与 RecyclerView 对比浅析–缓存机制
额外阅读:
瀑布流的使用:
mRecyclerView = (RecyclerView) findViewById(R.id.id_recyclerview);
mStaggeredHomeAdapter = new StaggeredHomeAdapter(this, mDatas);
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(3,
StaggeredGridLayoutManager.VERTICAL));
mRecyclerView.setAdapter(mStaggeredHomeAdapter);
// 设置item动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
瀑布流的adapter:
class StaggeredHomeAdapter extends
RecyclerView.Adapter<StaggeredHomeAdapter.MyViewHolder> {
private List<String> mDatas;
private LayoutInflater mInflater;
private List<Integer> mHeights;
public interface OnItemClickLitener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
private OnItemClickLitener mOnItemClickLitener;
public void setOnItemClickLitener(OnItemClickLitener mOnItemClickLitener) {
this.mOnItemClickLitener = mOnItemClickLitener;
}
public StaggeredHomeAdapter(Context context, List<String> datas) {
mInflater = LayoutInflater.from(context);
mDatas = datas;
mHeights = new ArrayList<Integer>();
for (int i = 0; i < mDatas.size(); i++) {
mHeights.add((int) (100 + Math.random() * 300));
}
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
MyViewHolder holder = new MyViewHolder(mInflater.inflate(
R.layout.item_staggered_home, parent, false));
return holder;
}
@Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {
LayoutParams lp = holder.tv.getLayoutParams();
lp.height = mHeights.get(position);
holder.tv.setLayoutParams(lp);// 设置item的高度
holder.tv.setText(mDatas.get(position));
// 如果设置了回调,则设置点击事件
if (mOnItemClickLitener != null) {
holder.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.getLayoutPosition();// 获取整个recyclerview中的position
mOnItemClickLitener.onItemClick(holder.itemView, pos);
}
});
holder.itemView.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int pos = holder.getLayoutPosition();// 获取整个recyclerview中的position
mOnItemClickLitener.onItemLongClick(holder.itemView, pos);
removeData(pos);
return false;
}
});
}
}
@Override
public int getItemCount() {
return mDatas.size();
}
public void addData(int position) {
mDatas.add(position, "Insert One");// 数据源增加数据
mHeights.add((int) (100 + Math.random() * 300));// 添加一个高度
notifyItemInserted(position);// 添加item
}
public void removeData(int position) {
mDatas.remove(position);// 数据源减少数据
notifyItemRemoved(position);// 删除item
}
class MyViewHolder extends ViewHolder {
TextView tv;
public MyViewHolder(View view) {
super(view);
tv = (TextView) view.findViewById(R.id.id_num);
}
}
}
————————-我是分割线—————————–
多类型的recyclerView的使用
if (moduleBeanList != null) {
//有数据
//设置适配器
homeRecycleAdapter = new HomeRecycleViewAdapter(mContext, moduleBeanList);
rvHome.setAdapter(homeRecycleAdapter);
//recycleView不仅要设置适配器还要设置布局管理者,否则图片不显示
//第一个参数是上下文,第二个参数是只有一列
GridLayoutManager manager = new GridLayoutManager(getActivity(), 1);
rvHome.setLayoutManager(manager);
}
多类型的adapter:
public class HomeRecycleViewAdapter extends RecyclerView.Adapter {
/**
* 4种类型
*/
/**
* 类型1:黑色星期五--使用banner实现
*/
public static final int BLACK_5_BANNER0 = 0;
/**
* 类型2:今日新品--使用GridView实现
*/
public static final int TODAY_NEW_GV1 = 1;
/**
* 类型3:品牌福利--使用ImageView实现
*/
public static final int PIN_PAI_IV2 = 2;
/**
* 类型4:搭配趋势--使用RecyclerView实现
*/
public static final int DAPEIQS_GV3 = 3;
/**
* 当前类型
*/
public int currentType = BLACK_5_BANNER0;
private final Context mContext;
private final List<WomenBean.WomenData.ModuleBean> moduleBeanList;
/**
* 以后用它来初始化布局
*/
private final LayoutInflater mLayoutInflater;
public HomeRecycleViewAdapter(Context mContext, List<WomenBean.WomenData.ModuleBean> moduleBeanList) {
this.mContext = mContext;
this.moduleBeanList = moduleBeanList;
//以后用它来初始化布局
mLayoutInflater = LayoutInflater.from(mContext);
}
/**
* 相当于getView创建ViewHolder布局
*
* @param parent
* @param viewType 当前的类型
* @return
*/
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == BLACK_5_BANNER0) {
return new BBNViewHolder(mContext, mLayoutInflater.inflate(R.layout.banner_viewpager, null));
} else if (viewType == TODAY_NEW_GV1) {
return new TODAYViewHolder(mContext, mLayoutInflater.inflate(R.layout.gv_channel, null));
} else if (viewType == PIN_PAI_IV2) {
return new PINPAIViewHolder(mContext, mLayoutInflater.inflate(R.layout.iv_pinpai, null));
} else if (viewType == DAPEIQS_GV3) {
return new DaPeiViewHolder(mContext, mLayoutInflater.inflate(R.layout.dapeiqs_rv, null));
}
return null;
}
/**
* 相当于getView中的绑定数据模块
*
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (getItemViewType(position) == BLACK_5_BANNER0) {
BBNViewHolder bbnViewHolder = (BBNViewHolder) holder;
List<WomenBean.WomenData.ModuleBean.DataBean> module0data = moduleBeanList.get(0).getData();
bbnViewHolder.setData(module0data);
} else if (getItemViewType(position) == TODAY_NEW_GV1) {
TODAYViewHolder todayViewHolder = (TODAYViewHolder) holder;
List<WomenBean.WomenData.ModuleBean.DataBean> module1data = moduleBeanList.get(1).getData();
todayViewHolder.setData(module1data);
} else if (getItemViewType(position) == PIN_PAI_IV2) {
PINPAIViewHolder pinpaiViewHolder = (PINPAIViewHolder) holder;
List<WomenBean.WomenData.ModuleBean.DataBean> pinpai2data = moduleBeanList.get(2).getData();
pinpaiViewHolder.setData(pinpai2data);
} else if (getItemViewType(position) == DAPEIQS_GV3) {
DaPeiViewHolder dapeiViewHolder = (DaPeiViewHolder) holder;
List<WomenBean.WomenData.ModuleBean.DataBean> dapeiqs6data = moduleBeanList.get(6).getData();
dapeiViewHolder.setData(dapeiqs6data);
}
}
/**
* 总共有多少个item
*
* @return
*/
@Override
public int getItemCount() {
return 4;
}
/**
* 得到类型
*/
@Override
public int getItemViewType(int position) {
switch (position) {
case BLACK_5_BANNER0:
currentType = BLACK_5_BANNER0;
break;
case TODAY_NEW_GV1:
currentType = TODAY_NEW_GV1;
break;
case PIN_PAI_IV2:
currentType = PIN_PAI_IV2;
break;
case DAPEIQS_GV3:
currentType = DAPEIQS_GV3;
break;
}
return currentType;
}
class DaPeiViewHolder extends RecyclerView.ViewHolder {
private final Context mContext;
private RecyclerView dapeiqs_rv;
public DaPeiViewHolder(Context mContext, View itemView) {
super(itemView);
this.mContext = mContext;
dapeiqs_rv = (RecyclerView) itemView.findViewById(R.id.dapeiqs_rv);
}
public void setData(List<WomenBean.WomenData.ModuleBean.DataBean> dapeiqs6data) {
//1.已有数据
//2.设置适配器:-->设置文本和recycleView的数据
DaPeiQSRecycleViewAdapter adapter = new DaPeiQSRecycleViewAdapter(mContext, dapeiqs6data);
//设置秒杀的adapter
dapeiqs_rv.setAdapter(adapter);
//recycleView不仅要设置适配器还要设置布局管理者,否则图片不显示
LinearLayoutManager manager = new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false);
dapeiqs_rv.setLayoutManager(manager);
}
}
class TODAYViewHolder extends RecyclerView.ViewHolder {
private final Context mContext;
private GridView gridView;
public TODAYViewHolder(Context mContext, View itemView) {
super(itemView);
this.mContext = mContext;
gridView = (GridView) itemView.findViewById(R.id.gv_channel);
}
public void setData(List<WomenBean.WomenData.ModuleBean.DataBean> module1data) {
//已得到数据了
//设置适配器
TodayGVAdapter adapter = new TodayGVAdapter(mContext, module1data);
gridView.setAdapter(adapter);
}
}
static class PINPAIViewHolder extends RecyclerView.ViewHolder {
private final Context mContext;
@Bind(R.id.iv_new_chok)
ImageView ivNewChok;
PINPAIViewHolder(Context mContext, View itemView) {
super(itemView);
this.mContext = mContext;
ButterKnife.bind(this, itemView);
ivNewChok = (ImageView) itemView.findViewById(R.id.iv_new_chok);
}
public void setData(List<WomenBean.WomenData.ModuleBean.DataBean> pinpai2data) {
Glide.with(mContext)
.load(pinpai2data.get(0).getImg())
.diskCacheStrategy(DiskCacheStrategy.ALL)
.crossFade() //设置淡入淡出效果,默认300ms,可以传参
.into(ivNewChok);
}
}
public class BBNViewHolder extends RecyclerView.ViewHolder {
private final Context mContext;
private Banner banner;
public BBNViewHolder(Context mContext, View itemView) {
super(itemView);
this.mContext = mContext;
banner = (Banner) itemView.findViewById(R.id.banner);
}
public void setData(List<WomenBean.WomenData.ModuleBean.DataBean> module0data) {
//设置Banner的数据
//得到图片地址的集合
List<String> imageUrls = new ArrayList<>();
for (int i = 0; i < module0data.size(); i++) {
String image = module0data.get(i).getImg();
imageUrls.add(image);
}
// 222222 //新版的banner的使用----偷下懒的使用方法
banner.setImages(imageUrls).setImageLoader(new GlideImageLoader()).start();
//设置item的点击事件
banner.setOnBannerClickListener(new OnBannerClickListener() {
@Override
public void OnBannerClick(int position) {
//注意这里的position是从1开始的
Toast.makeText(mContext, "position==" + position, Toast.LENGTH_SHORT).show();
}
});
}
}
public class GlideImageLoader extends ImageLoader {
@Override
public void displayImage(Context context, Object path, ImageView imageView) {
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
//Glide 加载图片简单用法
Glide.with(context).load(path).into(imageView);
}
}
}