RecyclerView的使用全面解析

来源:互联网 发布:python os.popen 编辑:程序博客网 时间:2024/06/06 12:03

RecyclerView的使用全面解析

本文参照:http://www.jianshu.com/p/626a082bf569 和原文一样 

1 RecyclerView的基本使用

    

引入RecyclerView

由于该控件并不在Andorid SDK中的,而是在support v7包中,因此我们要手动添加该控件。
在build.gradle中添加如下依赖:

  1. dependencies {
  2. ...
  3. compile 'com.android.support:appcompat-v7:23.1.1' //Toolbar
  4. compile 'com.android.support:recyclerview-v7:23.1.1' //RecyclerView
  5. }

几个重要的类

在我们开始讲解RecyclerView的用法之前,我们要先认识几个它常用的内部类,因为这几个内部类很重要,贯穿整个使用过程:
1、RecyclerView.Adapter:抽象类,为RecyclerView提供数据,一般根据不同的业务需求来编写具体的实现类。
2、RecyclerView.LayoutManager:抽象类,主要用于测量RecyclerView的子Item,以及根据不同的布局方式来实现Item的布局效果,v 7包自带的实现类有:LinearLayoutManager、StaggeredGridLayoutManager、GridLayoutManager。
3、RecyclerView.ItemDecoration:抽象类,这个主要用于不同的Item之间添加分割线(可选)。官方没有实现类,所以如果要添加分割线,我们需要手动实现这个抽象类。
4、RecyclerView.ItemAnimator:抽象类,这个主要用于当一个item添加或者删除的时候出现的动画效果,官方提供一个默认的实现类。如果想要使我们的RecyclerView在添加、删除数据的时候有炫酷的动画,可以实现这个抽象类。
可以看到,这些重要的类都是抽象类,这就为RecyclerView的高度可定制性打下了坚实的基础,利用不同的实现类,能自由、灵活地使用RecyclerView,这也是RecyclerView的迷人之处。


基本使用

使用线性布局

1、布局

在main.xml文件中进行如下布局:

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:toolbar="http://schemas.android.com/apk/res-auto"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical">
  6. <android.support.v7.widget.Toolbar
  7. android:id="@+id/toolbar"
  8. android:layout_width="match_parent"
  9. android:layout_height="wrap_content"
  10. android:background="?attr/colorPrimary"
  11. toolbar:titleTextColor="@android:color/white"
  12. toolbar:subtitleTextColor="@android:color/white"
  13. toolbar:popupTheme="@style/ToolbarPopupTheme">
  14. </android.support.v7.widget.Toolbar>
  15. <android.support.v7.widget.RecyclerView
  16. android:id="@+id/recyclerview"
  17. android:layout_width="match_parent"
  18. android:layout_height="match_parent"/>
  19. </LinearLayout>
这里使用了两个控件,一个是RecyclerView,一个是Toolbar,关于Toolbar的使用可以参考 Android ToolBar 使用完全解析.
在创建了RecyclerView控件后,需要创建item条目布局itemLayout.xml代码如下
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:orientation="vertical" android:layout_width="match_parent"
  3. android:layout_height="match_parent"
  4. android:background="@android:color/holo_blue_light"
  5. android:layout_margin="5dp">
  6. <TextView
  7. android:id="@+id/num"
  8. android:layout_width="match_parent"
  9. android:layout_height="50dp"
  10. android:gravity="center"
  11. android:textSize="20sp"/>
  12. </LinearLayout>
这个Item布局很简单,里面只有一个textview,并且布局的背景颜色为蓝色,设置了margin值,为了与别的item相互区分出来。

2、创建Adapter适配器 

 只要是复杂的控件都会使用到适配器这里也一样.RecylerView提供了一个抽象类 是RecyclerIVew.Adapter,我们可以通过这个类来写自己的适配器,我们先看看这个抽象类里面的抽象方法:
  1. public static abstract class Adapter<VH extends ViewHolder> {
  2. public abstract VH onCreateViewHolder(ViewGroup parent, int viewType); // 1
  3. public abstract void onBindViewHolder(VH holder, int position); // 2
  4. public abstract int getItemCount(); // 3
  5. }
