[Android开发]从Android官方Demo谈RecyclerView的用法

来源:互联网 发布:mysql w10 编辑:程序博客网 时间:2024/06/09 16:40

RecyclerView是Android5.0中出现的新控件,官方API解释就一句话:

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

整体架构如下图:
这里写图片描述

RecyclerView的灵活性体现在6个方面:

  • 可以控制显示方式,包括三个内置的不觉管理器,也可以定制
  • LinearLayoutManager 以垂直或水平滚动列表方式显示项目
  • GridLayoutManager 在网格中显示项目
  • StaggeredGridLayoutManager 瀑布了流中显示项目
  • 默认情况下显示增加删除的动画,也扩RecyclerView.ItemAnimator定制
  • 默认情况无分割线,可以扩展ItemDecoration定制

参考资料

官方API
官方Training:创建列表与卡片
深入理解 RecyclerView 系列之一:ItemDecoration
Android RecyclerView 使用完全解析 体验艺术般的控件

官方Demo效果

官方提供了一个Demo(github地址)的运行效果是这样的:
这里写图片描述
代码比较简单,重要的内容包括RecyclerView的初始化和其对应的Adapter的构造。

引申需求

设置分割线

分割线官方并没有提供默认的类型,默认也并没有分隔线。要提供分隔线必须自己实现RecyclerView.ItemDecoration。

/** * An ItemDecoration allows the application to add a special drawing and layout offset * to specific item views from the adapter's data set. This can be useful for drawing dividers * between items, highlights, visual grouping boundaries and more. * * <p>All ItemDecorations are drawn in the order they were added, before the item * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()} * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView, * RecyclerView.State)}.</p> */public static abstract class ItemDecoration {    /**     * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.     * Any content drawn by this method will be drawn before the item views are drawn,     * and will thus appear underneath the views.     *     * @param c Canvas to draw into     * @param parent RecyclerView this ItemDecoration is drawing into     * @param state The current state of RecyclerView     */    public void onDraw(Canvas c, RecyclerView parent, State state) {        onDraw(c, parent);    }    /**     * @deprecated     * Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)}     */    @Deprecated    public void onDraw(Canvas c, RecyclerView parent) {    }    /**     * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.     * Any content drawn by this method will be drawn after the item views are drawn     * and will thus appear over the views.     *     * @param c Canvas to draw into     * @param parent RecyclerView this ItemDecoration is drawing into     * @param state The current state of RecyclerView.     */    public void onDrawOver(Canvas c, RecyclerView parent, State state) {        onDrawOver(c, parent);    }    /**     * @deprecated     * Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)}     */    @Deprecated    public void onDrawOver(Canvas c, RecyclerView parent) {    }    /**     * @deprecated     * Use {@link #getItemOffsets(Rect, View, RecyclerView, State)}     */    @Deprecated    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {        outRect.set(0, 0, 0, 0);    }    /**     * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies     * the number of pixels that the item view should be inset by, similar to padding or margin.     * The default implementation sets the bounds of outRect to 0 and returns.     *     * <p>     * If this ItemDecoration does not affect the positioning of item views, it should set     * all four fields of <code>outRect</code> (left, top, right, bottom) to zero     * before returning.     *     * <p>     * If you need to access Adapter for additional data, you can call     * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the     * View.     *     * @param outRect Rect to receive the output.     * @param view    The child view to decorate     * @param parent  RecyclerView this ItemDecoration is decorating     * @param state   The current state of RecyclerView.     */    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {        getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),                parent);    }}

这里参考深入理解 RecyclerView 系列之一:ItemDecoration(http://www.tuicool.com/articles/fIbuYfI)来实现该接口。

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;/** * 参考:https://android.googlesource.com/platform/development/+/master/samples/Support7Demos/src * /com/example/android/supportv7/widget/decorator/DividerItemDecoration.java#101 */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);            android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());            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);        }    }    /**     * outRect是用来设置left、top、right、bottom的padding值的     * */    @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);        }    }}

点击事件

