妙用布局代码xml进行RecyclerView.ItemDecoration的绘制

妙用布局代码xml进行RecyclerView.ItemDecoration的绘制

  • 小伙伴是不是经常用RecyclerView.ItemDecoration来做分割线,实际上ItemDecoration除了做分割线,也可以做其他很多东西,比如微信聊天页里的时间或者系统消息显示
  • 如果我们的布局比较复杂,直接用ItemDecoration的canvas去绘制会非常麻烦,后期要调整UI也是很麻烦,有没有更简单的方法呢

验证版

  • 实际上我们可以利用LayoutInflater得到xml布局里的view,再利用View.getDrawingCache()方法获得bitmap,最后canvas绘制这个bitmap就可以了
  • 但比较遗憾的是我们看到View.getDrawingCache()已经被声明过时了,而且View的内容如果是变化的,那么也要想办法解决实时更新的问题,还有Bitmap的管理也是个问题,否则内存可能就会暴涨或抖动
  • 对于View.getDrawingCache()已经过时的问题,其实我们可以自己创建bitmap并绘制,这并不复杂也不难实现
  • view的内容要实时更新的话,其实也就是重新测量并布局的过程,这个也并不复杂
  • 至于Bitmap大内存的问题,可以考虑使用缓存复用的思路来解决
  • 上面的问题都有一定解决思路后,经过试验,确实可行,代码后面提供,可供参考
  • 以后想要绘制复杂的ItemDecoration,那么只要搞定xml就行了
  • 比如想要在列表里绘制时间显示,那么我们可以按照自己的要求写好布局xml,参考如下,后期UI上有任何变动和微调,就是简单的改改xml的问题了,完全不用动到canvas绘制那块的代码
  • item_time_bar.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_vertical"
    android:orientation="horizontal"
    android:paddingTop="12dp"
    tools:background="@color/white">

    <View
        android:layout_width="0dp"
        android:layout_height="0.33dp"
        android:layout_weight="1"
        android:background="@color/black_20" />

    <TextView
        android:id="@+id/time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12sp"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:textColor="@color/black_40"
        tools:text="2020/11/11 - 2020/11/20" />

    <View
        android:layout_width="0dp"
        android:layout_height="0.33dp"
        android:layout_weight="1"
        android:background="@color/black_20" />
</LinearLayout>
  • 最终为了通用一点,我把公共的进行了抽取,用户使用的时候只要继承它就能很方便的使用了
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.TextPaint;
import android.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;

/**
 * 可以用xml布局实现ItemDecoration的绘制,这对于复杂布局或以后需要调整的布局或布局内容变动频繁的非常有用
 * 适用于线性列表的ItemDecoration(例如聊天里的时间条显示)
 * 此类原理是采用LayoutInflate将xml解析为View对象,之后利用Bitmap进行绘制
 * 此类由于要经过测量布局,创建Bitmap等操作,性能较原生canvas绘制稍微有所下降,因此只适合较为复杂的布局或布局经常调整变化的情况
 */
public abstract class BaseLayoutItemDecoration extends RecyclerView.ItemDecoration {
    //    Map<String, Bitmap> mBitmapCache = new HashMap<>();

    //重写sizeOf,并返回以kB为单位的缓存对象的大小
    LruCache<String, Bitmap> mBitmapCache;

    private View mItemView;//布局的view对象

    private int mItemHeight;//布局的高度

    private int mParentRestMeasureWidth;//recyclerView的剩余宽度
    private int mParentRestMeasureHeight;//recyclerView的剩余高度
    private Paint mDefaultPaint;

    public BaseLayoutItemDecoration() {
        initDefaultPaint();
        initBitmapCache();
    }

    /**
     * 用户需要在此返回想要绘制的布局资源id
     *
     * @return
     */
    public abstract int getItemDecorationLayoutResId();

    /**
     * 用户根据需要在此对布局进行初始化(即用户根据需要在这里进行findViewById)
     *
     * @param mItemView
     */
    public abstract void initItemDecorationLayoutViews(@NonNull View mItemView);