继承该类的时候,必须重写这三个方法,我们分别解释一下这三个方法是什么作用:
①onCreateViewHolder:创建ViewHolder,该方法会在RecyclerView需要展示一个item的时候回调。重写该方法时,应该使ViewHolder加载item view的布局。这个能发避免了不必要的findViewById操作,提高了性能。如果熟悉ListView的ViewHolder操作那么也能很容易理解这个。
②onBindeViewHolder:该方法在RecyclerView在特定位置展示数据时候回调,顾名思义,把数据绑定、填充到相应的item view中。
③getItemCount:返回数据的数量。
我们注意到,继承该类的时候需要声明它的泛型类型VH,VH继承自RecyclerView.ViewHolder,同时第①个方法的返回值也是VH,由于ViewHolder是根据业务需求而变化的,不同的业务需求而需要的ViewHolder不尽相同,所以没必要单独写一个ViewHolder.java文件,因此我们可以在Adapter子类内部实现一个内部类ViewHolder,这样也符合单一职责原则。具体的Adapter适配器代码如下所示,MyAdapter.java:
  1. public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
  2. private List<String> mDataSet;
  3. //构造器,接受数据集
  4. public MyAdapter(List<String> data){
  5. mDataSet = data;
  6. }
  7. @Override
  8. public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  9. //加载布局文件
  10. View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.itemlayout,parent,false);
  11. ViewHolder vh = new ViewHolder(v);
  12. return vh;
  13. }
  14. @Override
  15. public void onBindViewHolder(final ViewHolder holder, int position) {
  16. //将数据填充到具体的view中
  17. holder.mTextView.setText(mDataSet.get(position));
  18. }
  19. @Override
  20. public int getItemCount() {
  21. return mDataSet.size();
  22. }
  23. class ViewHolder extends RecyclerView.ViewHolder{
  24. public TextView mTextView;
  25. public ViewHolder(View itemView) {
  26. super(itemView);
  27. //由于itemView是item的布局文件,我们需要的是里面的textView,因此利用itemView.findViewById获
  28. //取里面的textView实例,后面通过onBindViewHolder方法能直接填充数据到每一个textView了
  29. mTextView = (TextView) itemView.findViewById(R.id.num);
  30. }
  31. }
  32. }

3、MainActivity

一般在java代码中引用RecyclerView,要做如下工作:
①获取RecyclerView实例,通过findViewById()方法。
②为RecyclerView设置布局管理器
③为RecyclerView设置数据适配器Adapter。
我们来看看具体代码:


  1. public class MainActivity extends AppCompatActivity {
  2. private Toolbar toolbar;
  3. private RecyclerView mRecyclerView;
  4. private RecyclerView.LayoutManager mLayoutManager;
  5. private MyAdapter mAdapter;
  6. private List<String> mData;
  7. @Override
  8. protected void onCreate(Bundle savedInstanceState) {
  9. super.onCreate(savedInstanceState);
  10. setContentView(R.layout.activity_main);
  11. initToolbar();
  12. initData();
  13. initRecyclerView();
  14. }
  15. private void initData() {
  16. mData = new ArrayList<>();
  17. for(int i=0;i<20;i++){
  18. mData.add("Item "+i);
  19. }
  20. }
  21. private void initRecyclerView() {
  22. //1 实例化RecyclerView
  23. mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
  24. mLayoutManager = new LinearLayoutManager(this);
  25. //2 为RecyclerView创建布局管理器,这里使用的是LinearLayoutManager,表示里面的Item排列是线性排列
  26. mRecyclerView.setLayoutManager(mLayoutManager);
  27. mAdapter = new MyAdapter(mData);
  28. //3 设置数据适配器
  29. mRecyclerView.setAdapter(mAdapter);
  30. }
  31. private void initToolbar() {
  32. toolbar = (Toolbar) findViewById(R.id.toolbar);
  33. toolbar.setTitle("RecyclerView");
  34. toolbar.setSubtitle("demo");
  35. //设置导航图标、添加菜单点击事件要在setSupportActionBar方法之后
  36. setSupportActionBar(toolbar);
  37. toolbar.setNavigationIcon(R.mipmap.ic_drawer_home);
  38. toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
  39. @Override
  40. public boolean onMenuItemClick(MenuItem item) {
  41. switch (item.getItemId()) {
  42. case R.id.action_search:
  43. Toast.makeText(MainActivity.this, "Search !", Toast.LENGTH_LONG).show();
  44. break;
  45. case R.id.action_notifications:
  46. Toast.makeText(MainActivity.this, "Notificationa !", Toast.LENGTH_SHORT).show();
  47. break;
  48. case R.id.action_settings:
  49. Toast.makeText(MainActivity.this, "Settings !", Toast.LENGTH_SHORT).show();
  50. break;
  51. }
  52. return true;
  53. }
  54. });
  55. }
  56. @Override
  57. public boolean onCreateOptionsMenu(Menu menu) {
  58. getMenuInflater().inflate(R.menu.menu_main, menu);
  59. return true;
  60. }
  61. }

