RecyclerView,ItemDecoration进阶使用!!!

先来一波美女,养眼一发

这里写图片描述

写在开头

如果你现在还在用listview,gridview,那么我只能说,太out了。recyclerview现在使用的很普遍了已经,它不仅实现了他们俩的功能,还很方便的实现了横向列表,瀑布流列表等等!不过这些功能相信大部分人都知道,今天记录的是关于通过RecyclerView的ItemDecoration实现的一些进阶功能,从易到难包括如下:


1. ItemDecoration实现padding
2. ItemDecoration实现下划线
3. ItemDecoration实现酷炫吸顶效果
4. ItemDecoration实现item的拖拽,平移等操作
(穿插还通过OnItemTouchListener封装了列表的点击和长点击事件)

demo下载:https://github.com/loveAndroidAndroid/android-study
下面讲解有限,有需要的看demo地址

前两个简单,只列举3 4 效果图(看效果就好)

这里写图片描述这里写图片描述

ItemDecoration

ItemDecoration是RecyclerView内部的一个抽象类,要实现这个抽象类自然需要实现内部的抽象方法,除了deprecated的方法只有下面三个方法:

    //可以实现类似padding的效果
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
    //可以实现类似绘制背景的效果,内容在上面
    public void onDraw(Canvas c, RecyclerView parent, State state) 
    //可以绘制在内容的上面,覆盖内容
    public void onDrawOver(Canvas c, RecyclerView parent, State state)  

声明下:ItemDecoration的使用必须在setAdapter前,通过recyclerView.addItemDecoration()方法设置

下面讲解ItemDecoration实现的功能

1. ItemDecoration实现padding

    public class PaddingDecoration extends RecyclerView.ItemDecoration{

    private int padding;

    public PaddingDecoration(Context context) {
        //即你要设置的分割线的宽度 --这里设为10dp
        padding = context.getResources().getDimensionPixelSize(R.dimen.padding);
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        //outRect就是你那个item条目的矩形
        outRect.left = padding;  //相当于 设置 left padding
        outRect.top = padding;   //相当于 设置 top padding
        outRect.right = padding; //相当于 设置 right padding
        outRect.bottom = padding;  //相当于 设置 bottom padding
    }
}

2. ItemDecoration实现下划线

    public class DeviderDecoration extends RecyclerView.ItemDecoration {

    private int deviderHeight;
    private Paint dividerPaint;

    public DeviderDecoration(Context context) {
        //设置画笔
        dividerPaint = new Paint();
        //设置分割线颜色
        dividerPaint.setColor(context.getResources().getColor(R.color.colorAccent));
        //设置分割线宽度
        deviderHeight = context.getResources().getDimensionPixelSize(R.dimen.divider_height);
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        //改变宽度
        outRect.bottom = deviderHeight;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        //得到列表所有的条目
        int childCount = parent.getChildCount();
        //得到条目的宽和高
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();

        for (int i = 0; i < childCount - 1; i++) {
            View view = parent.getChildAt(i);
            //计算每一个条目的顶点和底部 float值
            float top = view.getBottom();
            float bottom = view.getBottom() + deviderHeight;
            //重新绘制
            c.drawRect(left, top, right, bottom, dividerPaint);
        }
    }
}

