一、简介
1.RecyclerView是什么?
RecylerView是support-v7包中的新组件,是一个强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,这一点从它的名字recylerview即回收view也可以看出。看到这也许有人会问,不是已经有ListView了吗,为什么还要RecylerView呢?这就牵扯到第二个问题了。
2.RecyclerView的优点是什么?
根据官方的介绍RecylerView是ListView的升级版,既然如此那RecylerView必然有它的优点,现就RecylerView相对于ListView的优点罗列如下:
①RecylerView封装了viewholder的回收复用,也就是说RecylerView标准化了ViewHolder,编写Adapter面向的是ViewHolder而不再是View了,复用的 逻辑被封装了,写起来更加简单。
②提供了一种插拔式的体验,高度的解耦,异常的灵活,针对一个Item的显示RecylerView专门抽取出了相应的类,来控制Item的显示,使其的扩展性非常强。例如:你想控制横向或者纵向滑动列表效果可以通过LinearLayoutManager这个类来进行控制(与GridView效果对应的是GridLayoutManager,与瀑布流对应的还有StaggeredGridLayoutManager等),也就是说RecylerView不再拘泥于ListView的线性展示方式,它也可以实现GridView的效果等多种效果。你想控制Item的分隔线,可以通过继承RecylerView的ItemDecoration这个类,然后针对自己的业务需求去抒写代码。
③可以控制Item增删的动画,可以通过ItemAnimator这个类进行控制,当然针对增删的动画,RecylerView有其自己默认的实现。
二、使用
1、导入jar包
android-support-v7-recyclerview.jar
2、布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<android.support.v7.widget.RecyclerView
android:id="@+id/id_recyclerview"
android:divider="#ffff0000"
android:dividerHeight="10dp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
3、代码
3.1Activity
- package com.example.reclerviewpractice;
- import java.util.ArrayList;
- import java.util.List;
- import com.example.reclerviewpractice.adapter.MyRecyclerAdapter;
- import android.annotation.SuppressLint;
- import android.os.Bundle;
- import android.support.v7.app.ActionBarActivity;
- import android.support.v7.widget.DefaultItemAnimator;
- import android.support.v7.widget.LinearLayoutManager;
- import android.support.v7.widget.OrientationHelper;
- import android.support.v7.widget.RecyclerView;
- public class MainActivity extends ActionBarActivity {
- private RecyclerView recyclerView;
- private List<String> mDatas;
- private MyRecyclerAdapter recycleAdapter;
- @SuppressLint("NewApi") @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout. activity_main);
- recyclerView = (RecyclerView) findViewById(R.id.recyclerView );
- initData();
- recycleAdapter= new MyRecyclerAdapter(MainActivity.this , mDatas );
- LinearLayoutManager layoutManager = new LinearLayoutManager(this);
- //设置布局管理器
- recyclerView.setLayoutManager(layoutManager);
- //设置为垂直布局,这也是默认的
- layoutManager.setOrientation(OrientationHelper.VERTICAL);
- //设置Adapter
- recyclerView.setAdapter( recycleAdapter);
- //设置增加或删除条目的动画
- recyclerView.setItemAnimator(new DefaultItemAnimator());
- }
- private void initData() {
- mDatas = new ArrayList<String>();
- for ( int i=0; i < 40; i++) {
- mDatas.add( "item"+i);
- }
- }
- }
- 3.2Adapter
-
- package com.example.reclerviewpractice.adapter;
- import java.util.List;
- import com.example.reclerviewpractice.R;
- import android.content.Context;
- import android.support.v7.widget.RecyclerView;
- import android.support.v7.widget.RecyclerView.ViewHolder;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.TextView;
- public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.MyViewHolder> {
- private List<String> mDatas;
- private Context mContext;
- private LayoutInflater inflater;
- public MyRecyclerAdapter(Context context, List<String> datas){
- this. mContext=context;
- this. mDatas=datas;
- inflater=LayoutInflater. from(mContext);
- }
- @Override
- public int getItemCount() {
- return mDatas.size();
- }
- //填充onCreateViewHolder方法返回的holder中的控件
- @Override
- public void onBindViewHolder(MyViewHolder holder, final int position) {
- holder.tv.setText( mDatas.get(position));
- }
- //重写onCreateViewHolder方法,返回一个自定义的ViewHolder
- @Override
- public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View view = inflater.inflate(R.layout. item_home,parent, false);
- MyViewHolder holder= new MyViewHolder(view);
- return holder;
- }
- class MyViewHolder extends ViewHolder{
- TextView tv;
- public MyViewHolder(View view) {
- super(view);
- tv=(TextView) view.findViewById(R.id. tv_item);
- }
- }
- }
- package com.example.reclerviewpractice;
- import android.content.Context;
- import android.content.res.TypedArray;
- import android.graphics.Canvas;
- import android.graphics.Rect;
- import android.graphics.drawable.Drawable;
- import android.support.v7.widget.LinearLayoutManager ;
- import android.support.v7.widget.RecyclerView;
- import android.view.View;
- public class DividerItemDecoration extends RecyclerView.ItemDecoration {
- private static final int[] ATTRS = new int[]{
- android.R.attr. listDivider
- };
- public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
- public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
- private Drawable mDivider;
- private int mOrientation;
- public DividerItemDecoration(Context context, int orientation) {
- final TypedArray a = context.obtainStyledAttributes(ATTRS );
- mDivider = a.getDrawable(0);
- a.recycle();
- setOrientation(orientation);
- }
- public void setOrientation( int orientation) {
- if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
- throw new IllegalArgumentException( "invalid orientation");
- }
- mOrientation = orientation;
- }
- @Override
- public void onDraw(Canvas c, RecyclerView parent) {
- if (mOrientation == VERTICAL_LIST) {
- drawVertical(c, parent);
- } else {
- drawHorizontal(c, parent);
- }
- }
- 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;
- final int bottom = top + mDivider.getIntrinsicHeight();
- mDivider.setBounds(left, top, right, bottom);
- mDivider.draw(c);
- }
- }
- public void drawHorizontal(Canvas c, RecyclerView parent) {
- final int top = parent.getPaddingTop();
- final int bottom = parent.getHeight() - parent.getPaddingBottom();
- 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 left = child.getRight() + params.rightMargin;
- final int right = left + mDivider.getIntrinsicHeight();
- mDivider.setBounds(left, top, right, bottom);
- mDivider.draw(c);
- }
- }
- @Override
- public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
- if (mOrientation == VERTICAL_LIST) {
- outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
- }else{
- outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
- }
- }
- }
- 3.4 改变分隔线样式
-
那么怎么更改分隔线的样式呢?在上面的DividerItemDecoration这个类中可以看到这个分隔线是跟ListView一样的,即系统的默认的样式,因此我们可以在styles的xml文件中进行更改,更改如下:
- <!-- Application theme. -->
- <style name ="AppTheme" parent="AppBaseTheme">
- <!-- All customizations that are NOT specific to a particular API-level can go here. -->
- <item name= "android:listDivider">@drawable/divider </item >
- </style >
divider的内容如下: -
- <?xml version="1.0" encoding= "utf-8"?>
- <shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
- <!-- 填充的颜色 -->
- <solid android:color ="@color/color_red"/>
- <!-- 线条大小 -->
- <size android:height ="1dp" android:width ="1dp"/>
- </shape>
- 三、Gridview表格样式
-
在上面的代码中我们使用了
- LinearLayoutManager layoutManager = new LinearLayoutManager(this);
- //设置布局管理器
- recyclerView.setLayoutManager(layoutManager);
RecyclerView.LayoutManager是一个抽象类,系统为我们提供了三个实现类
①LinearLayoutManager即线性布局,这个是在上面的例子中我们用到的布局
②GridLayoutManager即表格布局
③StaggeredGridLayoutManager即流式布局,如瀑布流效果
假如将上述例子换成GridView的效果,那么相应的代码应该这样改- recyclerView .setLayoutManager(new GridLayoutManager( this,4));
除此之外上述的分隔线也要做相应的更改,因为在上述DividerItemDecoration这个方法中从
- final int left = parent.getPaddingLeft();
- final int right = parent.getWidth() - parent.getPaddingRight();
这两行我们可以看出来,它是绘制了一条线这条线就是从RecyclerView去掉左右边距后,剩余的部分,因为当显示成ListView时每一行就一个Item所以整体效果看上去就跟ListView差不多,而当展示成GridView那样的效果时,每一行就不止一个条目了,而有可能是多个,所以这个类就不再适用了,我们需要重新写一个,这里我就直接用鸿洋大神写的了,它的博客地址:http://blog.csdn.net/lmj623565791/article/details/45059587
- package com.example.reclerviewpractice;
- import android.content.Context;
- import android.content.res.TypedArray;
- import android.graphics.Canvas;
- import android.graphics.Rect;
- import android.graphics.drawable.Drawable;
- import android.support.v7.widget.GridLayoutManager;
- import android.support.v7.widget.RecyclerView;
- import android.support.v7.widget.RecyclerView.LayoutManager;
- import android.support.v7.widget.RecyclerView.State;
- import android.support.v7.widget.StaggeredGridLayoutManager;
- import android.view.View;
- /**
- *
- * @author zhy
- *
- */
- 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());
- }
- } }
还有更让人瞠目结舌的效果,将上述代码做如下更改- StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.HORIZONTAL);
- //设置布局管理器
- recyclerView.setLayoutManager(layoutManager);
这里需要注意的是StaggeredGridLayoutManager构造的第二个参数传一个orientation,如果传入的是StaggeredGridLayoutManager.VERTICAL那么前面那个参数就代表有多少列;如果传是StaggeredGridLayoutManager.HORIZONTAL那么前面那个参数就代表有多少行
四、RecyclerView增加和删除的动画(包括RecyclerView.Adapter中刷新的几个方法的对比) -
在上面也提到了控制RecyclerView增加和删除的动画是通过ItemAnimator这个类来实现的,ItemAnimator这类也是个抽象的类,系统默认给我们提供了一种增加和删除的动画,下面我们就来看看这种动画的效果,我们需要做的修改如下:
- LinearLayoutManager layoutManager = new LinearLayoutManager(this);
- //设置布局管理器
- recyclerView.setLayoutManager(layoutManager);
- //设置增加或删除条目的动画
- recyclerView.setItemAnimator( new DefaultItemAnimator());
然后重写ActionBar的
- @Override
- public boolean onCreateOptionsMenu(Menu menu)
- {
- getMenuInflater().inflate(R.menu. main, menu);
- return super.onCreateOptionsMenu(menu);
- }
- @Override
- public boolean onOptionsItemSelected(MenuItem item)
- {
- switch (item.getItemId())
- {
- case R.id. id_action_add:
- recycleAdapter.addData(1);
- break;
- case R.id. id_action_delete:
- recycleAdapter.removeData(1);
- break;
- }
- return true;
- }
关于R.menu. main中的main.xml这个文件代码就不贴了,在最后的一个汇总的例子里会有recyclerViewAdapter中增加的两个方法:public void addData( int position) {mDatas .add(position, "Insert One" );notifyItemInserted(position);notifyItemRangeChanged(position, mDatas .size());}
public void removeData( int position) {mDatas .remove(position);notifyItemRemoved(position);
notifyItemRangeChanged(position, mDatas .size());}这里需要说一下RecyclerView.Adapter中刷新数据的几个方法,一共有这么几个方法
- public final void notifyDataSetChanged()
- public final void notifyItemChanged(int position)
- public final void notifyItemRangeChanged(int positionStart, int itemCount)
- public final void notifyItemInserted(int position)
- public final void notifyItemMoved(int fromPosition, int toPosition)
- public final void notifyItemRangeInserted(int positionStart, int itemCount)
- public final void notifyItemRemoved(int position)
- public final void notifyItemRangeRemoved(int positionStart, int itemCount)
notifyDataSetChanged()这个方法跟我们平时用到的ListView的Adapter的方法一样,这里就不多做描述了。
notifyItemChanged(int position),当position位置的数据发生了改变时就会调用这个方法,就会回调对应position的onBindViewHolder()方法了,当然,因为ViewHolder是复用的,所以如果position在当前屏幕以外,也就不会回调了,因为没有意义,下次position滚动会当前屏幕以内的时候同样会调用onBindViewHolder()方法刷新数据了。其他的方法也是同样的道理。public final void notifyItemRangeChanged(int positionStart, int itemCount),顾名思义,可以刷新从positionStart开始itemCount数量的item了(这里的刷新指回调onBindViewHolder()方法)。
public final void notifyItemInserted(int position),这个方法是在第position位置被插入了一条数据的时候可以使用这个方法刷新,注意这个方法调用后会有插入的动画,这个动画可以使用默认的,也可以自己定义。
public final void notifyItemMoved(int fromPosition, int toPosition),这个方法是从fromPosition移动到toPosition为止的时候可以使用这个方法刷新
public final void notifyItemRangeInserted(int positionStart, int itemCount),显然是批量添加。
public final void notifyItemRemoved(int position),第position个被删除的时候刷新,同样会有动画。
将上述更改运行,点击添加和删除按钮效果图如下:public final void notifyItemRangeRemoved(int positionStart, int itemCount),批量删除。
五、给RecyclerView的Item添加点击事件 -
-
- public interface OnItemClickListener{void onClick( int position);void onLongClick( int position);}public void setOnItemClickListener(OnItemClickListener onItemClickListener ){this . mOnItemClickListener =onItemClickListener;}
然后onBindViewHolder方法要做如下更改
- @Override
- public void onBindViewHolder(MyViewHolder holder, final int position) {
- holder. tv.setText( mDatas.get(position));
- if( mOnItemClickListener!= null){
- holder. itemView.setOnClickListener( new OnClickListener() {
- @Override
- public void onClick(View v) {
- mOnItemClickListener.onClick(position);
- }
- });
- holder. itemView.setOnLongClickListener( new OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- mOnItemClickListener.onLongClick(position);
- return false;
- }
- });
- }
- }<span style="color:#333333;">
- </span>
在MainAcitivity中增加
- recycleAdapter.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onLongClick(int position) {
- Toast.makeText(MainActivity.this,"onLongClick事件 您点击了第:"+position+"个Item",0).show();
- }
- @Override
- public void onClick(int position) {
- Toast.makeText(MainActivity.this,"onClick事件 您点击了第:"+position+"个Item",0).show();
- }
- });