线性的listview就出来了


更改布局管理器

以上使用了LinearLayoutManager,线性布局管理器,使得item呈竖直排列或者水平排列,除了这种线性布局之外,官方还提供了另外两种布局方式,分别是GridLayoutManager和StaggeredGridLayoutManager。前者容易理解,是表格形式的布局,类似于GridView,而后者则是瀑布流表格布局。

1、GridLayoutManager
要改变布局管理器很简单,我们只需要实例化一个表格布局管理器即可,我们先看看它的构造函数:

  1. /**
  2. * @param context Current context, will be used to access resources.
  3. * @param spanCount The number of columns or rows in the grid
  4. * @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link
  5. * #VERTICAL}.
  6. * @param reverseLayout When set to true, layouts from end to start.
  7. */
  8. public GridLayoutManager(Context context, int spanCount, int orientation,
  9. boolean reverseLayout) {
  10. super(context, orientation, reverseLayout);
  11. setSpanCount(spanCount);
  12. }
其中第二个参数spanCount表示表格的行数或者列数;第三个参数表示是水平滑动或者是竖直方向滑动;最后一个参数表示是否从数据的尾部开始显示。
我们在MainActivity中作出如下修改:
  1. mRecyclerView.setLayoutManager(new GridLayoutManager(this,4,VERTICAL,false));
这样就变成九宫格了

2、StaggeredGridLayoutManager    瀑布流
瀑布流效果现在经常会见到,如果只有ListView或者GridView实现的话,有一定难度,但是如果使用RecyclerView则会简单很多,只需要使用StaggeredGridLayoutManager即可实现。
我们在MainActivity中,更改使用的布局管理器如下:
  1. //构造器中,第一个参数表示列数或者行数,第二个参数表示滑动方向
  2. mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.VERTICAL));
当然,只做这些改动是不足够的,因为对于每个Item view来说,它的高度都是一样的,这样就达不到瀑布流的效果了,因此,我们要修改每一个Item View的Height,具体实现逻辑如下所示,MyAdapter.java:
  1. public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
  2. private List<String> mDataSet;
  3. private List<Integer> mHeight;
  4. public MyAdapter(List<String> data){
  5. mDataSet = data;
  6. mHeight = new ArrayList<Integer>();
  7. //随机获取一个mHeight值
  8. for (int i = 0; i < mDataSet.size(); i++)
  9. {
  10. mHeight.add( (int) (100 + Math.random() * 300));
  11. }
  12. }
  13. @Override
  14. public void onBindViewHolder(final ViewHolder holder, int position) {
  15. holder.mTextView.setText(mDataSet.get(position));
  16. //绑定数据的同时,修改每个ItemView的高度
  17. ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
  18. lp.height = mHeight.get(position);
  19. holder.itemView.setLayoutParams(lp);
  20. }
  21. ...
  22. }
使用RecyclerView可以很轻松地实现瀑布流效果。

:处理RecyclerView的点击事件

因此我们需要处理用户的点击事件。在ListView和GridView提供了onItemClickListener这个监听器,然而我们查找RecyclerView的API却没有类似的监听器,因此我们需要自己手动处理它的点击事件。

方法一:利用View.onClickListener及onLongClickListener

利用了java回调机制,这里我们依赖于子Item View的onClickListener及onLongClickListener。

使用接口回调
首先对MyAdapter.java代码做出如下修改
①新建两个内部接口:    

  public interface OnItemClickListener{        void onItemClick(View view,int position);    }    public interface OnItemLongClickListener{        void onItemLongClick(View view,int position);    }