    @CallSuper
    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        if (mItemView == null) {
            setCurrentItemDecorationRestMeasureWidth(getRestMeasureWidth(parent));
            setCurrentItemDecorationRestMeasureHeight(getRestMeasureHeight(parent));
            mItemView = LayoutInflater.from(parent.getContext()).inflate(getItemDecorationLayoutResId(), parent, false);
            initItemDecorationLayoutViews(mItemView);
        }
    }

    //-------------------------开放如下方法---------------------------------------------

    /**
     * 获得指定view的剩余宽度(即可供子View的最大宽度)
     *
     * @param view 此view必须是已经测量过的,否则返回值并不准确
     * @return
     */
    public int getRestMeasureWidth(@NonNull View view) {
        return view.getMeasuredWidth() - view.getPaddingEnd() - view.getPaddingStart();
    }

    /**
     * 获得指定view的剩余高度(即可供子View的最大高度)
     *
     * @param view 此view必须是已经测量过的,否则返回值并不准确
     * @return
     */
    public int getRestMeasureHeight(@NonNull View view) {
        return view.getMeasuredHeight() - view.getPaddingTop() - view.getPaddingBottom();
    }

    /**
     * 如果recyclerview的itemview宽度不固定或者与itemview宽度不一致,则需要在ondraw中调用此进行更新
     * 实例:
     * public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
     * int count = parent.getChildCount();
     * for (int i = 0; i < count; i++) {
     * View child = parent.getChildAt(i);
     * setRestMeasureWidth(getRestMeasureWidth(child));
     * setRestMeasureHeight(getRestMeasureHeight(child));
     * // ...
     * }
     * }
     *
     * @param width
     */
    public void setCurrentItemDecorationRestMeasureWidth(int width) {
        mParentRestMeasureWidth = width;
    }

    /**
     * 同{@link BaseLayoutItemDecoration#setCurrentItemDecorationRestMeasureWidth}
     *
     * @param height
     */
    public void setCurrentItemDecorationRestMeasureHeight(int height) {
        mParentRestMeasureHeight = height;
    }

    /**
     * 获得ItemDecoration布局view对象
     *
     * @return
     */
    public View getItemDecorationView() {
        return mItemView;
    }

    /**
     * 获得默认的画笔
     *
     * @return
     */
    public Paint getDefaultPaint() {
        return mDefaultPaint;
    }

    /**
     * 获得TimeDecoration布局的高度
     * 注意此值有可能是变化的,因此在使用之前必须先调用notifyItemLayoutContentChange
     *
     * @return
     */
    public int getItemDecorationHeight() {
        return mItemHeight;
    }

    /**
     * 获得TimeDecoration布局的高度
     * 内部自动调用notifyItemLayoutContentChange
     * 注意notifyItemLayoutContentChange可能会触发测量,因此不能同时多次调用getCurrentItemHeight,否则可能会导致性能稍微下降
     *
     * @return
     */
    public int getCurrentItemDecorationHeight() {
        notifyItemDecorationChanged();
        return mItemHeight;
    }

    /**
     * 用户每次更新layout的内容都必须调用此方法
     */
    public void notifyItemDecorationChanged() {
        measureAndLayout();
    }

    /**
     * 对view进行测量
     *
     * @param view
     * @param restWidth  view的父剩余宽度
     * @param restHeight view的父剩余高度
     */
    public void measure(@NonNull View view, int restWidth, int restHeight) {
        int widthMeasureMode = View.MeasureSpec.EXACTLY;
        int heightMeasureMode = View.MeasureSpec.EXACTLY;
        ViewGroup.LayoutParams lp = view.getLayoutParams();
        if (lp == null) {
            view.measure(View.MeasureSpec.makeMeasureSpec(restWidth, widthMeasureMode),
                    View.MeasureSpec.makeMeasureSpec(restHeight, heightMeasureMode));
            return;
        }
        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {//WRAP_CONTENT需要AT_MOST的测量模式
            widthMeasureMode = View.MeasureSpec.AT_MOST;
        }
        if (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {//WRAP_CONTENT需要AT_MOST的测量模式
            heightMeasureMode = View.MeasureSpec.AT_MOST;
        }
        view.measure(View.MeasureSpec.makeMeasureSpec(restWidth, widthMeasureMode),
                View.MeasureSpec.makeMeasureSpec(restHeight, heightMeasureMode));
    }

    /**
     * 获得指定view的bitmap
     * 未使用缓存
     *
     * @param v
     * @return
     */
    @Deprecated//未复用缓存会导致bitmap的频繁创建与销毁,导致频繁gc,性能有所下降,推荐使用getBitmapWithCache
    @Nullable
    public Bitmap getBitmap(@NonNull View v) {
        int width = v.getWidth();
        int height = v.getHeight();
        if (width <= 0 || height <= 0) {
            return null;
        }
        Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(b);
        v.draw(c);
        return b;
    }

    /**
     * 获得指定view的bitmap
     * 会复用缓存
     *
     * @param v
     * @param cacheIndex 用于标记缓存(注意使用不当可能导致缓存穿透)
     * @return
     */
    @Nullable
    public Bitmap getBitmapWithCache(@NonNull View v, int cacheIndex) {
        int width = v.getWidth();
        int height = v.getHeight();
        if (width <= 0 || height <= 0) {
            return null;
        }
        String key = getBitmapCacheKey(cacheIndex, v.getWidth(), v.getHeight());
        Bitmap cache = mBitmapCache.get(key);
        if (cache == null || cache.isRecycled()) {
            cache = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
            mBitmapCache.put(key, cache);
//            Log.i("123456", "没有命中缓存:" + key);
        }

        Canvas c = new Canvas(cache);
        cache.eraseColor(Color.TRANSPARENT);//擦除原有信息
        v.draw(c);

        return cache;
    }

    /**
     * 绘制TimeDecoration布局
     *
     * @param c          绘制的画布
     * @param paint      用户指定绘制的画笔,如用户无特殊要求,可直接使用{@link BaseLayoutItemDecoration#getDefaultPaint()}
     * @param left       绘制的起始点坐标
     * @param top        绘制的起始点坐标
     * @param cacheIndex 用于标记缓存(注意使用不当可能导致缓存穿透)
     */
    public void drawLayoutItemDecorationWithCache(@NonNull Canvas c, @NonNull Paint paint, float left, float top, int cacheIndex) {
        Bitmap bitmap = getBitmapWithCache(mItemView, cacheIndex);
        if (bitmap != null) {
            c.drawBitmap(bitmap, left, top, paint);
        }

    }

    /**
     * 绘制TimeDecoration布局(不使用缓存)
     *
     * @param c
     * @param paint
     * @param left
     * @param top
     */
    @Deprecated//未复用缓存会导致bitmap的频繁创建与销毁,导致频繁gc,性能有所下降,推荐使用drawLayoutItemDecorationWithCache
    public void drawLayoutItemDecorationWithoutCache(@NonNull Canvas c, @NonNull Paint paint, float left, float top) {
        Bitmap bitmap = getBitmap(mItemView);
        if (bitmap != null) {
            c.drawBitmap(bitmap, left, top, paint);
            bitmap.recycle();
        }
    }

    //-------------------------------------------私有方法--------------------------------------
    private void initDefaultPaint() {
        mDefaultPaint = new TextPaint();
        mDefaultPaint.setAntiAlias(true);//抗锯齿
        mDefaultPaint.setDither(true);
    }

    private String getBitmapCacheKey(int cacheIndex, int width, int height) {
        return cacheIndex + "_" + width + "_" + height;
    }

    /**
     * 对TimeDecoration布局进行测量和布局
     * 此步操作后宽高信息才是准确的
     */
    private void measureAndLayout() {
        measure(mItemView, mParentRestMeasureWidth, mParentRestMeasureHeight);
        mItemHeight = mItemView.getMeasuredHeight();
        mItemView.layout(0, 0, mItemView.getMeasuredWidth(), mItemView.getMeasuredHeight());
    }

    private void initBitmapCache() {
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);//kB为单位
        //设置LruCache的缓存大小
        int cacheSize = maxMemory / 16;
        mBitmapCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                // 必须重写此方法来衡量每张图片的大小,默认返回图片数量。
                return value.getByteCount() / 1024;
            }

            //当LruCache的内存容量满的时候会调用,将oldValue的元素移除出来腾出空间给新的元素加入
            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                if (oldValue != null && !oldValue.isRecycled()) {
                    oldValue.recycle();
                }
            }
        };
    }
}

