简介
A flexible view for providing a limited window into a large data set.
一个展示大量数据、灵活的View控件。
个人理解为ListView、GridView的官方升级版本,将ViewHolder、复用itemView等ListView优化方案吸收,更加灵活、解耦的展示大量数据。
2017.11.20 增加推荐点击事件处理、HeadView+FootView的封装.
基本使用流程
1.导包
使用RecycleView需要引入v7包对应。
// 版本号跟v7包版本号一致就行
compile 'com.android.support:recyclerview-v7:xx.x.x'
2.设置布局管理器
RecycleView.setLayoutManager(LayoutManager);
自带的三个布局管理器:
StaggeredGridLayoutManager : 流式布局管理器
A LayoutManager that lays out children in a staggered grid formation.
It supports horizontal & vertical layout as well as an ability to layout children in reverse..
以交错网格的形式布局,支持水平和垂直布局,同时支持反向。
LinearLayoutManager:线性布局管理器
similar functionality to {@link android.widget.ListView}
官方解释:功能和ListView类似。。。
GridLayoutManager:表格布局管理器
By default, each item occupies 1 span. You can change it by providing a custom
{@link SpanSizeLookup} instance via {@link #setSpanSizeLookup(SpanSizeLookup)}.
默认每个Item占据一个跨度(span:表格的列数既定之后,每个item的宽度),但是你可以setSpanSizeLookup()方法修改每个item的跨度。
功能类似于GridView,继承了LinearLayoutManager,但是官方的解释上面是重点,通过上面的功能能够实现更加灵活的表格布局,将往常只能嵌套使用的布局在不嵌套的情况展示。
3.设置Adapter
RecycleView.setAdapter();
adapter必须重写方法如下:
new RecyclerView.Adapter() {
/**
* 根据viewType返回想要的ViewHolder,强行绑定ListView中ViewHolder模式。
*
* 每个Item都会调用仅且调用一次,即每个Item的ViewHolder对象都不被其他Item(即使类型相同)复用。
* 这里与ListView使用ViewHolder有所区别。
*
* 当Item需要被复用,调用方法{@link #onBindViewHolder(RecyclerView.ViewHolder, int)}
*/
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item1, parent, false);
RecyclerViewAdapter1.ViewHolder viewHolder = new RecyclerViewAdapter1.ViewHolder(view);
return viewHolder;
}
/**
* 根据加载位置进行数据展示
* 当Item被展示到屏幕上时调用,进行Item展示、更新
*/
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
}
/**
* 返回总的Item数量
*/
@Override
public int getItemCount() {
return 0;
}
});
4. 添加分割线
RecycleView.addItemDecoration(RecyclerView.ItemDecoration);
以前添加分割线的方法不再有效。
android:divider="#ff0000"
android:dividerHeight="1dp"
自定义分割线
new RecyclerView.ItemDecoration() {
/**
* 调用在绘制Item之前
* 一般情况两个Draw方法选一个重写即可
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
}
/**
* 调用在绘制Item之后
*/
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
}
/**
* 设置偏移量
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
// 对Item进行偏移
outRect.set(0,0,0,8);
}
});
5. 设置动画
RecyclerView.setItemAnimator(new DefaultItemAnimator());
这里使用默认的系统动画。
同时调用如下方法进行Item的add和remove:
public void addItem(int position, String s){
arrayList.add(position,s);
notifyItemInserted(position);
}
public void remove(int position){
arrayList.remove(position);
notifyItemRemoved(position);
}
6.点击事件
方式一:由于RecycleView没有直接给出点击事件的接口,可以将事件监听接口放到Adapter中,在布局容器进行实现,容器中调用就好了,这里不做实现。
方式二(推荐使用):RxJava PublishSubject实现监听事件的传递。
没有使用RxJava不能使用!!!
adapter实现:
private final PublishSubject<Model> onClickSubject = PublishSubject.create();
/**
* 点击事件 进行订阅
*
* @author fengzhen
* @version v6.3.0, 2017/11/18 17:43
*/
public PublishSubject<Model> getOnClickSubject() {
return onClickSubject;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Model model = mListData.get(position);
ViewHolder viewHolder = (ViewHolder) holder;
viewHolder.mTvContent.setOnClickListener(v -> onClickSubject.onNext(model));
}
外部订阅:
mXXXAdapter.getOnClickSubject().subscribe(new DefaultSubscriber<Model>() {
@Override
public void onNext(Modeldata) {
// 点击事件处理
showShortToast(data.getString());
}
});
好,就酱!
7.HeadView + FootView
从ListView到RecyclerView怎么能把很常用的HeadView、FootView给忘了。
这里直接贴封装的一个简单Adapter,使用直接继承My Adapter 就可以直接用了。
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
/**
* 扩展RecyclerView.Adapter
* 添加HeadView、FootView
*
* @author fengzhen
* @version v6.3.0, 2017/11/20
*/
public abstract class RecyclerViewExtendAdapter extends RecyclerView.Adapter {
public static final int ITEM_TYPE_HEADVIEW = 999;
public static final int ITEM_TYPE_FOOTVIEW = 998;
private View mHeadview;
private View mFootview;
private boolean hasHeadView = false;
public void setHeadview(View headview) {
hasHeadView = headview != null;
this.mHeadview = headview;
notifyDataSetChanged();
}
public void setFootview(View footview) {
this.mFootview = footview;
notifyDataSetChanged();
}
public View getmHeadview() {
return mHeadview;
}
public View getmFootview() {
return mFootview;
}
/**
* 不可重写,
* 重写方法{@link RecyclerViewExtendAdapter#getNormalItemViewType}实现具体子类操作
*
* @author fengzhen
* @version v6.3.0, 2017/11/20 15:57
*/
@Override
public final int getItemViewType(int position) {
if (mHeadview != null && position < 1) {
return ITEM_TYPE_HEADVIEW;
} else if (mFootview != null && position == getItemCount() - 1) {
return ITEM_TYPE_FOOTVIEW;
} else {
return getNormalItemViewType(hasHeadView ? position - 1 : position);
}
}
/**
* 子类实现此方法,等同于直接实现{@link RecyclerView.Adapter#getItemViewType(int)} ()}
*
* @return The view type of this ViewHolder.
* @author fengzhen
* @version v6.3.0, 2017/11/20 15:52
*/
protected int getNormalItemViewType(int position) {
return super.getItemViewType(position);
}
/**
* 不可重写
* 子类实现{@link RecyclerViewExtendAdapter#getNormalItemCount()}
*
* @author fengzhen
* @version v6.3.0, 2017/11/20 16:10
*/
@Override
public final int getItemCount() {
return getNormalItemCount() + (mHeadview == null ? 0 : 1) + (mFootview == null ? 0 : 1);
}
/**
* 子类实现此方法,等同于实现{@link RecyclerView.Adapter#getItemCount()}
*
* @author fengzhen
* @version v6.3.0, 2017/11/20 16:06
*/
protected abstract int getNormalItemCount();
/**
* 不可重写
* 子类实现{@link RecyclerViewExtendAdapter#onNormalCreateViewHolder(ViewGroup, int)}
*
* @author fengzhen
* @version v6.3.0, 2017/11/20 16:10
*/
@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case ITEM_TYPE_HEADVIEW:
return new HeadViewHolder(mHeadview);
case ITEM_TYPE_FOOTVIEW:
return new FootViewHolder(mFootview);
default:
return onNormalCreateViewHolder(parent, viewType);
}
}
/**
* 子类实现此方法,等同于实现{@link RecyclerView.Adapter#onCreateViewHolder(ViewGroup, int)}
*
* @author fengzhen
* @version v6.3.0, 2017/11/20 16:06
*/
protected abstract RecyclerView.ViewHolder onNormalCreateViewHolder(ViewGroup parent, int viewType);
/**
* 不可重写
* 子类实现{@link RecyclerViewExtendAdapter#onNormalBindViewHolder(RecyclerView.ViewHolder, int, int)}
*
* @author fengzhen
* @version v6.3.0, 2017/11/20 16:10
*/
@Override
public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int itemViewType = getItemViewType(position);
switch (itemViewType) {
case ITEM_TYPE_HEADVIEW:
case ITEM_TYPE_FOOTVIEW:
break;
default:
onNormalBindViewHolder(holder, hasHeadView ? position - 1 : position, itemViewType);
break;
}
}
/**
* 子类实现此方法,等同于实现{@link RecyclerView.Adapter#onBindViewHolder(RecyclerView.ViewHolder, int)}
*
* @author fengzhen
* @version v6.3.0, 2017/11/20 16:06
*/
protected abstract void onNormalBindViewHolder(RecyclerView.ViewHolder holder, int position, int itemViewType);
private class HeadViewHolder extends RecyclerView.ViewHolder {
public HeadViewHolder(View itemView) {
super(itemView);
}
}
private class FootViewHolder extends RecyclerView.ViewHolder {
public FootViewHolder(View view) {
super(view);
}
}
}
7.去掉滑动收尾的下拉动画
设置属性:
android:overScrollMode="never"
复杂布局层数简化
1. 布局要求
实现如下的布局:
2. 布局解析
实现方式一: 直接硬布局,最外层用一个ScrollView,里面用 TextView + ListView + TextView + GridView。还需要对ListView和GridView进行重写测量方法,才能实现效果。
实现方式二: 整个作为一个ListView,Title、ListView 、GridView都看成是父ListView 的一个Item。但是对于其中的交互逻辑的代码不要控制,写出来肯定很不“优美”。
实现方式三: 使用RecycleView的GridLayoutManager布局,通过item type 设置,将Title、ListVite item和GridView item都作为RecycleView 的item,使用一层布局,这也是推荐的做法。
3. 布局实现
实现布局的重点在于GridLayoutManager的setSpanSizeLookup()方法。
设置布局管理器:
// 设置布局管理器
private void initLayout() {
// 这里的4,取所需列数的最小公倍数
mRecyclerView1.setLayoutManager(new GridLayoutManager(this, 4));
}
Adapter实现:
/**
* Created by fengzhen.
*
* recyclerView 适配器
*/
public class RecyclerViewAdapter1 extends RecyclerView.Adapter<RecyclerViewAdapter1.ViewHolder> {
// 布局类型
public static final int TITLE_1 = 0x0;
public static final int TITLE_2 = 0x1;
public static final int LINEAR_1 = 0x2;
public static final int GRID_2 = 0x3;
// 数据集
private List<String> arrayList;
public RecyclerViewAdapter1(List<String> list) {
arrayList = list;
}
/**
* 查找item布局,强制绑定ViewHolder
*/
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 没做类型判定 需要时判断 viewType
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item1, parent, false);
return new ViewHolder(view);
}
/**
* 根据加载位置进行数据展示
*/
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
// 没做类型判定 需要时判断 viewType
holder.textView1.setText(arrayList.get(position));
}
/**
* 实现布局的重点
*/
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
/**
* 处理GridLayoutManager布局
* LayoutManager列数设置为 所有可能出现的列数的最小公倍数
* 具体的列数,返回在总列数的比例
* 例:可能出现的列数有: 1、3、4、7
* LayoutManager的列数设置为 : 1*3*4*7 = 84;
* 对应列数的返回值: 84、 28、21、12;
*/
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if(manager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int type = getItemViewType(position);
switch (type){
case TITLE_1:
case TITLE_2:
return 4;
case LINEAR_1:
return 1;
case GRID_2:
return 2;
default:
return 4;
}
}
});
}
}
/**
* 返回不同的类型
*
* @author fengzhen
* @version v1.0, 2017/8/23 15:47
*/
@Override
public int getItemViewType(int position) {
if (position == 0) {
return TITLE_1;
} else if (position < 8) {
return LINEAR_1;
} else if (position == 8) {
return TITLE_2;
} else {
return GRID_2;
}
}
/**
* 类似listview中的方法,获取总item数
*/
@Override
public int getItemCount() {
return arrayList.size();
}
/**
* 继承RecycleView的ViewHolder,对item控件进行初始化
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView textView1;
public ViewHolder(View itemView) {
super(itemView);
textView1 = (TextView) itemView.findViewById(R.id.textView1);
}
}
}