美文网首页自定义控件
自定义Recyclerview.ItemDecoration实现

自定义Recyclerview.ItemDecoration实现

作者: Endeav0r | 来源:发表于2018-09-26 16:58 被阅读299次
先来看看最终实现的效果图:
ItemDecoration简介:

ItemDecoration是RecyclerView中的抽象内部类。Decoration翻译过来是装饰物,也就是item的装饰物,系统默认提供了DividerItemDecoration一个分割线实现类,使用方法如下:

rcy.addItemDecoration(new DividerItemDecoration(this, LinearLayout.HORIZONTAL));

ItemDecoration中有三个方法是我们必须关心的:

// 用于给item隔开距离,类似直接给item设padding。
1.public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)

// 与自定义View中的方法相同,这里用于给getItemOffsets()隔开的距离填充图形;
2.public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state)

// 这个方法可以将内容覆盖在item上,可用于制作悬停效果,item角标等
3.public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)

下面附上一张图来方便理解三个方法


ItemDecoration

首先我们假设绿色区域代表的是我们的内容,红色区域代表我们自己绘制的装饰,可以看到:

图1:代表了getItemOffsets(),可以实现类似padding的效果

图2:代表了onDraw(),可以实现类似绘制背景的效果,内容在上面

图3:代表了onDrawOver(),可以绘制在内容的上面,覆盖内容

先来看第一个方法:

参数中传给我们的outRect就是每个item的外围矩形,看到Rect就知道我们可以设置上下左右了,这里我们实现的是在头部加分割线,我们就只指定top值。


getItemOffsets.png
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        outRect.top = 1;
  
}

运行效果如下:

可以看到我们的Recyclerview每个item上面就隔开了一条分割线,只是颜色是系统默认的,我们来自己指定颜色,就需要用到第二个方法onDraw(),通过canvas在隔开的距离上填充图形,我们先简单画一个红色矩形:

  public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        int childCount = parent.getChildCount();
        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();

        for (int i = 0; i < childCount; i++) {
            View view = parent.getChildAt(i);
            float top = view.getTop() - 1;
            float bottom = view.getTop();

            c.drawRect(left, top, right, bottom, mPaint);
        }
    }

运行效果如下:

下面我们来看第三个方法onDrawOver(),这个方法是在item绘制完成后调用,指定的内容绘制到item view内容之上,先简单画个图形看看效果。

public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
    mPaint.setColor(Color.BLUE);
    c.drawRect(0,0,parent.getRight(),60,mPaint);
}

运行如下:

可以看到蓝色图形是覆盖在itemView之上的,这也是我们后面实现吸顶效果的主要方法。
ItemDecoration的三个主要方法介绍完成。

下面来看看实现吸顶效果的具体实现:

/**
 * @author: Endeavor
 * @date: 2018/9/25
 */

public class TitleItemDecoration extends RecyclerView.ItemDecoration {

    private final int mTitleHeight;
    private final int mTitleTextSize;
    private final Paint mPaint;
    private final Paint mTextPaint;
    private final Rect textRect;
    private TitleDecorationCallback callback;
    private final Paint mGrayPaint;

    public TitleItemDecoration(Context context, TitleDecorationCallback callback) {
        this.callback = callback;
        mTitleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, context.getResources().getDisplayMetrics());
        mTitleTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, context.getResources().getDisplayMetrics());
        mTextPaint = new Paint();
        mTextPaint.setTextSize(mTitleTextSize);
        mTextPaint.setAntiAlias(true);
        mTextPaint.setColor(Color.WHITE);

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.RED);

        mGrayPaint = new Paint();
        mGrayPaint.setAntiAlias(true);
        mGrayPaint.setColor(Color.DKGRAY);



        textRect = new Rect();
    }

    // 这个方法用于给item隔开距离,类似直接给item设padding
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();

        if (position == 0 || isFirst(position)) {
            outRect.top = mTitleHeight;
        } else {
            outRect.top = 1;
        }
    }

    // 这个方法用于给getItemOffsets()隔开的距离填充图形,
    // 在item绘制之前时被调用,将指定的内容绘制到item view内容之下;
    @Override
    public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {

        // 获取当前屏幕可见 item 数量,而不是 RecyclerView 所有的 item 数量
        int childCount = parent.getChildCount();
        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();

        for (int i = 0; i < childCount; i++) {
            final View view = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view
                    .getLayoutParams();
            int position = params.getViewLayoutPosition();

            if (position == 0 || isFirst(position)) {

                float top = view.getTop() - mTitleHeight;
                float bottom = view.getTop();
                canvas.drawRect(left, top, right, bottom, mPaint);

                String groupName = callback.getGroupName(position);
                mTextPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
                float x = view.getPaddingLeft();
                float y = top + (mTitleHeight - textRect.height()) / 2 + textRect.height();
                canvas.drawText(callback.getGroupName(position), x, y, mTextPaint);
            } else {
                float top = view.getTop() - 1;
                float bottom = view.getTop();
                canvas.drawRect(left, top, right, bottom, mGrayPaint);
            }
        }
    }

    // 在item被绘制之后调用,将指定的内容绘制到item view内容之上
    // 这个方法可以将内容覆盖在item上,可用于制作悬停效果,角标等(这里只实现悬停效果)
    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(c, parent, state);

        int position = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
        if (position <= -1 || position >= parent.getAdapter().getItemCount() - 1) {
            // 越界检查
            return;
        }

        View firstVisibleView = parent.findViewHolderForAdapterPosition(position).itemView;

        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();
        int top = parent.getPaddingTop();
        int bottom = top + mTitleHeight;


        // 如果当前屏幕上第二个显示的item是下一组的的第一个,并且第一个被title覆盖,则开始移动上个title。
        // 原理就是不断改变title所在矩形的top与bottom的值。
        if (isFirst(position + 1) && firstVisibleView.getBottom() < mTitleHeight) {
            if (mTitleHeight <= firstVisibleView.getHeight()) {
                int d = firstVisibleView.getHeight() - mTitleHeight;
                top = firstVisibleView.getTop()+d;
            }else {
                int d = mTitleHeight - firstVisibleView.getHeight();
                top = firstVisibleView.getTop()-d;// 这里有bug,mTitleHeight过高时 滑动有问题
            }
            bottom = firstVisibleView.getBottom();
        }
        c.drawRect(left, top, right, bottom, mPaint);

        String groupName = callback.getGroupName(position);
        mTextPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
        float x = left + firstVisibleView.getPaddingLeft();
        float y = top + (mTitleHeight - textRect.height()) / 2 + textRect.height();
        c.drawText(groupName, x, y, mTextPaint);

    }

    /**
     * 判断是否是同一组的第一个item
     *
     * @param position
     * @return
     */
    private boolean isFirst(int position) {
        if (position == 0) {
            return true;
        } else {
            long prevGroupId = callback.getGroupId(position - 1);
            long groupId = callback.getGroupId(position);
            return prevGroupId != groupId;
        }
    }

    public interface TitleDecorationCallback {

        long getGroupId(int position);

        String getGroupName(int position);
    }
}
参考:
图片资源:

ItemDecoration.png原图来自小武站台

getItemOffsets.png原图来自Piasy

向作者表示感谢,如作者不许我转图,烦请联系我删除。

相关文章

网友评论

    本文标题:自定义Recyclerview.ItemDecoration实现

    本文链接:https://www.haomeiwen.com/subject/mrcjoftx.html