优化版

  • 上面的方案由于引入了bitmap,始终是个麻烦,占用内存比较大
  • 后面决定弃用bitmap方案,改用View.draw(Canvas c)这种方案,这个方案主要的难点就是canvas的位置偏移问题,解决掉这个难点,画面才能绘制在正确的地方
  • 我们只要计算出要绘制的区域的左上角坐标,然后利用Canvas.translate即可位移到指定坐标,再调用View.draw方法即可
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.CallSuper;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

/**
 * 可以用xml布局实现ItemDecoration的绘制,这对于复杂布局或以后需要调整的布局或布局内容变动频繁的非常有用
 * 内部提供了按顺序计算绘制区域的方法
 */
public abstract class BaseLayoutOrderItemDecoration extends RecyclerView.ItemDecoration {

    public View mItemView;//布局的view对象

    private int mItemHeight;//布局的高度
    private int mItemWidth;//布局的宽度

    private int mParentRestMeasureWidth;//父view剩余宽度,用于测量
    private int mParentRestMeasureHeight;//父view的剩余高度,用于测量

    public BaseLayoutOrderItemDecoration() {
    }

    /**
     * 用户需要在此返回想要绘制的布局资源id
     * 注意,此布局的根布局的margin是无效的
     *
     * @return
     */
    @LayoutRes
    public abstract int getItemDecorationLayoutResId();