3. ItemDecoration实现酷炫吸顶效果

    public class SectionDecoration extends RecyclerView.ItemDecoration {

    private List<String> dataList;
    private DecorationCallback callback;
    private TextPaint textPaint;
    private Paint paint;
    private int topGap;
    private int alignBottom;
    private Paint.FontMetrics fontMetrics;


    public SectionDecoration(List<String> dataList, Context context, DecorationCallback decorationCallback) {
        Resources res = context.getResources();
        this.dataList = dataList;
        this.callback = decorationCallback;
        //设置悬浮栏的画笔---paint
        paint = new Paint();
        paint.setColor(res.getColor(R.color.colorGray));

        //设置悬浮栏中文本的画笔
        textPaint = new TextPaint();
        textPaint.setAntiAlias(true);
        textPaint.setTextSize(DensityUtil.dip2px(context, 14));
        textPaint.setColor(Color.DKGRAY);
        textPaint.setTextAlign(Paint.Align.LEFT);
        fontMetrics = new Paint.FontMetrics();

        //决定悬浮栏的高度等
        topGap = res.getDimensionPixelSize(R.dimen.sectioned_top);
        //决定文本的显示位置等
        alignBottom = res.getDimensionPixelSize(R.dimen.sectioned_alignBottom);
    }

    //图1:代表了getItemOffsets(),可以实现类似padding的效果
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        int pos = parent.getChildAdapterPosition(view);

        String groupId = callback.getGroupId(pos);
        if (groupId.equals("-1")) return;
        //只有是同一组的第一个才显示悬浮栏
        if (pos == 0 || isFirstInGroup(pos)) {
            outRect.top = topGap;
            if (dataList.get(pos) == "") {
                outRect.top = 0;
            }
        } else {
            outRect.top = 0;
        }
    }

    //图2:代表了onDraw(),可以实现类似绘制背景的效果,内容在上面
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View view = parent.getChildAt(i);
            int position = parent.getChildAdapterPosition(view);
            String groupId = callback.getGroupId(position);
            if (groupId.equals("-1")) return;
            String textLine = callback.getGroupFirstLine(position).toUpperCase();
            if (textLine == "") {
                float top = view.getTop();
                float bottom = view.getTop();
                c.drawRect(left, top, right, bottom, paint);
                return;
            } else {
                if (position == 0 || isFirstInGroup(position)) {
                    float top = view.getTop() - topGap;
                    float bottom = view.getTop();
                    //绘制悬浮栏
                    c.drawRect(left, top, right, bottom, paint);
                    //绘制文本
                    c.drawText(textLine, left, bottom, textPaint);
                }
            }
        }
    }

    //图3:代表了onDrawOver(),可以绘制在内容的上面,覆盖内容
    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(c, parent, state);

        int itemCount = state.getItemCount();
        int childCount = parent.getChildCount();
        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();
        float lineHeight = textPaint.getTextSize() + fontMetrics.descent;

        String preGroupId = "";
        String groupId = "-1";
        for (int i = 0; i < childCount; i++) {
            View view = parent.getChildAt(i);
            int position = parent.getChildAdapterPosition(view);

            preGroupId = groupId;
            groupId = callback.getGroupId(position);
            if (groupId.equals("-1") || groupId.equals(preGroupId)) continue;

            String textLine = callback.getGroupFirstLine(position).toUpperCase();
            if (TextUtils.isEmpty(textLine)) continue;

            int viewBottom = view.getBottom();
            float textY = Math.max(topGap, view.getTop());
            //下一个和当前不一样移动当前
            if (position + 1 < itemCount) {
                String nextGroupId = callback.getGroupId(position + 1);
                //组内最后一个view进入了header
                if (nextGroupId != groupId && viewBottom < textY) {
                    textY = viewBottom;
                }
            }
            //textY - topGap决定了悬浮栏绘制的高度和位置
            c.drawRect(left, textY - topGap, right, textY, paint);
            //left+2*alignBottom 决定了文本往左偏移的多少(加-->向左移)
            //textY-alignBottom  决定了文本往右偏移的多少  (减-->向上移)
//            c.drawText(textLine, left + 2 * alignBottom, textY - alignBottom, textPaint);
            c.drawText(textLine, left, textY - alignBottom, textPaint);
        }
    }


    /**
     * 判断是不是组中的第一个位置
     *
     * @param pos
     * @return
     */
    private boolean isFirstInGroup(int pos) {
        if (pos == 0) {
            return true;
        } else {
            // 因为是根据 字符串内容的相同与否 来判断是不是同意组的,所以此处的标记id 要是String类型
            // 如果你只是做联系人列表,悬浮框里显示的只是一个字母,则标记id直接用 int 类型就行了
            String prevGroupId = callback.getGroupId(pos - 1);
            String groupId = callback.getGroupId(pos);
            //判断前一个字符串 与 当前字符串 是否相同
            if (prevGroupId.equals(groupId)) {
                return false;
            } else {
                return true;
            }
        }
    }

    //定义一个借口方便外界的调用
    public interface DecorationCallback {
        String getGroupId(int position);

        String getGroupFirstLine(int position);
    }
}

使用

    textRecycler.addItemDecoration(new SectionDecoration(list, this, new SectionDecoration.DecorationCallback() {
            @Override
            public String getGroupId(int position) {
                if(NameBean.get(position)!=null) {
                    return NameBean.get(position);
                }
                return "-1";
            }

            @Override
            public String getGroupFirstLine(int position) {
                if(NameBean.get(position)!=null) {
                    return NameBean.get(position);
                }
                return "";
            }
        }));