②新建两个私有变量用于保存用户设置的监听器及其set方法:
    private OnItemClickListener mOnItemClickListener;    private OnItemLongClickListener mOnItemLongClickListener;    public void setOnItemClickListener(OnItemClickListener mOnItemClickListener){        this.mOnItemClickListener = mOnItemClickListener;    }    public void setOnItemLongClickListener(OnItemLongClickListener mOnItemLongClickListener) {        this.mOnItemLongClickListener = mOnItemLongClickListener;    }
  1. ③在onBindViewHolder方法内,实现回调:
@Overridepublic void onBindViewHolder(final ViewHolder holder, int position) {    holder.mTextView.setText(mDataSet.get(position));    //判断是否设置了监听器if(mOnItemClickListener != null){        //为ItemView设置监听器        holder.itemView.setOnClickListener(new View.OnClickListener() {            @Overridepublic void onClick(View v) {                int position = holder.getLayoutPosition(); // 1                mOnItemClickListener.onItemClick(holder.itemView,position); // 2            }        });    }    if(mOnItemLongClickListener != null){        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {            @Overridepublic boolean onLongClick(View v) {                int position = holder.getLayoutPosition();                mOnItemLongClickListener.onItemLongClick(holder.itemView,position);                //返回true 表示消耗了事件 事件不会继续传递return true;            }        });    }}

可以看到,这里实际上用到了子Item View的onClickListener和onLongClickListener这两个监听器,如果当前子item view被点击了,会触发点击事件进行回调,然后在①处获取当前点击位置的position值,接着在②号代码处进行再次回调,而这一次的回调是我们自己手动添加的,需要实现上面所述的接口。
修改完MyAdapter.java后,我们接着在MainActivity.java中设置监听器,采用匿名内部类的形式实现了onItemClickListener、onItemLongClickListener接口,这种写法与一般的设置监听器的流程相同:
mAdapter = new MyAdapter(mData);mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {    @Overridepublic void onItemClick(View view, int position) {        Toast.makeText(MainActivity.this, "click " + mData.get(position), Toast.LENGTH_SHORT).show();    }});mAdapter.setOnItemLongClickListener(new MyAdapter.OnItemLongClickListener() {    @Overridepublic void onItemLongClick(View view, int position) {        Toast.makeText(MainActivity.this,"long click "+mData.get(position),Toast.LENGTH_SHORT).show();    }});mRecyclerView.setAdapter(mAdapter);

方法二:利用RecyclerView.OnItemTouchListener

官方虽然没有提供现成的监听器,但是提供了一个内部接口:OnItemTouchListener,我们看看官方文档对它的描述:An OnItemTouchListener allows the application to intercept touch events in progress at the view hierarchy level of the RecyclerView before those touch events are considered for RecyclerView's own scrolling behavior。大概意思是说该接口允许我们对RecyclerView的触摸事件进行拦截,我们看看它的几个接口方法:


  @Overridepublic boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {        return false;    }    @Overridepublic void onTouchEvent(RecyclerView rv, MotionEvent e) {    }    @Overridepublic void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {    }

熟悉View事件分发机制的同学看到这些方法的名字一定觉得很熟悉了吧,所以这个接口的实质是利用了View的事件分发与拦截机制。大体思路是:我们可以在onInterceptTouchEvent获取当前触摸位置对应的子item view,根据点击状态决定是否要把事件拦截,在拦截的时候同时添加一个回调方法,这样我们自己实现的监听器接口就能在这里得到回调。具体的请看如下代码:
新建一个RecyclerViewClickListener.java:

说一下这三个方法  有问题的话可以参照这个
onInterceptTouchEvent() 拦截事件
 onTouchEvent() 处理事件
 onRequestDisallowInterceptTouchEvent() 请求不要拦截事件
 dispatchTouchEvent() 分发事件

