RecycleView使用详解
来源:互联网 发布:sql语句查询多张表 编辑:程序博客网 时间:2024/05/29 12:50
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 。
最基本的使用:
布局文件
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v7.widget.RecyclerView android:id="@+id/mRecycler" android:layout_width="match_parent" android:layout_height="match_parent" android:divider="#ffff0000" android:dividerHeight="10dp" > </android.support.v7.widget.RecyclerView></RelativeLayout>
.
.
item布局
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:background="#66ff0000" android:padding="5dp" > <TextView android:id="@+id/mTvTitle" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="标题" android:textSize="20sp" android:padding="10dp" android:textColor="#ff0000" android:background="#4f92c9" /> <TextView android:id="@+id/mTvDesc" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="描述" android:textSize="16sp" android:padding="10dp" android:textColor="#3657d7" android:background="#a9a667" /></LinearLayout>
.
.
MainActivity
public class MainActivity extends AppCompatActivity { private RecyclerView mRecyclerView; private List<DataBean> 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 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<TestAdapter.MyViewHolder>{ // 孩子数 @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高度需要包裹,不能填充父窗体
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v7.widget.RecyclerView android:id="@+id/mRecycler" android:layout_width="match_parent" android:layout_height="wrap_content" android:divider="#ffff0000" android:dividerHeight="10dp"></android.support.v7.widget.RecyclerView></RelativeLayout>
item布局宽度不能填充父窗体
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="180dp" android:layout_height="100dp" android:orientation="vertical" android:background="#66ff0000" android:padding="5dp" > <TextView android:id="@+id/mTvTitle" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="标题" android:textSize="20sp" android:padding="10dp" android:textColor="#ff0000" android:background="#4f92c9" /> <TextView android:id="@+id/mTvDesc" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="描述" android:textSize="16sp" android:padding="10dp" android:textColor="#3657d7" android:background="#a9a667" /></LinearLayout>
效果:
说说StaggeredGridLayoutManager 流式布局
在上面的横向布局里面,我们已经用到了StaggeredGridLayoutManager
做的东西就几点:
1、item布局需要在外层给出具体高度,这样方便待会计算的时候保证最低高度
2、在RecyclerView.Adapter的继承类的onBindViewHolder里面动态改变高度
接下来看代码:
item布局里面,我们给最外层指定了最低高度100dp,最低宽度180dp
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/mLlItemRoot" android:layout_width="180dp" android:layout_height="100dp" android:orientation="vertical" android:background="#66ff0000" android:padding="5dp" android:gravity="center" android:layout_margin="2dp" > <TextView android:id="@+id/mTvTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="标题" android:layout_gravity="center_horizontal" android:textSize="20sp" android:padding="10dp" android:textColor="#ff0000" android:background="#4f92c9" /> <TextView android:id="@+id/mTvDesc" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="描述" android:textSize="16sp" android:gravity="center_horizontal" android:padding="10dp" android:textColor="#3657d7" android:background="#a9a667" /></LinearLayout>
.
.
接下来是adapter,主要看 onBindViewHolder方法
public class TestAdapter extends RecyclerView.Adapter<TestAdapter.MyViewHolder>{ private List<DataBean> mDatas; private Context mContext; private LayoutInflater mInflater; private List<Integer> mHeights; public TestAdapter(Context mContext, List<DataBean> mDatas) { mInflater = LayoutInflater.from(mContext); this.mContext = mContext; this.mDatas = mDatas; mHeights = new ArrayList<Integer>(); 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<DataBean> 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 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
<?xml version="1.0" encoding="utf-8"?><android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/mCvItemRoot" android:layout_width="180dp" android:layout_height="100dp" xmlns:card_view="http://schemas.android.com/apk/res-auto" card_view:cardBackgroundColor="@color/cardview_dark_background" card_view:cardCornerRadius="10dp" card_view:cardElevation="8dp" android:layout_margin="3dp" > <LinearLayout android:id="@+id/mLlItemRoot" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="5dp" android:gravity="center" android:layout_margin="2dp"> <TextView android:id="@+id/mTvTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="标题" android:layout_gravity="center_horizontal" android:textSize="20sp" android:padding="10dp" android:textColor="#ff0000" android:background="#4f92c9" /> <TextView android:id="@+id/mTvDesc" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:text="描述" android:textSize="16sp" android:gravity="center_horizontal" android:padding="10dp" android:textColor="#3657d7" android:background="#a9a667" /> </LinearLayout></android.support.v7.widget.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<TestAdapter.MyViewHolder>{ private List<DataBean> mDatas; private Context mContext; private LayoutInflater mInflater; private List<Integer> mHeights; public TestAdapter(Context mContext, List<DataBean> mDatas) { mInflater = LayoutInflater.from(mContext); this.mContext = mContext; this.mDatas = mDatas; mHeights = new ArrayList<Integer>(); 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); }
大概步骤如上。
接下来看代码:
简单的下拉刷新的实现
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout ... </LinearLayout> <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/mSwipeRefresh" android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.RecyclerView android:id="@+id/mRecycler" android:layout_width="wrap_content" android:layout_height="wrap_content" android:divider="#ffff0000" android:dividerHeight="10dp"> </android.support.v7.widget.RecyclerView> </android.support.v4.widget.SwipeRefreshLayout></LinearLayout>
Mainactivity
public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener { private RecyclerView mRecyclerView; private TextView mTvAdd; private TextView mTvRemove; private List<DataBean> 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 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<String> 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()); }}
.
.
页面布局
<?xml version="1.0" encoding="utf-8"?><android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/swipeRefreshLayout" android:layout_width="match_parent" android:layout_height="match_parent" > <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="vertical"></android.support.v7.widget.RecyclerView></android.support.v4.widget.SwipeRefreshLayout>
.
.
item布局
<?xml version="1.0" encoding="utf-8"?><android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginTop="6dp" android:orientation="vertical" app:cardBackgroundColor="@color/colorPrimary" app:cardPreventCornerOverlap="true" app:cardUseCompatPadding="true" app:contentPadding="6dp"> <TextView android:id="@+id/mTvName" android:layout_width="match_parent" android:layout_height="36dp" android:gravity="center" android:textColor="#ffffff" android:text="你好色彩" /></android.support.v7.widget.CardView>
.
.
Adapter
public class RecyclerViewAdapter extends Adapter<ViewHolder> { private static final int TYPE_ITEM = 0; private static final int TYPE_FOOTER = 1; private Context context; private List<String> mDatas; public RecyclerViewAdapter(Context context, List<String> 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);
作者:阿敏其人
链接:http://www.jianshu.com/p/b4d1bfd55ae9
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- Android RecycleView使用详解
- RecycleView使用详解
- Android中RecycleView的使用详解
- recycleview详解
- RecycleView详解
- RecycleView使用
- RecycleView使用
- recycleview使用
- material design(二)RecycleView的使用详解
- Android RecycleView---- RecycleView的简单使用
- Recycleview的使用
- 浅谈RecycleView使用
- RecycleView的使用
- RecycleView使用体验(一)
- RecycleView使用体验(二)
- RecycleView使用小节
- RecycleView 的使用
- Android RecycleView的使用
- PopupWindow的使用原理以及实现卡片效果
- 线性顺序表
- fiddler 设置断点并修改请求
- ServletJSP:下
- nginx处理post请求(http响应头部的收发)
- RecycleView使用详解
- Struts2注意点
- Linux已经26岁了,和我同龄
- 关于 cin 和 cou,以及什么是最快的输入输出方法
- mybatis报错
- 手动测试驱动编程
- 让孩子过早懂事,是种残忍的教养
- CSS中属性有多个值的理解
- 7-1 数组循环左移