RecyclerView用法
gradle
compile 'com.android.support:cardview-v7:21.0.3'
compile 'com.android.support:recyclerview-v7:21.0.3'
xml
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recycler_view"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"/>
Activity
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));//这里用线性显示 类似于listview
// mRecyclerView.setLayoutManager(new GridLayoutManager(this, 2));//这里用线性宫格显示 类似于grid view
// mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL));//这里用线性宫格显示 类似于瀑布流
mRecyclerView.setAdapter(new NormalRecyclerViewAdapter(this));
adapter
public class NormalRecyclerViewAdapter extends RecyclerView.Adapter<NormalRecyclerViewAdapter.NormalTextViewHolder> {
private final LayoutInflater mLayoutInflater;
private final Context mContext;
private String[] mTitles;
public NormalRecyclerViewAdapter(Context context) {
mTitles = context.getResources().getStringArray(R.array.titles);
mContext = context;
mLayoutInflater = LayoutInflater.from(context);
}
@Override
public NormalTextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new NormalTextViewHolder(mLayoutInflater.inflate(R.layout.item_text, parent, false));
}
@Override
public void onBindViewHolder(NormalTextViewHolder holder, int position) {
holder.mTextView.setText(mTitles[position]);
}
@Override
public int getItemCount() {
return mTitles == null ? 0 : mTitles.length;
}
public static class NormalTextViewHolder extends RecyclerView.ViewHolder {
@InjectView(R.id.text_view)
TextView mTextView;
NormalTextViewHolder(View view) {
super(view);
ButterKnife.inject(this, view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("NormalTextViewHolder", "onClick--> position = " + getPosition());
}
});
}
}
}
多Item布局实现(MultipleItem)
@Override
public int getItemViewType(int position) {
return super.getItemViewType(position);
}
其中getItemViewType方法是用来获取当前项Item(position参数)是哪种类型的布局,这里要注意的就是这个函数onCreateViewHolder(ViewGroup parent, int viewType)这里的第二个参数就是View的类型,可以根据这个类型判断去创建不同item的ViewHolder。
public class MultipleItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public static enum ITEM_TYPE {
ITEM_TYPE_IMAGE,
ITEM_TYPE_TEXT
}
private final LayoutInflater mLayoutInflater;
private final Context mContext;
private String[] mTitles;
public MultipleItemAdapter(Context context) {
mTitles = context.getResources().getStringArray(R.array.titles);
mContext = context;
mLayoutInflater = LayoutInflater.from(context);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal()) {
return new ImageViewHolder(mLayoutInflater.inflate(R.layout.item_image, parent, false));
} else {
return new TextViewHolder(mLayoutInflater.inflate(R.layout.item_text, parent, false));
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof TextViewHolder) {
((TextViewHolder) holder).mTextView.setText(mTitles[position]);
} else if (holder instanceof ImageViewHolder) {
((ImageViewHolder) holder).mTextView.setText(mTitles[position]);
}
}
@Override
public int getItemViewType(int position) {
return position % 2 == 0 ? ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal() : ITEM_TYPE.ITEM_TYPE_TEXT.ordinal();
}
@Override
public int getItemCount() {
return mTitles == null ? 0 : mTitles.length;
}
public static class TextViewHolder extends RecyclerView.ViewHolder {
@InjectView(R.id.text_view)
TextView mTextView;
TextViewHolder(View view) {
super(view);
ButterKnife.inject(this, view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TextViewHolder", "onClick--> position = " + getPosition());
}
});
}
}
public static class ImageViewHolder extends RecyclerView.ViewHolder {
@InjectView(R.id.text_view)
TextView mTextView;
@InjectView(R.id.image_view)
ImageView mImageView;
ImageViewHolder(View view) {
super(view);
ButterKnife.inject(this, view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("ImageViewHolder", "onClick--> position = " + getPosition());
}
});
}
}
}
RecyclerView的三种方法
1、notifyItemInserted(int position)
意思就是在位置position的地方,插入一条。
mList.add(2,"插入"+count);
//我在position=2的位置,插入了数据,结果很 快插入了,不需要更新整个列表
mAdapter.notifyItemInserted(2);
/*从position为2位置开始的itemCount个数的item是新加来的,后面的位置position要相应的更新。
比如我在位置2的地方添加一条数据,原来位置2的地方的的数据现在的position应该是3了,如果不加这句话,则点击3的时候,还是提示position为2。
这个应该是google的一个bug。
*/
mAdapter.notifyItemRangeChanged(2,mList.size()-2);
2、notifyItemRangeChanged(int positionStart, int itemCount)
3、notifyItemRemoved(int position)
把position位置的那条删除
mList.remove(2);
mAdapter.notifyItemRemoved(2);
mAdapter.notifyItemRangeChanged(0,mList.size()-2);
RecyclerView的头部与尾部实现
RecyclerView不像ListView拥有addHeaderView()与addFooterView()的方法简单添加头部尾部即可,而且RecyclerView也没有像ListView的列表点击监听方法(setItemOnclickListener),这里我也不明白为什么官方会取消了这些独有的属性,不过我们依然可以在onBindViewHolder方法中进行事件绑定!
具体头部与尾部实现方法,这里有个诀窍,这里先看一个方法:
public int getItemViewType(int position)
getItemViewType方法是在执行onCreateViewHolder(ViewGroup parent, int viewType)前回调用viewType,目的是为了根据viewType不同创建不同的视图。我们可以通过在onCreateViewHolder创建视图的时候,对viewType进行判断,如果添加了头部,在position = 0的时候回调头部的viewType给onCreateViewHolder,从而创建头部。尾部创建方法于此类同,直接看下代码,适配器的实现:
package cn.wsy.recyclerdemo;
import android.content.Context;
import android.support.v7.widget.GridLayoutManager;
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.ArrayList;
import java.util.List;
/**
* Created by wsy on 2016/8/4.
*/
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyHolder> {
private RecyclerView mRecyclerView;
private List<String> data = new ArrayList<>();
private Context mContext;
private View VIEW_FOOTER;
private View VIEW_HEADER;
//Type
private int TYPE_NORMAL = 1000;
private int TYPE_HEADER = 1001;
private int TYPE_FOOTER = 1002;
public MyAdapter(List<String> data, Context mContext) {
this.data = data;
this.mContext = mContext;
}
@Override
public MyAdapter.MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_FOOTER) {
return new MyHolder(VIEW_FOOTER);
} else if (viewType == TYPE_HEADER) {
return new MyHolder(VIEW_HEADER);
} else {
return new MyHolder(getLayout(R.layout.item_list_layout));
}
}
@Override
public void onBindViewHolder(MyHolder holder, int position) {
if (!isHeaderView(position) && !isFooterView(position)) {
if (haveHeaderView()) position--;
TextView content = (TextView) holder.itemView.findViewById(R.id.item_content);
TextView time = (TextView) holder.itemView.findViewById(R.id.item_time);
content.setText(data.get(position));
time.setText("2016-1-1");
}
}
@Override
public int getItemCount() {
int count = (data == null ? 0 : data.size());
if (VIEW_FOOTER != null) {
count++;
}
if (VIEW_HEADER != null) {
count++;
}
return count;
}
@Override
public int getItemViewType(int position) {
if (isHeaderView(position)) {
return TYPE_HEADER;
} else if (isFooterView(position)) {
return TYPE_FOOTER;
} else {
return TYPE_NORMAL;
}
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
try {
if (mRecyclerView == null && mRecyclerView != recyclerView) {
mRecyclerView = recyclerView;
}
ifGridLayoutManager();
} catch (Exception e) {
e.printStackTrace();
}
}
private View getLayout(int layoutId) {
return LayoutInflater.from(mContext).inflate(layoutId, null);
}
public void addHeaderView(View headerView) {
if (haveHeaderView()) {
throw new IllegalStateException("hearview has already exists!");
} else {
//避免出现宽度自适应
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
headerView.setLayoutParams(params);
VIEW_HEADER = headerView;
ifGridLayoutManager();
notifyItemInserted(0);
}
}
public void addFooterView(View footerView) {
if (haveFooterView()) {
throw new IllegalStateException("footerView has already exists!");
} else {
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
footerView.setLayoutParams(params);
VIEW_FOOTER = footerView;
ifGridLayoutManager();
notifyItemInserted(getItemCount() - 1);
}
}
private void ifGridLayoutManager() {
if (mRecyclerView == null) return;
final RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
final GridLayoutManager.SpanSizeLookup originalSpanSizeLookup =
((GridLayoutManager) layoutManager).getSpanSizeLookup();
((GridLayoutManager) layoutManager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return (isHeaderView(position) || isFooterView(position)) ?
((GridLayoutManager) layoutManager).getSpanCount() :
1;
}
});
}
}
private boolean haveHeaderView() {
return VIEW_HEADER != null;
}
public boolean haveFooterView() {
return VIEW_FOOTER != null;
}
private boolean isHeaderView(int position) {
return haveHeaderView() && position == 0;
}
private boolean isFooterView(int position) {
return haveFooterView() && position == getItemCount() - 1;
}
public static class MyHolder extends RecyclerView.ViewHolder {
public MyHolder(View itemView) {
super(itemView);
}
}
}
private void initRecyc() {
// mRecyclerView.setLayoutManager(new GridLayoutManager(this,2));
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new MyAdapter(data, this);
mRecyclerView.setAdapter(adapter);
adapter.addFooterView(LayoutInflater.from(this).inflate(R.layout.item_footer_layout,null));
adapter.addHeaderView(LayoutInflater.from(this).inflate(R.layout.item_header_layout,null));
}
五、注意的问题
笔者在添加头部尾部的时候,发现在配置RecyclerView,如果模式是配置GridLayoutManager的时候,发现头部会跑到第一格,也就是不是自己想要独立一行的效果,这里贴上关键代码,可以解决(简单数学问题啦哈~):
分割线
RecyclerView 默认是没有分割线的,需要通过下面这个方法添加
public void addItemDecoration(ItemDecoration decor) {
addItemDecoration(decor, -1);
}
ItemDecoration是 RecyclerView 的一个内部抽象类,很明显,这个东西是给我们实现的. 当我们实现 ItemDecoration 的时候,只需要关注 3 个方法
public class ItemDivider extends RecyclerView.ItemDecoration {
// 构造方法,可以在这里做一些初始化,比如指定画笔颜色什么的
public ItemDivider() {
}
/**
* 指定item之间的间距(就是指定分割线的宽度) 回调顺序 1
* @param outRect Rect to receive the output.
* @param view The child view to decorate
* @param parent RecyclerView this ItemDecoration is decorating
* @param state The current state of RecyclerView.
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}
/**
* 在item 绘制之前调用(就是绘制在 item 的底层) 回调顺序 2
* 一般分割线在这里绘制
* 看到canvas,对自定义控件有一定了解的话,就能想到为什么说给RecyclerView设置分割线更灵活了
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
}
/**
* 在item 绘制之后调用(就是绘制在 item 的上层) 回调顺序 3
* 也可以在这里绘制分割线,和上面的方法 二选一
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView
*/
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
}
getItemOffsets 指定item 之间的间距(默认为0),将来就是在这个间距内绘制分割线
getItemOffsets 中为 outRect 设置的4个方向的值,将被计算进所有 decoration 的尺寸中,而这个尺寸,被计入了 RecyclerView 每个 item view 的 padding 中 。
onDraw 在绘制 item之前执行,也就是说,在这里绘制的图形可能会被item遮盖(所以需要指定item之间的间距)
官方实现
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin +
Math.round(ViewCompat.getTranslationY(child));
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
在 onDraw 为 divider 设置绘制范围,并绘制到 canvas 上,而这个绘制范围可以超出在 getItemOffsets 中设置的范围,但由于 decoration 是绘制在 child view 的底下,所以并不可见,但是会存在 overdraw。
onDrawOver 在绘制item之后执行,在这里绘制的图形,可能会遮住item(说以如果要在这里绘制分割线的话,也要找准位置)
使用的GridItemDecoration
public class GridLayoutItemDecoration extends RecyclerView.ItemDecoration {
private int mSpanCount;
private int mHorizonSpan;
private int mVerticalSpan;
private int mOrientation;
public GridLayoutItemDecoration(int count) {
mSpanCount = count;
mOrientation = GridLayoutManager.VERTICAL;
}
public GridLayoutItemDecoration(int count, int orientation) {
mSpanCount = count;
setOrientation(orientation);
}
public GridLayoutItemDecoration(int count, int orientation, int horizonSpan, int verticalSpan) {
this(count);
setDivideParams(horizonSpan, verticalSpan);
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != LinearLayoutManager.HORIZONTAL && orientation != LinearLayoutManager.VERTICAL) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
//获取dapter中的位置
final int position = parent.getChildAdapterPosition(view);
//获取总共的数量
final int totalCount = parent.getAdapter().getItemCount();
//如果是第一列的话left为0,否则为定义的大小
int left = (position % mSpanCount == 0) ? 0 : mHorizonSpan;
//如果不是最后一列的话bottom为mVerticalSpan
int bottom = ((position + 1) % mSpanCount == 0) ? 0 : mVerticalSpan;
if (isVertical(parent)) {
//如果不是最后一行的话
if (!isLastRaw(parent, position, mSpanCount, totalCount)) {
outRect.set(left/2, 0, mHorizonSpan/2, mVerticalSpan);
} else {
outRect.set(left/2, 0, mHorizonSpan/2, 0);
}
} else {
if (!isLastColumn(parent, position, mSpanCount, totalCount)) {
outRect.set(0, 0, mHorizonSpan, bottom);
} else {
outRect.set(0, 0, mHorizonSpan, bottom);
}
}
}
private boolean isVertical(RecyclerView parent) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
int orientation = ((GridLayoutManager) layoutManager)
.getOrientation();
return orientation == StaggeredGridLayoutManager.VERTICAL;
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
int orientation = ((StaggeredGridLayoutManager) layoutManager)
.getOrientation();
return orientation == StaggeredGridLayoutManager.VERTICAL;
}
return false;
}
public void setDivideParams(int horizon, int vertical) {
mHorizonSpan = horizon;
mVerticalSpan = vertical;
}
private boolean isLastRaw(RecyclerView parent, int pos, int spanCount,
int childCount) {
if (isVertical(parent)) {
if ((pos - pos % spanCount) + spanCount >= childCount)
return true;
} else {
if ((pos + 1) % spanCount == 0) {
return true;
}
}
return false;
}
private boolean isLastColumn(RecyclerView parent, int pos, int spanCount,
int childCount) {
if (isVertical(parent)) {
if ((pos + 1) % spanCount == 0) {
return true;
}
} else {
childCount = childCount - childCount % spanCount;
if (pos >= childCount)
return true;
}
return false;
}
}
StickyHeader的Decoration实现
完整代码地址https://github.com/sangx123/sangxiang/tree/master/StickyHeaderDemo-master
public class StickyHeaderDecoraton extends RecyclerView.ItemDecoration {
public static final String TAG = "StickyHeaderDecoraton";
private float mDividerHeight;
private Paint mPaint;
private Paint mTextPaint;
private List<AppInfoBean> mDatas;
public StickyHeaderDecoraton(int height, Context ctx) {
this(height, Color.GRAY, ctx);
}
public StickyHeaderDecoraton(int height, @ColorInt int color, Context ctx) {
this.mDividerHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, ctx.getResources().getDisplayMetrics());
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(color);
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(Color.RED);
mTextPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14, ctx.getResources().getDisplayMetrics()));
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
// 如果是头部,留出顶部空间用作画Header
if (((SectionBean) view.getTag()).isGroupStart) {
outRect.set(0, (int) mDividerHeight, 0, 0);
}
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
}
/**
* 画头部
*
* @param c
* @param parent
* @param view
* @param position
*/
private void drawHeader(Canvas c, RecyclerView parent, View view, int position) {
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
int bottoom = view.getTop() - params.topMargin - Math.round(ViewCompat.getTranslationY(view));
int top = (int) (bottoom - mDividerHeight);
// 计算文字居中时候的基线
Rect targetRect = new Rect(left, top, right, bottoom);
Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
c.drawRect(left, top, right, bottoom, mPaint);
c.drawText(mDatas.get(position).tag, left, baseline, mTextPaint);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
//循环获取第i个iew
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
// 如果是头,画之
if (position != RecyclerView.NO_POSITION
&& ((SectionBean) view.getTag()).isGroupStart) {
drawHeader(c, parent, view, position);
}
}
View view = parent.getChildAt(0);
View view2 = parent.getChildAt(1);
if (view != null && view2 != null) {
SectionBean section1 = (SectionBean) view.getTag();
SectionBean section2 = (SectionBean) view2.getTag();
int position = parent.getChildAdapterPosition(view);
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
int bottoom = (int) mDividerHeight;
int top = 0;
// 判断是否达到临界点
// (第一个可见item是每组的最后一个,第二个可见tiem是下一组的第一个,并且第一个可见item的底部小于header的高度)
// 这里直接判断item的底部位置小于header的高度有点欠妥,应该还要考虑paddingtop以及margintop,这里展示不考虑了
if (section1.isGroupEnd && section2.isGroupStart && view.getBottom() <= mDividerHeight) {
bottoom = view.getBottom();
top = (int) (bottoom - mDividerHeight);
}
// 计算文字居中时候的基线
Rect targetRect = new Rect(left, top, right, bottoom);
Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
// 背景
c.drawRect(left, top, right, bottoom, mPaint);
// 文字
c.drawText(mDatas.get(position).tag, left, baseline, mTextPaint);
}
}
/**
* 头部的名称
*
* @param mDatas
*/
public void setDatas(List<AppInfoBean> mDatas) {
this.mDatas = mDatas;
}
}
git完整版代码为
compile 'com.timehop.stickyheadersrecyclerview:library:0.4.3@aar'
包含点击header的点击事件等等。