本文出自 “阿敏其人” 博客,转载或引用请注明出处。
RecyclerView不属于MD系列,但是却常常一起使用。
一、Reclycler的作用和优点
用来干嘛—— 代替 ListView个GridView。
- 自带ViewHolder
- 分割线控制方便
- 横向,竖向、列表,多行列表和流式皆可
- item增删动画控制方便
二、简单使用
以前没有RecyclerView,我们要使用RecyclerView可以引入下面这个
com.android.support:recyclerview-v7:23.4.0
不过你要是使用md设计,也以这样如下引用就好 (本文采用的就是这样)
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:23.4.0'
support:design 里面应该包含了 support:recyclerview 。
最基本的使用:
布局文件
.
.
item布局
.
.
MainActivity
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private List mDatas;
private TestAdapter mAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
mRecyclerView = (RecyclerView) findViewById(R.id.mRecycler);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(mAdapter = new TestAdapter());
}
protected void initData()
{
mDatas = new ArrayList();
DataBean dataBean = null;
for (int i = 0; i < 20; i++)
{
dataBean = new DataBean();
dataBean.title = "标题 "+i;
dataBean.desc = "描述一下 "+i;
mDatas.add(dataBean);
}
}
class TestAdapter extends RecyclerView.Adapter{
// 孩子数
@Override
public int getItemCount() {
return mDatas.size();
}
// 创建视图
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
MyViewHolder myViewHolder = new MyViewHolder(LayoutInflater.from(MainActivity.this)
.inflate(R.layout.item_recy_test,parent, false));
return myViewHolder;
}
// 绑定视图视图 以前getView的事情 关键方法
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
DataBean dataBean = mDatas.get(position);
holder.mTvTitle.setText(dataBean.title);
holder.mTvDesc.setText(dataBean.desc);
}
// 必须实现的Holder
class MyViewHolder extends RecyclerView.ViewHolder
{
TextView mTvTitle;
TextView mTvDesc;
public MyViewHolder(View itemView) {
super(itemView);
mTvTitle = (TextView) itemView.findViewById(R.id.mTvTitle);
mTvDesc = (TextView) itemView.findViewById(R.id.mTvDesc);
}
}
}
}
.
.
效果图
少年我知道你不是伸手党,你会自己去打一遍,而且不怎么看代码。如果你遇到了下面的问题,那你可以看看
- 问题1、为什么RecyclerView的数据没显示出来?
遥想当年,我们设置ListView,find到ListView之后满脑海想的就是setAdapter,然后在这里我们也是这么做,所以,没显示出来
很有可能是你没设置 LayoutManager,LayoutManager对于RecyclerView是一个很重要的概念。
结果就像现在下图
- 问题2、明明就是item就是match_parent,但是为什么只显示一部分?
还是需要遥想当年,左牵黄,右擎苍,潇潇洒洒地在ListView的getView方法的时候,我们总是传入layout布局,然后就null,写顺手了。
如果写成上图备注的样子,即父亲为null,就会出现下图的状况
三、列表分割线
我们说过,RecyclerView自带设置分割线,(但是却没有分割线的样式可以选,必须自己实现)
先听一下,回头看一下我们之前activity_main的布局文件
我们设置分割线,但是安卓ListView的做法设置分割线没有显示。
现在我们来设置分割线
RecyclerView自带设置分割线的api
mRecyclerView.addItemDecoration((ItemDecoration decor);
我们分割线的样式需要自己实现
下面附上一份分割线样式代码 参考
public class RecycleViewDivider extends RecyclerView.ItemDecoration {
private Paint mPaint;
private Drawable mDivider;
private int mDividerHeight = 2;//分割线高度,默认为1px
private int mOrientation;//列表的方向:LinearLayoutManager.VERTICAL或LinearLayoutManager.HORIZONTAL
private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
/**
* 默认分割线:高度为2px,颜色为灰色
*
* @param context
* @param orientation 列表方向
*/
public RecycleViewDivider(Context context, int orientation) {
if (orientation != LinearLayoutManager.VERTICAL && orientation != LinearLayoutManager.HORIZONTAL) {
throw new IllegalArgumentException("请输入正确的参数!");
}
mOrientation = orientation;
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
}
/**
* 自定义分割线
*
* @param context
* @param orientation 列表方向
* @param drawableId 分割线图片
*/
public RecycleViewDivider(Context context, int orientation, int drawableId) {
this(context, orientation);
mDivider = ContextCompat.getDrawable(context, drawableId);
mDividerHeight = mDivider.getIntrinsicHeight();
}
/**
* 自定义分割线
*
* @param context
* @param orientation 列表方向
* @param dividerHeight 分割线高度
* @param dividerColor 分割线颜色
*/
public RecycleViewDivider(Context context, int orientation, int dividerHeight, int dividerColor) {
this(context, orientation);
mDividerHeight = dividerHeight;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(dividerColor);
mPaint.setStyle(Paint.Style.FILL);
}
//获取分割线尺寸
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.set(0, 0, 0, mDividerHeight);
}
//绘制分割线
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
if (mOrientation == LinearLayoutManager.VERTICAL) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
//绘制横向 item 分割线
private void drawHorizontal(Canvas canvas, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getMeasuredWidth() - parent.getPaddingRight();
final int childSize = parent.getChildCount();
for (int i = 0; i < childSize; i++) {
final View child = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
final int top = child.getBottom() + layoutParams.bottomMargin;
final int bottom = top + mDividerHeight;
if (mDivider != null) {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
if (mPaint != null) {
canvas.drawRect(left, top, right, bottom, mPaint);
}
}
}
//绘制纵向 item 分割线
private void drawVertical(Canvas canvas, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom();
final int childSize = parent.getChildCount();
for (int i = 0; i < childSize; i++) {
final View child = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
final int left = child.getRight() + layoutParams.rightMargin;
final int right = left + mDividerHeight;
if (mDivider != null) {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
if (mPaint != null) {
canvas.drawRect(left, top, right, bottom, mPaint);
}
}
}
}
.
.
设置分割线:
mRecyclerView = (RecyclerView) findViewById(R.id.mRecycler);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.addItemDecoration(new RecycleViewDivider(MainActivity.this, LinearLayoutManager.VERTICAL)); // 设置分割线
mRecyclerView.setAdapter(mAdapter = new TestAdapter());
发现只需要在原来的代码上加上一行代码
mRecyclerView.addItemDecoration(new RecycleViewDivider(MainActivity.this,
效果图:
看得很辛苦,那么我们让分割线加大一些,指定一下颜色吧
mRecyclerView.addItemDecoration(new RecycleViewDivider(MainActivity.this,
LinearLayoutManager.VERTICAL,20, Color.GRAY)); // 设置分割线
四、布局管理器
RecyclerView.LayoutManager,这是一个抽象类,系统提供了3个实现类:
LinearLayoutManager 线性管理器,支持横向、纵向。
GridLayoutManager 网格布局管理器
StaggeredGridLayoutManager 瀑布就式布局管理器
说说LinearLayoutManager
LinearLayoutManager 我们上面用的一直是纵向。
现在来设置成为横向的:
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); // 创建线性布局管理器
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); // 设置线性布局为横向(默认为纵向)
mRecyclerView.setLayoutManager(linearLayoutManager); // 设置布局管理器
说说GridLayoutManager
- 纵向
mRecyclerView = (RecyclerView) findViewById(R.id.mRecycler);
mRecyclerView.setLayoutManager(new C(this,2)); // 设置布局管理器 GridView
mRecyclerView.addItemDecoration(new RecycleViewDivider(MainActivity.this, GridLayoutManager.VERTICAL)); // 设置分割线
mRecyclerView.setAdapter(mAdapter = new TestAdapter());
效果
- 横向
利用 StaggeredGridLayoutManager
只需要一行代码:
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.HORIZONTAL)); // 设置布局管理器 GridView
不过布局有一点点需要改动
activity_main的RecyclerView高度需要包裹,不能填充父窗体
item布局宽度不能填充父窗体
效果:
说说StaggeredGridLayoutManager 流式布局
在上面的横向布局里面,我们已经用到了StaggeredGridLayoutManager
做的东西就几点:
1、item布局需要在外层给出具体高度,这样方便待会计算的时候保证最低高度
2、在RecyclerView.Adapter的继承类的onBindViewHolder里面动态改变高度
接下来看代码:
item布局里面,我们给最外层指定了最低高度100dp,最低宽度180dp
.
.
接下来是adapter,主要看 onBindViewHolder方法
public class TestAdapter extends RecyclerView.Adapter{
private List mDatas;
private Context mContext;
private LayoutInflater mInflater;
private List mHeights;
public TestAdapter(Context mContext, List mDatas) {
mInflater = LayoutInflater.from(mContext);
this.mContext = mContext;
this.mDatas = mDatas;
mHeights = new ArrayList();
for (int i = 0; i < mDatas.size(); i++)
{
// 这里为什么是300? 因为item的高度我们给的是100.如果item不给实际高度,那么lp.height拿出来的很可能是个负数
// 我给item的值100,但是下面lp.height给出来的是300,才写了300
mHeights.add( 300+(int) (Math.random() * 100));
}
}
// 孩子数
@Override
public int getItemCount() {
return mDatas.size();
}
// 创建视图
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 应该调用三个参数的inflate方法,传入父亲(parent)
MyViewHolder myViewHolder = new MyViewHolder(mInflater.inflate(R.layout.item_recy_test,parent, false));
return myViewHolder;
}
// 绑定视图视图 以前getView的事情 关键方法
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
ViewGroup.LayoutParams lp = holder.mLlItemRoot.getLayoutParams();
//
if(position==1){
System.out.println("===========lp.height: "+lp.height);
System.out.println("===========lp.height + 50 : "+(lp.height+50));
}
lp.height = mHeights.get(position);
holder.mLlItemRoot.setLayoutParams(lp);
DataBean dataBean = mDatas.get(position);
holder.mTvTitle.setText(dataBean.title);
holder.mTvDesc.setText(dataBean.desc);
}
// 必须实现的Holder
class MyViewHolder extends RecyclerView.ViewHolder
{
TextView mTvTitle;
TextView mTvDesc;
LinearLayout mLlItemRoot;
public MyViewHolder(View itemView) {
super(itemView);
mLlItemRoot = (LinearLayout) itemView.findViewById(R.id.mLlItemRoot);
mTvTitle = (TextView) itemView.findViewById(R.id.mTvTitle);
mTvDesc = (TextView) itemView.findViewById(R.id.mTvDesc);
}
}
}
还要需要注意的就是这句代码
mHeights.add( 300+(int) (Math.random() * 100));
这里为什么是300? 因为item的高度我们给的是100.如果item不给实际高度,那么lp.height拿出来的很可能是个负数
我给item的值100,但是下面lp.height给出来的是300,才写了300
为什么要把高度写在集合里面而不写在onBindViewHolder?
如果这样循环往复拉动几次会出现什么状况?高度越来越大,每次都上在上次的基础上扩大。
.
.
MainActivity
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private List mDatas;
private TestAdapter mAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
mRecyclerView = (RecyclerView) findViewById(R.id.mRecycler);
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL)); // 设置布局管理器 GridView
mRecyclerView.addItemDecoration(new DividerGridItemDecoration(MainActivity.this)); // 设置分割线
mRecyclerView.setAdapter(mAdapter = new TestAdapter(MainActivity.this,mDatas));
}
protected void initData()
{
mDatas = new ArrayList();
DataBean dataBean = null;
for (int i = 0; i < 20; i++)
{
dataBean = new DataBean();
dataBean.title = "标题 "+i;
dataBean.desc = "描述一下 "+i;
mDatas.add(dataBean);
}
}
}
.
.
在上面的代码这句
mRecyclerView.addItemDecoration(new DividerGridItemDecoration(MainActivity.this)); // 设置分割线
我们还用到了一个分割线文件
附上分割线文件,参考csdn 鸿洋
public class DividerGridItemDecoration extends RecyclerView.ItemDecoration
{
private static final int[] ATTRS = new int[] { android.R.attr.listDivider };
private Drawable mDivider;
public DividerGridItemDecoration(Context context)
{
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
}
@Override
public void onDraw(Canvas c, RecyclerView parent, State state)
{
drawHorizontal(c, parent);
drawVertical(c, parent);
}
private int getSpanCount(RecyclerView parent)
{
// 列数
int spanCount = -1;
LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager)
{
spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
} else if (layoutManager instanceof StaggeredGridLayoutManager)
{
spanCount = ((StaggeredGridLayoutManager) layoutManager)
.getSpanCount();
}
return spanCount;
}
public void drawHorizontal(Canvas c, RecyclerView parent)
{
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 left = child.getLeft() - params.leftMargin;
final int right = child.getRight() + params.rightMargin
+ mDivider.getIntrinsicWidth();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawVertical(Canvas c, RecyclerView parent)
{
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.getTop() - params.topMargin;
final int bottom = child.getBottom() + params.bottomMargin;
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
private boolean isLastColum(RecyclerView parent, int pos, int spanCount,
int childCount)
{
LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager)
{
if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边
{
return true;
}
} else if (layoutManager instanceof StaggeredGridLayoutManager)
{
int orientation = ((StaggeredGridLayoutManager) layoutManager)
.getOrientation();
if (orientation == StaggeredGridLayoutManager.VERTICAL)
{
if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边
{
return true;
}
} else
{
childCount = childCount - childCount % spanCount;
if (pos >= childCount)// 如果是最后一列,则不需要绘制右边
return true;
}
}
return false;
}
private boolean isLastRaw(RecyclerView parent, int pos, int spanCount,
int childCount)
{
LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager)
{
childCount = childCount - childCount % spanCount;
if (pos >= childCount)// 如果是最后一行,则不需要绘制底部
return true;
} else if (layoutManager instanceof StaggeredGridLayoutManager)
{
int orientation = ((StaggeredGridLayoutManager) layoutManager)
.getOrientation();
// StaggeredGridLayoutManager 且纵向滚动
if (orientation == StaggeredGridLayoutManager.VERTICAL)
{
childCount = childCount - childCount % spanCount;
// 如果是最后一行,则不需要绘制底部
if (pos >= childCount)
return true;
} else
// StaggeredGridLayoutManager 且横向滚动
{
// 如果是最后一行,则不需要绘制底部
if ((pos + 1) % spanCount == 0)
{
return true;
}
}
}
return false;
}
@Override
public void getItemOffsets(Rect outRect, int itemPosition,
RecyclerView parent)
{
int spanCount = getSpanCount(parent);
int childCount = parent.getAdapter().getItemCount();
if (isLastRaw(parent, itemPosition, spanCount, childCount))// 如果是最后一行,则不需要绘制底部
{
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
} else if (isLastColum(parent, itemPosition, spanCount, childCount))// 如果是最后一列,则不需要绘制右边
{
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else
{
outRect.set(0, 0, mDivider.getIntrinsicWidth(),
mDivider.getIntrinsicHeight());
}
}
}
效果:
感觉有点丑,后面我们可以用CradView感觉就会好很多。
五、item的增删动画 ItemAnimator
我们之前ListView的时候,通知试图刷新变化调用的是 notifyDataSetChanged()
而在RecyclerView,除了notifyDataSetChanged(),还有
- 添加 notifyItemInserted(position) 指定位置添加一个item
- 删除 notifyItemRemoved(position) 指定删除哪一个item
既然知道怎么添加和删除,那么我们就在adapter里面新增两个对用的方法
public void addData(int position)
{
DataBean tempAdd = new DataBean();
tempAdd.title="新增标题";
tempAdd.desc="新增描述";
mDatas.add(position, tempAdd);
mHeights.add( 300+(int) (Math.random() * 100));
notifyItemInserted(position);
}
public void removeData(int position)
{
mDatas.remove(position);
notifyItemRemoved(position);
}
然后我们弄两个按钮,点击就调用方法
mTvAdd = (TextView) findViewById(R.id.mTvAdd);
mTvAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAdapter.addData(1);
}
});
mTvRemove = (TextView) findViewById(R.id.mTvRemove);
mTvRemove.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAdapter.removeData(1);
}
});
效果图
我们发现这个增删的过程还是有动画的,这是RecyclerView我们实现的默认动画。
mRecyclerView = (RecyclerView) findViewById(R.id.mRecycler);
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL)); // 设置布局管理器 GridView
mRecyclerView.addItemDecoration(new DividerGridItemDecoration(MainActivity.this)); // 设置分割线
// 设置item动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mRecyclerView.setAdapter(mAdapter = new TestAdapter(MainActivity.this,mDatas));
我们也可以利用ItemAnimator()实现动画。
六、自己实现点击事件
RecyclerView没有点击回调的方法方法可以用,我们需要自己实现
// 点击回调
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;
}
// 绑定视图视图 以前getView的事情 关键方法
@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
ViewGroup.LayoutParams lp = holder.mLlItemRoot.getLayoutParams();
// 如果设置了回调,则设置点击事件
if (mOnItemClickLitener != null)
{
holder.itemView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
int pos = holder.getLayoutPosition();
mOnItemClickLitener.onItemClick(holder.itemView, pos);
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener()
{
@Override
public boolean onLongClick(View v)
{
int pos = holder.getLayoutPosition();
mOnItemClickLitener.onItemLongClick(holder.itemView, pos);
return false;
}
});
}
}
效果图
到这里,回顾开头我们说的
- 自带ViewHolder
- 分割线控制方便
- 横向,竖向、列表,多行列表和流式皆可
- item增删动画控制方便
都言及了。
七、CardView 的配合使用
对于CardView,你可以简单的认为它是一个使用了Material Desgin风格的FrameLayout,只不过比普通的FrameLayout多了圆角背景和阴影效果。所以它常用作ListView 或者 RecyclerView等视图Item的布局容器;
我们自然可以联想到它的使用跟FrameLayout非常相似,只不多多了几个用于控制圆角、阴影等自身特有的属性:
design里面没有包含CardView
所以我们还需要引入CardView
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:23.4.0'
compile 'com.android.support:cardview-v7:23.4.0' // 引入 cardview
接下来,把itme的布局文件最外层设置为CardView
其中关键代码无非这两句
card_view:cardBackgroundColor="@color/cardview_dark_background"
card_view:cardCornerRadius="10dp"
card_view:cardElevation="8dp"
第一句指定颜色
第二句指定圆角角度
第三句指定阴影
当然Adapter也有相应的变化
起始就是把原来的 LinearLayout 换成 CardView ,计算高度也是换成 CardView 而已,为了让流式效果明显一些,我们把随机生成的高度弄大了一些。
public class TestAdapter extends RecyclerView.Adapter{
private List mDatas;
private Context mContext;
private LayoutInflater mInflater;
private List mHeights;
public TestAdapter(Context mContext, List mDatas) {
mInflater = LayoutInflater.from(mContext);
this.mContext = mContext;
this.mDatas = mDatas;
mHeights = new ArrayList();
for (int i = 0; i < mDatas.size(); i++)
{
// 这里为什么是300? 因为item的高度我们给的是100.如果item不给实际高度,那么lp.height拿出来的很可能是个负数
// 我给item的值100,但是下面lp.height给出来的是300,才写了300
mHeights.add( 300+(int) (Math.random() * 200));
}
}
// 孩子数
@Override
public int getItemCount() {
return mDatas.size();
}
// 创建视图
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 应该调用三个参数的inflate方法,传入父亲(parent)
MyViewHolder myViewHolder = new MyViewHolder(mInflater.inflate(R.layout.item_recy_test,parent, false));
return myViewHolder;
}
// 点击回调
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;
}
// 绑定视图视图 以前getView的事情 关键方法
@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
// 如果设置了回调,则设置点击事件
if (mOnItemClickLitener != null)
{
holder.itemView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
int pos = holder.getLayoutPosition();
mOnItemClickLitener.onItemClick(holder.itemView, pos);
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener()
{
@Override
public boolean onLongClick(View v)
{
int pos = holder.getLayoutPosition();
mOnItemClickLitener.onItemLongClick(holder.itemView, pos);
return false;
}
});
}
ViewGroup.LayoutParams lp = holder.mCvItemRoot.getLayoutParams();
if(position==1){
System.out.println("===========lp.height: "+lp.height);
System.out.println("===========lp.height + 50 : "+(lp.height+50));
}
lp.height = mHeights.get(position);
holder.mCvItemRoot.setLayoutParams(lp);
DataBean dataBean = mDatas.get(position);
holder.mTvTitle.setText(dataBean.title);
holder.mTvDesc.setText(dataBean.desc);
}
// 必须实现的Holder
class MyViewHolder extends RecyclerView.ViewHolder
{
TextView mTvTitle;
TextView mTvDesc;
LinearLayout mLlItemRoot;
CardView mCvItemRoot;
public MyViewHolder(View itemView) {
super(itemView);
mCvItemRoot = (CardView) itemView.findViewById(R.id.mCvItemRoot);
mLlItemRoot = (LinearLayout) itemView.findViewById(R.id.mLlItemRoot);
mTvTitle = (TextView) itemView.findViewById(R.id.mTvTitle);
mTvDesc = (TextView) itemView.findViewById(R.id.mTvDesc);
}
}
public void addData(int position)
{
DataBean tempAdd = new DataBean();
tempAdd.title="新增标题";
tempAdd.desc="新增描述";
mDatas.add(position, tempAdd);
mHeights.add( 300+(int) (Math.random() * 100));
notifyItemInserted(position);
}
public void removeData(int position)
{
mDatas.remove(position);
notifyItemRemoved(position);
}
}
八、下拉刷新和上拉加载更多
ANDROID官方的SwipeRefreshLayout可用于刷新,但是这个只是做下拉刷新。
而下拉刷新需要判断列表是否抵达底部、
所以需要注意两点:
一是用 SwipeRefreshLayout 包裹 RecyclerView 实现下拉刷新。
下拉刷新是通过实现 SwipeRefreshLayout.OnRefreshListener 接口来实现的,也就是说下拉刷新具有了通用性,不只是 RecyclerView ;
二是滑倒底部的时候自动加载实现加载更多。
加载更多要通过 LayoutManager 来获取 RecyclerView 是否滑动到底部来实现。
下拉刷新
说起来就是几步
1、RecyclerView 外层需要包一个 SwipeRefreshLayout
2、給SwipeRefreshLayout设置颜色
mSwipeRefresh.setColorSchemeResources(
R.color.google_blue,
R.color.google_green,
R.color.google_red,
R.color.google_yellow
);
3、回调接口
mSwipeRefresh.setOnRefreshListener(this); //复写onRefresh方法 做下拉刷新
...
@Override
public void onRefresh() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
DataBean addTemp = new DataBean();
addTemp.title = "标题 下拉新增";
addTemp.desc = "desc 下拉新增";
mDatas.add(0,addTemp);
mAdapter.notifyDataSetChanged();
mSwipeRefresh.setRefreshing(false); //
}
}, 1000);
}
大概步骤如上。
接下来看代码:
简单的下拉刷新的实现
Mainactivity
public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {
private RecyclerView mRecyclerView;
private TextView mTvAdd;
private TextView mTvRemove;
private List mDatas;
private TestAdapter mAdapter;
private SwipeRefreshLayout mSwipeRefresh;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTvAdd = (TextView) findViewById(R.id.mTvAdd);
mTvAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAdapter.addData(1);
}
});
mTvRemove = (TextView) findViewById(R.id.mTvRemove);
mTvRemove.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAdapter.removeData(1);
}
});
mRecyclerView = (RecyclerView) findViewById(R.id.mRecycler);
initData();
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL)); // 设置布局管理器 GridView
mRecyclerView.addItemDecoration(new DividerGridItemDecoration(MainActivity.this)); // 设置分割线
mRecyclerView.setItemAnimator(new DefaultItemAnimator()); // 设置item动画
mRecyclerView.setAdapter(mAdapter = new TestAdapter(MainActivity.this,mDatas));
mAdapter.setOnItemClickLitener(new TestAdapter.OnItemClickLitener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this,"点击:"+position,Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MainActivity.this,"长按:"+position,Toast.LENGTH_SHORT).show();
}
});
// 刷新
mSwipeRefresh = (SwipeRefreshLayout) findViewById(R.id.mSwipeRefresh);
// 刷新的时候的颜色
mSwipeRefresh.setColorSchemeResources(
R.color.google_blue,
R.color.google_green,
R.color.google_red,
R.color.google_yellow
);
// implements SwipeRefreshLayout.OnRefreshListener
mSwipeRefresh.setOnRefreshListener(this); //复写onRefresh方法 做下拉刷新
// 刷新
}
@Override
public void onRefresh() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
DataBean addTemp = new DataBean();
addTemp.title = "标题 下拉新增";
addTemp.desc = "desc 下拉新增";
mDatas.add(0,addTemp);
mAdapter.notifyDataSetChanged();
mSwipeRefresh.setRefreshing(false); //
}
}, 1000);
}
protected void initData()
{
mDatas = new ArrayList();
DataBean dataBean = null;
for (int i = 0; i < 20; i++)
{
dataBean = new DataBean();
dataBean.title = "标题 "+i;
dataBean.desc = "描述一下 "+i;
mDatas.add(dataBean);
}
}
}
.
.
效果图
上拉加载更多数据
上拉加载更多用一个新的页面展示,更加清晰。
主要就是完成从下面这几步:
- 1、Adapter 定义两个常量区分普通item视图和脚部
private static final int TYPE_ITEM = 0;
private static final int TYPE_FOOTER = 1;
- 2、Adapter getItemCount()方法为脚部做调整
@Override
public int getItemCount() {
return mDatas.size() == 0 ? 0 : mDatas.size() + 1;
}
- 3、C利用getItemViewType做视图判断
@Override
public int getItemViewType(int position) {
if (position + 1 == getItemCount()) {
return TYPE_FOOTER;
} else {
return TYPE_ITEM;
}
}
- 4、Adapter onCreateViewHolder 初始化视图
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_ITEM) {
View view = LayoutInflater.from(context).inflate(R.layout.item_base, parent,
false);
return new ItemViewHolder(view);
} else if (viewType == TYPE_FOOTER) {
View view = LayoutInflater.from(context).inflate(R.layout.item_foot, parent,
false);
return new FootViewHolder(view);
}
return null;
}
- 5、Adapter onBindViewHolder 帮顶视图做区分
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
if (holder instanceof ItemViewHolder) {
if (onItemClickListener != null) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getLayoutPosition();
onItemClickListener.onItemClick(holder.itemView, position);
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int position = holder.getLayoutPosition();
onItemClickListener.onItemLongClick(holder.itemView, position);
return false;
}
});
}
ItemViewHolder itemViewHolder = (ItemViewHolder)holder;
itemViewHolder.mTvName.setText(mDatas.get(position));
}
}
- 6、在Activity里面的scroll监听做上拉要执行的逻辑
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
Log.d("test", "StateChanged = " + newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
Log.d("test", "onScrolled");
int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
if (lastVisibleItemPosition + 1 == adapter.getItemCount()) {
Log.d("test", "loading executed");
boolean isRefreshing = swipeRefreshLayout.isRefreshing();
if (isRefreshing) {
adapter.notifyItemRemoved(adapter.getItemCount());
return;
}
if (!isLoading) {
isLoading = true;
handler.postDelayed(new Runnable() {
@Override
public void run() {
mDatas.add("上拉 加载 很多数据");
adapter.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
adapter.notifyItemRemoved(adapter.getItemCount());
Log.d("test", "load more completed");
isLoading = false;
}
}, 1000);
}
}
}
});
大概就是这么些步骤,,下面附上代码。
public class RefreshActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private SwipeRefreshLayout swipeRefreshLayout;
boolean isLoading;
private List mDatas = new ArrayList<>();
private RecyclerViewAdapter adapter = new RecyclerViewAdapter(this, mDatas);
private Handler handler = new Handler();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_refresh);
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
initView();
initData();
}
public void initView() {
swipeRefreshLayout.setColorSchemeResources(
R.color.google_blue,
R.color.google_green,
R.color.google_red,
R.color.google_yellow
);
swipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
swipeRefreshLayout.setRefreshing(true);
}
});
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() { // 下拉加载新数据
handler.postDelayed(new Runnable() {
@Override
public void run() {
mDatas.add(0,"下拉刷新出来的新数据");
adapter.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
adapter.notifyItemRemoved(adapter.getItemCount());
}
}, 2000);
}
});
final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
Log.d("test", "StateChanged = " + newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
Log.d("test", "onScrolled");
int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
if (lastVisibleItemPosition + 1 == adapter.getItemCount()) {
Log.d("test", "loading executed");
boolean isRefreshing = swipeRefreshLayout.isRefreshing();
if (isRefreshing) {
adapter.notifyItemRemoved(adapter.getItemCount());
return;
}
if (!isLoading) {
isLoading = true;
handler.postDelayed(new Runnable() {
@Override
public void run() {
mDatas.add("上拉 加载 很多数据");
adapter.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
adapter.notifyItemRemoved(adapter.getItemCount());
Log.d("test", "load more completed");
isLoading = false;
}
}, 1000);
}
}
}
});
//添加点击事件
adapter.setOnItemClickListener(new RecyclerViewAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Log.d("test", "item position = " + position);
}
@Override
public void onItemLongClick(View view, int position) {
}
});
}
public void initData() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
getData();
}
}, 1500);
}
/**
* 获取测试数据
*/
private void getData() {
for (int i = 0; i < 20; i++) {
mDatas.add("数据 "+i);
}
adapter.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
adapter.notifyItemRemoved(adapter.getItemCount());
}
}
.
.
页面布局
.
.
item布局
.
.
Adapter
public class RecyclerViewAdapter extends Adapter {
private static final int TYPE_ITEM = 0;
private static final int TYPE_FOOTER = 1;
private Context context;
private List mDatas;
public RecyclerViewAdapter(Context context, List mDatas) {
this.context = context;
this.mDatas = mDatas;
}
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
private OnItemClickListener onItemClickListener;
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
@Override
public int getItemCount() {
return mDatas.size() == 0 ? 0 : mDatas.size() + 1;
}
@Override
public int getItemViewType(int position) {
if (position + 1 == getItemCount()) {
return TYPE_FOOTER;
} else {
return TYPE_ITEM;
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_ITEM) {
View view = LayoutInflater.from(context).inflate(R.layout.item_base, parent,
false);
return new ItemViewHolder(view);
} else if (viewType == TYPE_FOOTER) {
View view = LayoutInflater.from(context).inflate(R.layout.item_foot, parent,
false);
return new FootViewHolder(view);
}
return null;
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
if (holder instanceof ItemViewHolder) {
if (onItemClickListener != null) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getLayoutPosition();
onItemClickListener.onItemClick(holder.itemView, position);
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int position = holder.getLayoutPosition();
onItemClickListener.onItemLongClick(holder.itemView, position);
return false;
}
});
}
ItemViewHolder itemViewHolder = (ItemViewHolder)holder;
itemViewHolder.mTvName.setText(mDatas.get(position));
}
}
static class ItemViewHolder extends ViewHolder {
TextView mTvName;
public ItemViewHolder(View view) {
super(view);
mTvName = (TextView) view.findViewById(R.id.mTvName);
}
}
static class FootViewHolder extends ViewHolder {
public FootViewHolder(View view) {
super(view);
}
}
九、其他
1、反序
比如服务器给我们返回一组日期从早到晚的数组,我们也用的很开心,但是突然说要降序,我们不用自己写比较器,只需要利用下面代码反序展示就好
mLinearLayoutManager = new LinearLayoutManager(this);
// 两者一起使用,才能使得反转后从上方开始展示
mLinearLayoutManager.setReverseLayout(true);//列表翻转
mLinearLayoutManager.setStackFromEnd(true);//列表再底部开始展示,反转后由上面开始展示
rvGroupList.setLayoutManager(mLinearLayoutManager);
2、跳转到指定位置
在Adapter里面添加如下方法,需要地方调用即可
public static void moveToPosition(LinearLayoutManager manager, int position) {
manager.scrollToPositionWithOffset(position, 0);
manager.setStackFromEnd(true);
}
调用示例
mHideMsgLogAdapter.moveToPosition(mLinearLayoutManager,messList.size()-1);
3、RecyclerView自身无效的解决办法
有时候,我们的RecyclerView只是展示数据,不需要item点击。
但是整个RecyclerView有需要实现点击事件执行一定的操作。
我们发现,直接对RecyclerView直接setOnClickListener是无效的。
既然默认无效,那就只能从Touch机制入手了。
比如
mRvRecent.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
// 即可点击整个RecyclerView
}
return false;
}
});
4、RecyclerView中的Edittext监听
当我们对RecyclerView中的Edittext进行内容监听的时候。
很可能会出现position乱掉的问题。
那么解决办法就是给Edittext设置Tag。
if (holder.mEtContent.getTag() instanceof TextWatcher) {
holder.mEtContent.removeTextChangedListener((TextWatcher) holder.mEtContent.getTag());
}
holder.mEtContent.setText(foosBean.content);
TextWatcher textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
String cont = editable.toString().trim();
mList.get(position).content = cont;
// 如果倍数输入框有值,那么就把置为选中状态
if (!TextUtils.isEmpty(cont) && !cont.equals("0")) {
foosBean.isSelect = true;
} else {
foosBean.isSelect = false;
}
}
};
holder.mEtContent.addTextChangedListener(textWatcher);
holder.mEtContent.setTag(textWatcher);
.
.
就到这里吧。
如有兴趣,可另外看下他篇:
MD系列2、ToolBar+DrawerLayout + NavigationView
Md系列3、CoordinatorLayout 里 Toobar和TabLayout等发生的一系列故事
demo
本篇完。
参考:
RecyclerView下拉刷新上拉加载
Android RecyclerView 使用完全解析 体验艺术般的控件
使用SwipeRefreshLayout和RecyclerView实现仿“”下拉刷新和上拉加载更多