RecyclerView学习笔记

来源:互联网 发布:power map for mac 编辑:程序博客网 时间:2024/06/05 05:32

自学习android以来,其实一直都有接触到 RecyclerView,今天便总结一下关于RecyclerView的相关知识,并不是非常全面。主要从以下几个方面:

  1. RecyclerView概述
  2. RecyclerView与ListView区别
  3. RecyclerView基本使用
  4. RecyclerView item单击与长按事件
  5. RecyclerView item长按拖拽和侧滑删除

源码地址:https://github.com/Ti2Yuan/RecyclerViewDemo


1. RecyclerView概述

2014年Google IO的召开,Android L Preview版发布,对于开发者而言,它带来了性能上的改善。其中,一个全新的控件也进入开发者的视野中,并得到越来越多的使用,大有取代ListView的趋势,它就是RecyclerView。

    A flexible view for providing a limited window into a large data set.        

上面那句话是官网中对RecyclerView的描述:能在有限的窗口中显示大数据集的灵活视图。
RecyclerView是Google support-v7包下新增的控件,用来替代ListView的使用,RecyclerView标准化了ViewHolder类似于ListView中convertView用来做视图缓存。但是它却是ListView的增强版。


2.RecyclerView与ListView区别

正如官方文档所言,RecyclerView是ListView的豪华增强版。它主要包含以下几处新的特性,如ViewHolder,ItemDecorator,LayoutManager,SmoothScroller以及增加或删除item时item动画等。官方推荐我们采用RecyclerView来取代ListView。

ViewHolder
ViewHolder是用来保存视图引用的类,在ListView中,ViewHolder需要自己来定义,但只是一种推荐的使用方式,不是必须要使用的。取而代之的是ListView性能的迟缓,因为ListView每次getView的时候都会调用findViewById(int)方法。而在RecyclerView中则强调必须使用RecyclerView.ViewHolder,否则代码将不能运行。虽然这个过程实现起来稍显复杂,但是却避免了ListView不使用ViewHolder而带来的性能问题。

LayoutManager
ListView只能在垂直方向上滚动,google并没有给出ListView在水平方向上面滚动的Android API支持。但是RecyclerView相较于ListView,在滚动上可以支持多种类型列表,例如:

LinearLayoutManager,可以支持水平和竖直方向上滚动的列表。

StaggeredGridLayoutManager,可以支持交叉网格风格的列表,类似于瀑布流或者Pinterest。

GridLayoutManager,支持网格展示,可以水平或者竖直滚动,如展示图片的画廊。

ItemAnimator
列表动画是一个全新的、拥有无限可能的维度。起初的Android API中,删除或添加item时,item是无法产生动画效果的。后面随着Android的进化,Google的Chat Hasse推荐使用ViewPropertyAnimator属性动画来实现上述需求。
RecyclerView.ItemAnimator用于在RecyclerView中添加、删除或移动item时处理动画效果。同时也存在一个默认的动画效果DefaultItemAnimator。而ListView则不具备这种API。

Adapter
在ListView的Adapter中,getView方法将视图跟position绑定在一起。同时我们能通过registerDataObserver在Adapter中注册一个观察者。在RecyclerView中,我们也可以通过RecyclerView.AdapterDataObserver观察。ListView有三个Adapter的默认实现,分别是ArrayAdapter、CursorAdapter和SimpleCursorAdapter。然而,RecyclerView的Adapter则拥有除了内置的内DB游标和ArrayList的支持之外的所有功能。RecyclerView.Adapter的实现中,我们必须采取措施将数据提供给Adapter。

ItemDecoration
在ListView中我们可以通过divider和dividerHeight这些相关属性为item添加间隔符。但是在RecyclerView中可通过RecyclerView.ItemDecoration类来实现自定义间隔符。默认情况下item之间不会展示间隔符。这的确增加了开发人员的负担,如果你想要添加间隔符的话。你还可以参考官方示例中的DividerItemDecoration.java文件。

OnItemTouchListener
ListView通过AdapterView.OnItemClickListener接口来探测点击事件。而RecyclerView则可以通过RecyclerView.OnItemTouchListener接口来探测触摸事件。这种探测方式虽然增加了实现的难度,但是却给予开发人员拦截触摸事件更多的控制权限。

还有就是ListView可以添加MultiChoiceModeListener来设置选择模式,但是RecyclerView却没有。

总结:
recyclerView自定义强,可以实现复杂的布局,但过程稍显复杂。


3.RecyclerView基本使用

首先,在moudle下的build.gradle导入包

dependencies {    compile 'com.android.support:recyclerview-v7:23.2.0'}

然后在布局中