4. ItemDecoration实现item的拖拽,平移等操作

(穿插还通过OnItemTouchListener封装了列表的点击和长点击事件)

item的点击事件和长点击事件

该功能主要通过addOnItemTouchListener() 来实现。

    textRecycler.addOnItemTouchListener(new OnRecyclerItemClickListener(textRecycler) {
            @Override
            public void onItemClick(RecyclerView.ViewHolder viewHolder) {
                ToastUtils.showToast(viewHolder.getAdapterPosition()+1+"");
            }

            @Override
            public void onLongClick(RecyclerView.ViewHolder viewHolder) {
                ToastUtils.showToast(viewHolder.getAdapterPosition()+1+"");
                //当 item 被长按且不是第一个时,开始拖曳这个 item
                if (viewHolder.getLayoutPosition() != 0) {
                    itemTouchHelper.startDrag(viewHolder);
                }
            }
        });

其中 OnRecyclerItemClickListener 是自定义的一个触摸监听器,代码如下:

    public abstract class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener {

    //手势探测器
    private GestureDetectorCompat mGestureDetectorCompat;
    private RecyclerView mRecyclerView;

    public OnRecyclerItemClickListener(RecyclerView mRecyclerView) {
        this.mRecyclerView = mRecyclerView;
        mGestureDetectorCompat = new GestureDetectorCompat(mRecyclerView.getContext(),
                new ItemTouchHelperGestureListener(mRecyclerView,this));
    }

    //第一个是拦截触摸事件的,第二个是处理触摸事件的,第三个是处理触摸冲突的。第三个这里我们用不到
    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        mGestureDetectorCompat.onTouchEvent(e);
        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
        mGestureDetectorCompat.onTouchEvent(e);
    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    }
    //提供单机 长按的方法
    public abstract void onItemClick(RecyclerView.ViewHolder viewHolder);
    public abstract void onLongClick(RecyclerView.ViewHolder viewHolder);
}

GestureDetectorCompat 中传入了一个 ItemTouchHelperGestureListener,代码如下:

    public class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {

    private RecyclerView mRecyclerView;
    private OnRecyclerItemClickListener onRecyclerItemClickListener;

    public ItemTouchHelperGestureListener(RecyclerView mRecyclerView, OnRecyclerItemClickListener onRecyclerItemClickListener) {
        this.mRecyclerView = mRecyclerView;
        this.onRecyclerItemClickListener = onRecyclerItemClickListener;
    }

    //一次单独的轻触抬起手指操作,就是普通的点击事件
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        //这个ChildHelper类,它会协助获取RecyclerView中的childVIew。 可点击看源码
        View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
        if (childViewUnder != null) {
            RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder);
            onRecyclerItemClickListener.onItemClick(childViewHolder);
        }
        return true;
    }

    //长按屏幕超过一定时长,就会触发,就是长按事件
    @Override
    public void onLongPress(MotionEvent e) {
        View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
        if (childViewUnder != null) {
            RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder);
            onRecyclerItemClickListener.onLongClick(childViewHolder);
        }
    }

其实通过一个手势探测器 GestureDetectorCompat 来探测屏幕事件,然后通过手势监听器 SimpleOnGestureListener 来识别手势事件的种类,然后调用我们设置的对应的回调方法。
通过findChildViewUnder()可以知道我们点击的是哪个item,可看源码

     public View findChildViewUnder(float x, float y) {
        final int count = mChildHelper.getChildCount();
        for (int i = count - 1; i >= 0; i--) {
            final View child = mChildHelper.getChildAt(i);
            final float translationX = ViewCompat.getTranslationX(child);
            final float translationY = ViewCompat.getTranslationY(child);
            if (x >= child.getLeft() + translationX &&
                    x <= child.getRight() + translationX &&
                    y >= child.getTop() + translationY &&
                    y <= child.getBottom() + translationY) {
                return child;
            }
        }
        return null;
    }

同时我们调用 RecyclerView 的另一个方法 getChildViewHolder(),可以获得该 item 的 ViewHolder,最后再回调我们定义的虚方法 onItemClick() 就ok了,这样我们就可以在外部实现该方法来获得 item 的点击事件了。

ItemDecoration实现item的拖拽,平移等操作