/**  *  @author ChenYu  *      2016-05-10  */public class RecyclerViewClickListener implements RecyclerView.OnItemTouchListener {    private int mLastDownX,mLastDownY;    //该值记录了最小滑动距离private int touchSlop ;    private OnItemClickListener mListener;    //是否是单击事件private boolean isSingleTapUp = false;    //是否是长按事件private boolean isLongPressUp = false;    private boolean isMove = false;    private long mDownTime;    //内部接口,定义点击方法以及长按方法public interface OnItemClickListener {        void onItemClick(View view, int position);        void onItemLongClick(View view, int position);    }    public RecyclerViewClickListener(Context context,OnItemClickListener listener){        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();        mListener = listener;    }    @Overridepublic boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {        int x = (int) e.getX();        int y = (int) e.getY();        switch (e.getAction()){            /**             *  如果是ACTION_DOWN事件,那么记录当前按下的位置,             *  记录当前的系统时间。             */case MotionEvent.ACTION_DOWN:                mLastDownX = x;                mLastDownY = y;                mDownTime = System.currentTimeMillis();                isMove = false;                break;            /**             *  如果是ACTION_MOVE事件,此时根据TouchSlop判断用户在按下的时候是否滑动了,             *  如果滑动了,那么接下来将不处理点击事件             */case MotionEvent.ACTION_MOVE:                if(Math.abs(x - mLastDownX)>touchSlop || Math.abs(y - mLastDownY)>touchSlop){                    isMove = true;                }                break;            /**             *  如果是ACTION_UP事件,那么根据isMove标志位来判断是否需要处理点击事件;             *  根据系统时间的差值来判断是哪种事件,如果按下事件超过1ms,则认为是长按事件,             *  否则是单击事件。             */case MotionEvent.ACTION_UP:                if(isMove){                    break;                }                if(System.currentTimeMillis()-mDownTime > 1000){                    isLongPressUp = true;                }else {                    isSingleTapUp = true;                }                break;        }        if(isSingleTapUp ){            //根据触摸坐标来获取childView            View childView = rv.findChildViewUnder(e.getX(),e.getY());            isSingleTapUp = false;            if(childView != null){                //回调mListener#onItemClick方法                mListener.onItemClick(childView,rv.getChildLayoutPosition(childView));                return true;            }            return false;        }        if (isLongPressUp ){            View childView = rv.findChildViewUnder(e.getX(),e.getY());            isLongPressUp = false;            if(childView != null){                mListener.onItemLongClick(childView, rv.getChildLayoutPosition(childView));                return true;            }            return false;        }        return false;    }    @Overridepublic void onTouchEvent(RecyclerView rv, MotionEvent e) {    }    @Overridepublic void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {    }}

接着我们在MainActivity.java添加一段代码,同时不要忘记把方法一的代码注释掉哦:
 //调用RecyclerView#addOnItemTouchListener方法能添加一个RecyclerView.OnItemTouchListener对象 mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener(this,new RecyclerViewClickListener.OnItemClickListener() {    @Overridepublic void onItemClick(View view, int position) {        Toast.makeText(MainActivity.this,"Click "+mData.get(position),Toast.LENGTH_SHORT).show();    }    @Overridepublic void onItemLongClick(View view, int position) {        Toast.makeText(MainActivity.this,"Long Click "+mData.get(position),Toast.LENGTH_SHORT).show();    }}));

那么方法一和方法二有何区别呢?
首先,方法一我们是直接在MyAdapter数据适配器中,为itemview设置了内置监听器,再通过这个监听器实现我们的回调方法,相当于回调了两次,同时这个方法与MyAdapter的耦合度比较高,也违反了单一职责原则,当然其简易性也是突出的优点。而方法二,我们利用了onTouchListener接口对事件进行了拦截,在拦截中处理我们的点击事件,实现了与适配器的解耦,但是复杂程度会比方法一大。总地来说,如果RecyclerView需要处理的点击事件逻辑很简单,那么可以使用方法一;如果需要处理比较复杂的点击事件,比如说,双击、长按等点击事件,则需要使用方法二去实现各种复杂的逻辑。

对方法二的优化

在实现方法二的RecyclerViewClickListener的时候,在内部对事件的实现了单击、长按的判断,但是这个长按事件不是标准的,只有松开手指的时候才会触发长按事件,这也算是一点瑕疵,同时如果要增加别的事件,比如说双击事件,则需要增加相应的逻辑,如果需要判断的事件种类变多则会给我们的代码编写带来困难,那么有没有更加简便的方法呢?其实安卓SDK为我们提供了一个手势检测类:GestureDetector来处理各种不同的手势,那么我们完全可以利用GestureDetector来对方法二进行改进。

新建RecyclerViewClickListener2.java:

public class RecyclerViewClickListener2 implements RecyclerView.OnItemTouchListener {    private GestureDetector mGestureDetector;    private OnItemClickListener mListener;    //内部接口,定义点击方法以及长按方法public interface OnItemClickListener {        void onItemClick(View view, int position);        void onItemLongClick(View view, int position);    }    public RecyclerViewClickListener2(Context context, final RecyclerView recyclerView,OnItemClickListener listener){        mListener = listener;        mGestureDetector = new GestureDetector(context,                new GestureDetector.SimpleOnGestureListener(){ //这里选择SimpleOnGestureListener实现类,可以根据需要选择重写的方法//单击事件@Overridepublic boolean onSingleTapUp(MotionEvent e) {                        View childView = recyclerView.findChildViewUnder(e.getX(),e.getY());                        if(childView != null && mListener != null){                            mListener.onItemClick(childView,recyclerView.getChildLayoutPosition(childView));                            return true;                        }                        return false;                    }                    //长按事件@Overridepublic void onLongPress(MotionEvent e) {                        View childView = recyclerView.findChildViewUnder(e.getX(),e.getY());                        if(childView != null && mListener != null){                            mListener.onItemLongClick(childView,recyclerView.getChildLayoutPosition(childView));                        }                    }                });    }    @Overridepublic boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {        //把事件交给GestureDetector处理if(mGestureDetector.onTouchEvent(e)){            return true;        }elsereturn false;    }    @Overridepublic void onTouchEvent(RecyclerView rv, MotionEvent e) {    }    @Overridepublic void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {    }}
在MainActivity.java改动如下:
mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener2(this, mRecyclerView,        new RecyclerViewClickListener2.OnItemClickListener() {    @Overridepublic void onItemClick(View view, int position) {        Toast.makeText(MainActivity.this,"Click "+mData.get(position),Toast.LENGTH_SHORT).show();    }    @Overridepublic void onItemLongClick(View view, int position) {        Toast.makeText(MainActivity.this,"Long Click "+mData.get(position),Toast.LENGTH_SHORT).show();    }}));

运行结果与上面的相同。

三:操作数据及添加分割线

添加数据和删除数据和修改数据,接下来将讲述怎么操作数据和添加分割线。 
 不过我感觉RecyclerView和CardView配合使用效果更好

操作数据

首先我们看看官方提供了什么API给我们调用,我们看RecyclerView.Adapter中提供的四个方法如下:

//该方法用于当增加一个数据的时候,position表示新增数据显示的位置final void notifyItemInserted(int position)//该方法用于删除一个数据的时候,position表示数据删除的位置final void notifyItemRemoved(int position)//该方法表示所在position对应的item位置不会改变,但是该item内容发生变化final void notifyItemChanged(int position)//该方法一般用于:适配器之前装载的数据大部分已经过时了,需要重新更新数据//调用该方法的时候,recyclerView会重新计算子item及所有子item重新布局//出于效率考虑,官方建议用更加精确的方法(比如上面三个方法)来取代这个方法final void notifyDataSetChanged()

为了直观地看到各个方法的用法,我们对原有代码进行修改,看看具体的运行结果如何。
首先对MyAdapter.java修改,新增三个方法: //移除数据
    public void removeData(int position) {        mDataSet.remove(position);        notifyItemRemoved(position);    }    //新增数据public void addData(int position){        mDataSet.add(position,"Add One");        notifyItemInserted(position);    }    //更改某个位置的数据public void changeData(int position){        mDataSet.set(position,"Item has changed"+ count++);        notifyItemChanged(position);    }

接下来我们在MainActivity.java中调用这三个方法:
mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener2(this, mRecyclerView,    new RecyclerViewClickListener2.OnItemClickListener() {        @Overridepublic void onItemClick(View view, int position) {            Toast.makeText(MainActivity.this, "Click " + mData.get(position), Toast.LENGTH_SHORT).show();        }        @Overridepublic void onItemLongClick(View view, int position) {            //长按某个item后,将移除这个item            mAdapter.removeData(position);        }    }));...toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {    @Overridepublic boolean onMenuItemClick(MenuItem item) {        switch (item.getItemId()) {            case R.id.action_search:                Toast.makeText(MainActivity.this, "Search !", Toast.LENGTH_LONG).show();                break;            case R.id.action_add:                mAdapter.addData(1);                break;            case R.id.action_change:                mAdapter.changeData(2);                break;        }        return true;    }});

可以看出,数据都被正确地增加、删除、修改成功了。我们再仔细观察一下,每一个Item变化的时候都不是瞬间变化的,它都会有一个动画效果,使得用户体验很好,其实这里面使用了RecyclerView默认提供的动画效果:
//这一句不是必要的,因为RecyclerView会默认使用mRecyclerView.setItemAnimator(new DefaultItemAnimator());
还记得在第一节的时候提到过RecyclerView.ItemAnimator这个抽象类,它是用于控制Item的动画效果的,而DefaultItemAnimator()正是它的一个实现类。这也说明了,我们可以自定义实现很多特效动画。至于它的各种动画效果,可以参考GitHub上的开源项目:RecyclerViewItemAnimators,这里就不展开来讲了。

添加分割线

细心的同学会发现,笔者的RecyclerView每个Item之间是用magin属性来分开的,那么RecycleView有没有像ListView那样可以直接在xml属性中添加android:divider呢?答案是没有的,可能是考虑到RecycleView灵活多变的特点,官方没有添加这个属性,不过我们还是有办法可以手动去添加。在第一节有提到过:RecyclerView.ItemDecoration这个抽象类,它是用于添加分割线的,按照惯例,我们看看这个抽象类里面有什么方法:



public static abstract class ItemDecoration {    public void onDraw(Canvas c, RecyclerView parent, State state) {        onDraw(c, parent);    }    public void onDrawOver(Canvas c, RecyclerView parent, State state) {        onDrawOver(c, parent);    }    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {        getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),                parent);    }}

省略了三个官方不提倡的用法,这里简单说明一下以上各个方法的作用:
onDraw和onDrawOver,显然,这两个方法是用于绘制的,那么绘制分割线的逻辑可以放在这里面,它们二者的具体区别是:onDraw是在item view绘制之前调用,而onDrawOver是在item view绘制之后调用,因此我们一般选择重写其中一个方法即可。getItemOffsets,这个方法是告诉RecyclerView在绘制完一个item view的时候,应该留下多少空位,以便于绘制分割线。
既然知道了这三个方法的作用,那么我们来写一个实现类,新建DividerItemDecoration(注:该类参考自Android官方):

public class DividerItemDecoration extends RecyclerView.ItemDecoration {    //使用系统自带的listDividerprivate 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){        //使用TypeArray加载该系统资源final TypedArray ta = context.obtainStyledAttributes(ATTRS);        mDivider = ta.getDrawable(0);        //缓存        ta.recycle();        setOrientation(orientation);    }    public void setOrientation(int orientation){        if(orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST){            throw new IllegalArgumentException("invalid orientation");        }        mOrientation = orientation;    }    @Overridepublic void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {        if(mOrientation == VERTICAL_LIST){            drawVertical(c,parent);        }else{            drawHorizontal(c,parent);        }    }    public void drawVertical(Canvas c,RecyclerView parent){        //获取分割线的左边距,即RecyclerView的padding值final int left = parent.getPaddingLeft();        //分割线右边距final int right = parent.getWidth() - parent.getPaddingRight();        final int childCount = parent.getChildCount();        //遍历所有item view,为它们的下方绘制分割线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);        }    }    @Overridepublic void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {        if(mOrientation == VERTICAL_LIST){            //设置偏移的高度是mDivider.getIntrinsicHeight,该高度正是分割线的高度            outRect.set(0,0,0,mDivider.getIntrinsicHeight());        }else{            outRect.set(0,0,mDivider.getIntrinsicWidth(),0);        }    }}

接着在MainActivity.java添加如下代码:
mRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL_LIST));
最后,我们把之前在itemlayout.xml添加的margin值去掉,那么我们看看运行结果:
 源码下载地址:
http://download.csdn.net/detail/a553181867/9518275
连接张鸿 洋 http://blog.csdn.net/lmj623565791/article/details/38173061
Recyclerview动画 https://github.com/gabrielemariotti/RecyclerViewItemAnimators

0 0
原创粉丝点击