<android.support.v7.widget.RecyclerView        android:layout_width="match_parent"        android:layout_height="match_parent"        android:id="@+id/recycleView"        android:layout_margin="10dp"        android:scrollbars="vertical"/>`

然后就是为RecyclerView设置LayoutManager,Adapter,ItemAnimator和ItemDecoration。而自定义的RecyclerViewAdapter中必须继承RecyclerView.Adapter,并且实现需要重写的方法,如onCreateViewHolder,onBindViewHolder,getItemCount()等等。并且可通过getItemViewType(int position)方法返回item 类型,可用于返回headItem和FootItem类型。

recyclerView =(RecyclerView)findViewById(R.id.recycleView);mLayoutManager = new LinearLayoutManager(this);adapter = new RecyclerViewAdapter(this, list);recyclerView.setAdapter(adapter);recyclerView.setLayoutManager(mLayoutManager);recyclerView.setItemAnimator(new DefaultItemAnimator());recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {            @Override      public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {                super.onDraw(c, parent, state);            }        });
@Override    public int getItemViewType(int position) {        if (position == 0) {            return TYPE_HEAD;        } else if (position + 1 == getItemCount()) {            return TYPE_FOOT;        } else {            return TYPE_ITEM;        }    }

4. RecyclerView item单击与长按事件

一般情况下,我们可以在自定义RecyclerView.Adapter的Adapter类中为ViewHolder的rootView设置OnClickListener()来监听item的点击事件,但这种方式或多或少浪费性能,而存在一种更加高逼格的方式来实现诸如单击和长按事件,就是通过探测手势触摸的方式。一下是使用方法:

 recyclerView.addOnItemTouchListener(new OnRecyclerViewItemTouchListener(recyclerView) {            @Override            protected void onItemClick(RecyclerView.ViewHolder vh) {            }            @Override            protected void onItemLongClick(RecyclerView.ViewHolder vh) {            }        });
public abstract class OnRecyclerViewItemTouchListener implements RecyclerView.OnItemTouchListener {    private RecyclerView recyclerView;    private GestureDetectorCompat gestureDetector;    public OnRecyclerViewItemTouchListener(RecyclerView recyclerView) {        this.recyclerView = recyclerView;        gestureDetector = new GestureDetectorCompat(recyclerView.getContext(),                new ItemTouchHelperGestureListener());    }    @Override    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {        gestureDetector.onTouchEvent(e);        return false;    }    @Override    public void onTouchEvent(RecyclerView rv, MotionEvent e) {        gestureDetector.onTouchEvent(e);    }    @Override    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {    }    private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {        @Override        public boolean onDown(MotionEvent motionEvent) {            return false;        }        @Override        public void onShowPress(MotionEvent motionEvent) {        }        /**         * 单击事件         * @param motionEvent         * @return         */        @Override        public boolean onSingleTapUp(MotionEvent motionEvent) {            View child = recyclerView.findChildViewUnder(motionEvent.getX(),motionEvent.getY());            if(child != null){                RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);                onItemClick(vh);            }            return true;        }        @Override        public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {            return false;        }        /**         * 长按事件         * @param motionEvent         */        @Override        public void onLongPress(MotionEvent motionEvent) {            View child = recyclerView.findChildViewUnder(motionEvent.getX(),motionEvent.getY());            if(child != null){                RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);                onItemLongClick(vh);            }        }        @Override        public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {            return false;        }    }    protected abstract void onItemClick(RecyclerView.ViewHolder vh);    protected abstract void onItemLongClick(RecyclerView.ViewHolder vh);}

RecyclerView提供了设置触摸监听的方法,那么我们定义一个类OnRecyclerViewItemTouchListener实现OnItemTouchListener。重写3个方法OnInterceptTouchEvent,onTouchEvent,onRequestDisallowInterceptTouchEvent。其中第三个方法是处理触摸事件冲突的,前两个方法就是View的事件分发机制里面的事件拦截和事件处理的两个方法,参数里为我们提供了触摸事件的数据MotionEvent,我们要做的就是去解析坐标点和触摸规律来识别触摸手势,然后获取触摸的是哪一个item,sdk已经为我们实现了手势的识别:

GestureDetectorCompat 就是处理手势的类:手势探测器,它比GestureDetector能更好兼容低版本的api,但使用方法是一致的,我们实例化一个手势探测器:

gestureDetector = new GestureDetectorCompat(recyclerView.getContext(),                new ItemTouchHelperGestureListener());

实例化手势探测器的时候需要提供一个手势监听器:OnGestureListener,探测器识别出手势后就会回调手势监听器中对应的方法,我们就可以在回调方法中做我们想做的事情了。

sdk为我们提供了两个手势监听器:OnGestureListener,OnDoubleTapListener。

OnGestureListener主要回调各种单击事件,而OnDoubleTapListener回调各种双击事件。而我们需要处理的点击事件其实就是上面的:onSingleTapUp()。而sdk 还提供了一个外部类SimpleOnGestureListener,这个类实现了上面两个接口的所有方法,但全都是空实现,函数体里什么也没写,其中就是把上面两个接口合并一下,给出默认的空实现,这样继承SimpleOnGestureListener的时候就不用实现每一个方法了。RecyclerView已经为我们提供了findChildViewUnder(),我们可以通过这个方法获得点击的item,同时我们调用RecyclerView的另一个方法getChildViewHolder(),可以获得该item的ViewHolder,最后再回调我们定义的虚方法onItemClick()就ok了,这样我们就可以在外部实现该方法来获得item的点击事件了。


5. RecyclerView item长按拖拽和侧滑删除

可以通过ItemTouchHelper这个类来实现RecyclerView item的拖拽和滑动。

 This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView. 

这是Google官方文档描述这个类的话:这是一个支持RecyclerView滑动删除和拖拽的实用工具类。

具体用法如下:

 itemTouchHelper = new ItemTouchHelper(new ItemTouchHelpCallback(this,list) {            @Override            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {                int position = viewHolder.getLayoutPosition();                if(position != 0 && position != recyclerView.getAdapter().getItemCount() - 1) {                    adapter.notifyItemRemoved(position);                    list.remove(position-1);                }            }        });        itemTouchHelper.attachToRecyclerView(recyclerView);
public abstract class ItemTouchHelpCallback extends ItemTouchHelper.Callback {    private List<String> list;    private Context context;    public ItemTouchHelpCallback(Context context, List<String> list) {        this.list = list;        this.context = context;    }    @Override    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {        final int dragFlags, swipeFlags;        if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {            dragFlags = ItemTouchHelper.UP |                    ItemTouchHelper.DOWN |                    ItemTouchHelper.LEFT |                    ItemTouchHelper.RIGHT;            swipeFlags = 0;        } else {            dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;            swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;        }        return makeMovementFlags(dragFlags, swipeFlags);    }    //上下移动item    @Override    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {        //得到拖动viewHolder的position        int fromPosition = viewHolder.getAdapterPosition();        //得到目标viewHolder的position        int toPosition = target.getAdapterPosition();        if (fromPosition != 0 && fromPosition != recyclerView.getAdapter().getItemCount() - 1 &&                toPosition != 0 && toPosition != recyclerView.getAdapter().getItemCount() - 1)        {            if (fromPosition < toPosition) {                for (int i = fromPosition - 1; i < toPosition - 1; ++i) {                    Collections.swap(list, i, i + 1); //改变实际的数据集                }            } else {                for (int i = fromPosition - 1; i > toPosition - 1; i--) {                    Collections.swap(list, i, i - 1); //改变实际的数据集                }            }        }        recyclerView.getAdapter().notifyItemMoved(fromPosition, toPosition);        return true;    }    //当长按选中item的时候(拖拽开始的时候)调用    @Override    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {            viewHolder.itemView.setBackgroundColor(context.getResources().                    getColor(R.color.colorPrimary));        }        Vibrator vib = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);        vib.vibrate(70); //震动70毫秒        super.onSelectedChanged(viewHolder, actionState);    }    //当手指松开的时候(拖拽完成的时候)调用    @Override    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {        super.clearView(recyclerView, viewHolder);        viewHolder.itemView.setBackgroundColor(Color.WHITE);    }    @Override    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {            //滑动时改变Item的透明度            final float alpha = 1 - Math.abs(dX) / (float) viewHolder.itemView.getWidth();            viewHolder.itemView.setAlpha(alpha);            viewHolder.itemView.setTranslationX(dX);        }    }}

要使用ItemTouchHelper,你需要创建一个ItemTouchHelper.Callback。这个接口可以让你监听“move”与 “swipe”事件。自定义类继承它时需要实现一些方法:getMovementFlags,onMove,onSwiped。另外还有一些方法如onSelectedChanged,clearView可以控制这个item被选中和被释放的状态,还有onChildDraw方法可以重写item滑动的默认动画。

ItemTouchHelper可以让你轻易得到一个事件的方向。重写getMovementFlags()方法来指定可以支持的拖放和滑动的方向。使用helperItemTouchHelper.makeMovementFlags(int, int)来构造返回的flag。

@Overridepublic boolean isLongPressDragEnabled() {    return true;}

ItemTouchHelper可以用于没有滑动的拖动操作(或者反过来),你必须指明你到底要支持哪一种。要支持长按RecyclerView item进入拖动操作,你必须在isLongPressDragEnabled()方法中返回true。或者,也可以调用ItemTouchHelper.startDrag(RecyclerView.ViewHolder) 方法来开始一个拖动。这会在后面讲到。

@Overridepublic boolean isItemViewSwipeEnabled() {    return true;}

而要在view任意位置触摸事件发生时启用滑动操作,则直接在sItemViewSwipeEnabled()中返回true就可以了。或者,你也主动调用ItemTouchHelper.startSwipe(RecyclerView.ViewHolder) 来开始滑动操作。

源码地址:https://github.com/Ti2Yuan/RecyclerViewDemo

参考:
http://www.jianshu.com/p/16712681731e
http://blog.csdn.net/u014497502/article/details/50917321
http://www.cnblogs.com/littlepanpc/p/4497290.html
http://www.voidcn.com/blog/liaoinstan/article/p-5785579.html
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0630/3123.html

0 0