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

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

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

整体架构如下图:
[Android开发]从Android官方Demo谈RecyclerView的用法_第1张图片

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

  • 可以控制显示方式,包括三个内置的不觉管理器,也可以定制
  • LinearLayoutManager 以垂直或水平滚动列表方式显示项目
  • GridLayoutManager 在网格中显示项目
  • StaggeredGridLayoutManager 瀑布了流中显示项目
  • 默认情况下显示增加删除的动画,也扩RecyclerView.ItemAnimator定制
  • 默认情况无分割线,可以扩展ItemDecoration定制
    #参考资料
    官方API
    官方Training:创建列表与卡片
    深入理解 RecyclerView 系列之一:ItemDecoration
    Android RecyclerView 使用完全解析 体验艺术般的控件

官方Demo效果

官方提供了一个Demo(github地址)的运行效果是这样的:
[Android开发]从Android官方Demo谈RecyclerView的用法_第2张图片
代码比较简单,重要的内容包括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.
 *
 * 

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)}.

*/ 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 outRect 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. * *

* If this ItemDecoration does not affect the positioning of item views, it should set * all four fields of outRect (left, top, right, bottom) to zero * before returning. * *

* 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 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中的集合中
    [Android开发]从Android官方Demo谈RecyclerView的用法_第3张图片

请我喝咖啡

如果觉得写得不错,可以扫描我的微信二维码请我喝咖啡哦~

在这里插入图片描述
或者点击 打赏地址 请我喝杯茶~

你可能感兴趣的:(Android开发)