    /**
     * 用户根据自身需要在此处对布局进行初始化(即用户根据需要在这里进行findViewById)
     *
     * @param itemView 即getItemDecorationLayoutResId所对应的view对象
     */
    public abstract void initItemDecorationLayoutViews(@NonNull View itemView);

    /**
     * 注意不能在此处设置view的内容,这是由于getCurrentOrderDecorationDistance会调用此方法,因此在此处设置view的内容会覆盖用户实际上想要的内容
     *
     * @param outRect
     * @param view
     * @param parent
     * @param state
     */
    @CallSuper
    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        if (mItemView == null) {
            mItemView = LayoutInflater.from(parent.getContext()).inflate(getItemDecorationLayoutResId(), parent, false);
            initItemDecorationLayoutViews(mItemView);
            setCurrentItemDecorationRestMeasureWidth(getRestMeasureWidth(parent));
            setCurrentItemDecorationRestMeasureHeight(getRestMeasureHeight(parent));
        }
    }

    //-------------------------开放如下方法---------------------------------------------

    /**
     * 获得整个child装饰view的左上角坐标和右下角坐标(是多个itemdecoration合起来的区域)
     *
     * @param layoutManager
     * @param child
     * @return
     */
    public Rect getDecoratedBoundsWithMargins(RecyclerView.LayoutManager layoutManager, View child) {
        Rect rect = new Rect();
        layoutManager.getDecoratedBoundsWithMargins(child, rect);
        return rect;
    }


    /**
     * 获得当前ItemDecoration及其之前的ItemDecoration作用后的绘制区域坐标,与RecyclerView.LayoutManager.getDecoratedBoundsWithMargins相类似
     * 只不过它只包含自己及在它之前添加的ItemDecoration,例如recyclerview共添加了3个ItemDecoration,假如当前ItemDecoration是第二个添加的,
     * 那么getCurrentOrderDecoratedBoundsWithMargins获得的就是包含第一个和第二个ItemDecoration的区域坐标,
     * 假如当前ItemDecoration是第三个添加的,那么getCurrentOrderDecoratedBoundsWithMargins获得的就是包含第一个,第二个和第三个ItemDecoration的区域坐标
     * <p>
     * 注意与ItemDecoration的添加顺序有关,另外切不可在getItemOffsets中调用此方法,避免死循环
     *
     * @param child
     * @param parent
     * @param state
     * @return
     */
    public Rect getCurrentOrderDecoratedBoundsWithMargins(@NonNull View child, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
        //获得child的区域坐标
        int left = child.getLeft() - lp.getMarginStart();
        int top = child.getTop() - lp.topMargin;
        int right = child.getRight() + lp.getMarginEnd();
        int bottom = child.getBottom() + lp.bottomMargin;
        Rect childPosition = new Rect(left, top, right, bottom);

        Rect distanceRect = getCurrentOrderDecorationDistance(child, parent, state);

        childPosition.left -= distanceRect.left;
        childPosition.top -= distanceRect.top;
        childPosition.right += distanceRect.right;
        childPosition.bottom += distanceRect.bottom;

        return childPosition;
    }

    /**
     * 获得当前ItemDecoration及其之前的ItemDecoration作用后与child的上下左右距离,与RecyclerView.LayoutManager.calculateItemDecorationsForChild类似
     * 注意,这与itemDecoration的添加顺序有关,所得到的结果是当前ItemDecoration及在当前ItemDecoration之前添加的ItemDecoration的综合,
     * 并不包含当前ItemDecoration之后添加的ItemDecoration
     * 注意与ItemDecoration的添加顺序有关,另外切不可在getItemOffsets中调用此方法,避免死循环
     *
     * @param child
     * @param parent
     * @param state
     * @return
     */
    public Rect getCurrentOrderDecorationDistance(@NonNull View child, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        Rect rect = new Rect();
        int itemDecorCount = parent.getItemDecorationCount();
        Rect tmp = new Rect();
        for (int i = 0; i < itemDecorCount; i++) {
            RecyclerView.ItemDecoration id = parent.getItemDecorationAt(i);
            tmp.setEmpty();
            id.getItemOffsets(tmp, child, parent, state);
            rect.left += tmp.left;//加上偏移以得到正确的绘制坐标
            rect.top += tmp.top;//加上偏移以得到正确的绘制坐标
            rect.right += tmp.right;//加上偏移以得到正确的绘制坐标
            rect.bottom += tmp.bottom;//加上偏移以得到正确的绘制坐标
            if (id == this) {//如果是自己则可以退出循环了
                break;
            }
        }
        return rect;
    }

    /**
     * 获得当前ItemDecoration及其之前的ItemDecoration作用后的绘制区域的高度(包含child的margin)
     * 注意与ItemDecoration的添加顺序有关,另外切不可在getItemOffsets中调用此方法,避免死循环
     *
     * @param child
     * @param parent
     * @param state
     * @return
     */
    public int getCurrentOrderDecoratedMeasuredHeight(@NonNull View child, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        Rect distanceRect = getCurrentOrderDecorationDistance(child, parent, state);
        ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
        return child.getMeasuredHeight() + mlp.topMargin + mlp.bottomMargin + distanceRect.top + distanceRect.bottom;
    }

    /**
     * 获得当前ItemDecoration及其之前的ItemDecoration作用后的绘制区域的宽度(包含child的margin)
     * 注意与ItemDecoration的添加顺序有关,另外切不可在getItemOffsets中调用此方法,避免死循环
     *
     * @param child
     * @param parent
     * @param state
     * @return
     */
    public int getCurrentOrderDecoratedMeasuredWidth(@NonNull View child, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        Rect distanceRect = getCurrentOrderDecorationDistance(child, parent, state);
        ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
        return child.getMeasuredWidth() + mlp.getMarginStart() + mlp.getMarginEnd() + distanceRect.left + distanceRect.right;
    }


    /**
     * 获得指定view的剩余宽度(即可供子View的最大宽度)
     *
     * @param view 此view必须是已经测量过的,否则返回值并不准确
     * @return
     */
    public int getRestMeasureWidth(@NonNull View view) {
        return view.getMeasuredWidth() - view.getPaddingEnd() - view.getPaddingStart();
    }

    /**
     * 获得指定view的剩余高度(即可供子View的最大高度)
     *
     * @param view 此view必须是已经测量过的,否则返回值并不准确
     * @return
     */
    public int getRestMeasureHeight(@NonNull View view) {
        return view.getMeasuredHeight() - view.getPaddingTop() - view.getPaddingBottom();
    }

    /**
     * 如果recyclerview的itemview宽度不固定或者与itemview宽度不一致,则需要在ondraw中调用此进行更新
     * 实例:
     * public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
     * int count = parent.getChildCount();
     * for (int i = 0; i < count; i++) {
     * View child = parent.getChildAt(i);
     * setCurrentItemDecorationRestMeasureWidth(getCurrentDecoratedMeasuredWidth(child,parent,state));
     * setCurrentItemDecorationRestMeasureHeight(getCurrentDecoratedMeasuredHeight(child,parent,state));
     * // ...
     * }
     * }
     *
     * @param width
     */
    public void setCurrentItemDecorationRestMeasureWidth(int width) {
        mParentRestMeasureWidth = width;
    }

    /**
     * 同{@link BaseLayoutOrderItemDecoration#setCurrentItemDecorationRestMeasureWidth}
     *
     * @param height
     */
    public void setCurrentItemDecorationRestMeasureHeight(int height) {
        mParentRestMeasureHeight = height;
    }

    /**
     * 获得ItemDecoration布局view对象
     *
     * @return
     */
    public View getItemDecorationView() {
        return mItemView;
    }

    /**
     * 获得ItemDecoration布局的高度
     * 内部自动调用notifyItemLayoutContentChange
     * 注意notifyItemLayoutContentChange可能会触发测量,因此不能同时多次调用 getItemDecorationHeight,否则可能会导致性能稍微下降
     *
     * @return
     */
    public int getItemDecorationHeight() {
        notifyItemDecorationChanged();
        return mItemHeight;
    }

    /**
     * 获得ItemDecoration布局的宽度
     * 内部自动调用notifyItemLayoutContentChange
     * 注意notifyItemLayoutContentChange可能会触发测量,因此不能同时多次调用 getItemDecorationWidth,否则可能会导致性能稍微下降
     *
     * @return
     */
    public int getItemDecorationWidth() {
        notifyItemDecorationChanged();
        return mItemWidth;
    }

    /**
     * 用户每次更新layout的内容都必须调用此方法
     */
    public void notifyItemDecorationChanged() {
        measureAndLayout();
    }

    /**
     * 对view进行测量
     *
     * @param view
     * @param restWidth  view的父剩余宽度
     * @param restHeight view的父剩余高度
     */
    public void measure(@NonNull View view, int restWidth, int restHeight) {
        int widthMeasureMode = View.MeasureSpec.EXACTLY;
        int heightMeasureMode = View.MeasureSpec.EXACTLY;
        ViewGroup.LayoutParams lp = view.getLayoutParams();
        if (lp == null) {
            view.measure(View.MeasureSpec.makeMeasureSpec(restWidth, widthMeasureMode),
                    View.MeasureSpec.makeMeasureSpec(restHeight, heightMeasureMode));
            return;
        }
        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {//WRAP_CONTENT需要AT_MOST的测量模式
            widthMeasureMode = View.MeasureSpec.AT_MOST;
        } else if (lp.width > 0) {
            restWidth = Math.min(lp.width, restWidth);
        }
        if (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {//WRAP_CONTENT需要AT_MOST的测量模式
            heightMeasureMode = View.MeasureSpec.AT_MOST;
        } else if (lp.height > 0) {
            restHeight = Math.min(lp.height, restHeight);
        }
        view.measure(View.MeasureSpec.makeMeasureSpec(restWidth, widthMeasureMode),
                View.MeasureSpec.makeMeasureSpec(restHeight, heightMeasureMode));
    }

    /**
     * 绘制左部或顶部的Decoration视图
     *
     * @param canvas
     * @param child
     * @param parent
     * @param state
     */
    public void drawDecorationViewOnLeftOrTop(@NonNull Canvas canvas, @NonNull View child, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        canvas.save();
        Rect rect = getCurrentOrderDecoratedBoundsWithMargins(child, parent, state);
        canvas.translate(rect.left, rect.top);
        getItemDecorationView().draw(canvas);
        canvas.restore();
    }

    /**
     * 绘制右部的Decoration视图
     *
     * @param canvas
     * @param child
     * @param parent
     * @param state
     */
    public void drawDecorationViewOnRight(@NonNull Canvas canvas, @NonNull View child, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        canvas.save();
        Rect rect = getCurrentOrderDecoratedBoundsWithMargins(child, parent, state);
        canvas.translate(rect.right - getItemDecorationWidth(), rect.top);
        getItemDecorationView().draw(canvas);
        canvas.restore();
    }

    /**
     * 绘制底部的Decoration视图
     *
     * @param canvas
     * @param child
     * @param parent
     * @param state
     */
    public void drawDecorationViewOnBottom(@NonNull Canvas canvas, @NonNull View child, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        canvas.save();
        Rect rect = getCurrentOrderDecoratedBoundsWithMargins(child, parent, state);
        canvas.translate(rect.left, rect.bottom - getItemDecorationHeight());
        getItemDecorationView().draw(canvas);
        canvas.restore();
    }

    //-------------------------------------------私有方法--------------------------------------

    private void measureAndLayout() {
        measure(mItemView, mParentRestMeasureWidth, mParentRestMeasureHeight);
        mItemHeight = mItemView.getMeasuredHeight();
        mItemWidth = mItemView.getMeasuredWidth();
        mItemView.layout(0, 0, mItemView.getMeasuredWidth(), mItemView.getMeasuredHeight());
    }
}

参考

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值