ItemTouchHelper 一个帮助开发人员处理拖拽和滑动删除的实现类,它能够让你非常容易实现侧滑删除、拖拽的功能。

    /**
 * Created by wen on 2017/8/8.
 * ItemTouchHelper 一个帮助开发人员处理拖拽和滑动删除的实现类,它能够让你非常容易实现侧滑删除、拖拽的功能。
 */

public class MyItemTouchHelper extends ItemTouchHelper.Callback {

    RecyclerAdapter adapter;

    public MyItemTouchHelper(RecyclerAdapter adapter) {
        this.adapter = adapter;
    }

    //通过返回值来设置是否处理某次拖曳或者滑动事件
    //dragFlags 是拖拽标志,
    //swipeFlags 是滑动标志,
    //swipeFlags 都设置为0,暂时不考虑滑动相关操作。
    //getMovementFlags() 用于设置是否处理拖拽事件和滑动事件,以及拖拽和滑动操作的方向,有以下两种情况:
    //如果是列表类型的 RecyclerView,拖拽只有 UP、DOWN 两个方向
    //如果是网格类型的则有 UP、DOWN、LEFT、RIGHT 四个方向
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
            int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
            int swipeFlags = 0;
            return makeMovementFlags(dragFlags, swipeFlags);
        } else {
            int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
            //注意:和拖曳的区别就是在这里
            int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
            return makeMovementFlags(dragFlags, swipeFlags);
        }

    }

    //当长按并进入拖曳状态时,拖曳的过程中不断的回调此方法
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        //拖动的 item 的下标
        int fromPosition = viewHolder.getAdapterPosition();
        //目标 item 的下标,目标 item 就是当拖曳过程中,不断和拖动的 item 做位置交换的条目。
        int toPosition = target.getAdapterPosition();
        //对应某些需求,某一个item不能拖拽
        if (toPosition == 0) {
            return false;
        }
        if (fromPosition < toPosition) {
            for (int i = fromPosition; i < toPosition; i++) {
                //通过你传入的adapter得到你的数据 并进行交换
                Collections.swap(((RecyclerAdapter) adapter).getDataList(), i, i + 1);
            }
        } else {
            for (int i = fromPosition; i > toPosition; i--) {
                Collections.swap(((RecyclerAdapter) adapter).getDataList(), i, i - 1);
            }
        }
        adapter.notifyItemMoved(fromPosition, toPosition);
        return true;
    }

    //滑动删除的回调
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        int adapterPosition = viewHolder.getAdapterPosition();
        adapter.notifyItemRemoved(adapterPosition);
        ((RecyclerAdapter)adapter).getDataList().remove(adapterPosition);
    }

    //当长按 item 刚开始拖曳的时候调用
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            //给被拖曳的 item 设置一个深颜色背景
            viewHolder.itemView.setBackgroundColor(Color.LTGRAY);
        }
        super.onSelectedChanged(viewHolder, actionState);
    }

    //当完成拖曳手指松开的时候调用
    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        //给已经完成拖曳的 item 恢复开始的背景。
        // 这里我们设置的颜色尽量和你 item 在 xml 中设置的颜色保持一致
        viewHolder.itemView.setBackgroundColor(Color.WHITE);
    }

    //返回 false 让它控制所有的 item 都不能拖曳。
    @Override
    public boolean isLongPressDragEnabled() {
        return false;
    }
}

使用

    itemTouchHelper = new ItemTouchHelper(new MyItemTouchHelper(adapter));
        itemTouchHelper.attachToRecyclerView(textRecycler);

        textRecycler.addOnItemTouchListener(new OnRecyclerItemClickListener(textRecycler) {
            @Override
            public void onItemClick(RecyclerView.ViewHolder viewHolder) {
                ToastUtils.showToast(viewHolder.getAdapterPosition()+1+"");
            }

            @Override
            public void onLongClick(RecyclerView.ViewHolder viewHolder) {
                ToastUtils.showToast(viewHolder.getAdapterPosition()+1+"");
                //当 item 被长按且不是第一个时,开始拖曳这个 item(这里只是一个特殊需求)
                if (viewHolder.getLayoutPosition() != 0) {
                    itemTouchHelper.startDrag(viewHolder);
                }
            }
        });

写在最后

本文记录了recycleview的ItemDecoration的进阶使用!在此声明还有一个好玩的东西ItemAnimator可以实现列表的动画,留作提醒,下次研究!有什么写的不好,也请提出来讨论吧。

猜你喜欢

转载自blog.csdn.net/say_from_wen/article/details/77184666
今日推荐