RecyclerView本身不提供单击和长按事件,需要自己实现。分为几个步骤。
第一步,需要在CustomAdapter中自己定义回调接口。

    /**     * 点击事件接口     * */    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;    }

第二步,在CustomAdapter中的onBindViewHolder(ViewHolder viewHolder, final int position)中调用接口函数。

    @Override    public void onBindViewHolder(ViewHolder viewHolder, final int position) {        Log.d(TAG, "Element " + position + " set.");        // Get element from your dataset at this position and replace the contents of the view        // with that element        viewHolder.getTextView().setText(mDataSet[position]);        viewHolder.setColor(position);        if (onItemClickListener!=null){            //可以获得每个item的包装类itemView            viewHolder.itemView.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    onItemClickListener.onItemClick(v,position);                }            });            viewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {                @Override                public boolean onLongClick(View v) {                    onItemClickListener.onItemLongClick(v,position);                    return true;                }            });        }    }

第三步,在CustomAdapter初始化的地方传入该接口实例。

 mAdapter.setOnItemClickListener(new CustomAdapter.OnItemClickListener() {            @Override            public void onItemClick(View view, int position) {               ××××单击事件××××××            }            @Override            public void onItemLongClick(View view, int position) {              ××××长按事件××××××            }        });

多选模式

多选模式可以采用ActionMode来进行UI设计,本质上是通过长按进入ActionMode模式,可以点击按钮取消多选模式。
这里配合上面的点击事件,仅仅模拟了多选的操作,而没有添加ActionMode。

mAdapter.setOnItemClickListener(new CustomAdapter.OnItemClickListener() {    @Override    public void onItemClick(View view, int position) {        if (isOnLonCliked){            addOrRemove(position);            Log.i(TAG, position+"OnLonCliked");        }else {            Toast.makeText(getActivity(),position+" clicked",Toast.LENGTH_LONG).show();        }    }    @Override    public void onItemLongClick(View view, int position) {        isOnLonCliked=true;        Log.i(TAG, position+"OnLonCliked");    }});

其中,addOrRemove(int position)多选的逻辑。

** * 模拟多选情况 * */private void  addOrRemove(int position){    if(mAdapter.positionSet.contains(position)){        mAdapter.positionSet.remove(position);    }else {        mAdapter.positionSet.add(position);    }    if (mAdapter.positionSet.size()==0){        isOnLonCliked=false;    }    mAdapter.notifyDataSetChanged();}

CustomAdapter中有一个集合在记录多选模式下已经点选的位置,点击时判断该集合是否包含了该位置,如果已经包含,就取消颜色,否则改变颜色。

/** * Provide views to RecyclerView with data from mDataSet. */     ××××××省略无关代码××××××××××××××    public static Set<Integer> positionSet = new HashSet<>();    // BEGIN_INCLUDE(recyclerViewSampleViewHolder)    /**     * Provide a reference to the type of views that you are using (custom ViewHolder)     */    public static class ViewHolder extends RecyclerView.ViewHolder {        public final TextView textView;        public ViewHolder(View v) {            super(v);            // Define click listener for the ViewHolder's View.            v.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    Log.d(TAG, "Element " + getAdapterPosition() + " clicked.");                }            });            textView = (TextView) v.findViewById(R.id.textView);            Log.d(TAG, "ViewHolder ");        }        public TextView getTextView() {            return textView;        }        public void setColor(int position){            if (positionSet.contains(position)){                textView.setTextColor(Color.GREEN);            }else {                textView.setTextColor(Color.BLACK);            }        }    }××××××省略无关代码××××××××××××××

最后达成的效果是

  • 长按RecyclerView中的某一项,会进入到多选模式

  • 如果点击的项已经在CustomAdapter中的集合中,则去除这些项,如果集合清空,则退出多选模式

  • 多选模式下再点击某些项,这些项会记录到CustomAdapter中的集合中
    这里写图片描述

GitHub地址

本文的工程地址为:
https://github.com/caoyanfeng/GoogleSamples
如果觉得写得不错,请给我的GitHub点star哦~

0 0
原创粉丝点击