ItemDecoration深入解析与实战(二)—— 实际运用

栏目: IOS · Android · 发布时间: 6年前

内容简介:这是这个系列的第二篇文章,第一篇这篇文章将会实现上篇文章最后说的几个实战点,包括:看完这6点标题,应该会知道这篇文章的篇幅会稍长,不过因为是实战类型的文章,所以也不会特别枯燥。

这是这个系列的第二篇文章,第一篇 ItemDecoration深入解析与实战(一)——源码分析 是偏原理性的,而这篇是偏应用性的。没看过上一篇文章对阅读此文也基本没多大影响,不过了解原理会加深对本文Demo的理解。

这篇文章将会实现上篇文章最后说的几个实战点,包括:

  1. ( LinearLayoutManager ) 最简单的分割线实现
  2. ( LinearLayoutManager ) 自定义分割线实现
  3. ( GridLayoutManager ) 网格布局下的均分等距间距(分割线)
  4. ( StaggeredLayoutManger ) 瀑布流布局下均分等距间距(分割线)
  5. ( GridLayoutManager ) 网格布局下实现表格式边框
  6. 打造粘性头部

看完这6点标题,应该会知道这篇文章的篇幅会稍长,不过因为是实战类型的文章,所以也不会特别枯燥。

建议

1. 你需要具备怎样的前提知识

  • 阅读本文应该有一定的 RecyclerView 使用基础
  • 对 View 的基础绘制使用有了解(没有影响也不大)

2. 阅读顺序

  • 从头到尾,这有个难易顺序,读下去会比较顺畅
  • 由于文章较长,可以挑上面6点其中一个感兴趣的进行阅读,拉到下方每个点的第一部分都会有一个实现图,可以观看实际效果决定是否想要阅读

二 实战

1. ( LinearLayoutManager )最简单的分割线实现

(1) 实现效果

ItemDecoration深入解析与实战(二)—— 实际运用

(2) 具体实现

像这种单一颜色的分割线实现起来很简单,就是一行代码:

public class SimpleDividerDecoration extends RecyclerView.ItemDecoration {

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
                               @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        outRect.set(0,0,0,5);
    }
}
复制代码

这个5对应的就是 outRect.bottom ,看过这系列的上篇文章就能容易理解,这个跟在 ItemView 的布局文件中增加一个 marginBottom 是一样的效果的。不过这样默认是没有颜色的,这个分割线的颜色就取决于 RecyclerView 的背景颜色。如我们的效果图的实现:

ItemDecoration深入解析与实战(二)—— 实际运用
RecyclerView rvTest = findViewById(R.id.rv_test);
rvTest.addItemDecoration(new SimpleDividerDecoration());
复制代码

这种实现很简单,但是缺点也很突出,因为他是依赖于 RecyclerView 的背景的,而如果我们为 RecyclerView 设置一个padding,就会变成这样:

ItemDecoration深入解析与实战(二)—— 实际运用

就是说万一我们的需求是有padding,而且背景颜色要跟分割线颜色不同那就没办法了。如果要解决这一问题,就要看第2点。

2. ( LinearLayoutManager )自定义分割线实现

(1) 实现效果

ItemDecoration深入解析与实战(二)—— 实际运用

(2) 使用

由于 support 包中已经有了一个默认的实现,所以就没有自己写了,这是官方自带的 ItemDecoration 实现类,先看下怎么用:

rvTest.setLayoutManager(new LinearLayoutManager(this));      
DividerItemDecoration decoration = new DividerItemDecoration(this,DividerItemDecoration.VERTICAL);
decoration.setDrawable(getResources().getDrawable(R.drawable.divider_gradient));
rvTest.addItemDecoration(decoration);
复制代码

在示例中,我为这个Decoration添加了一个 Drawable ,这个 Drawable 就是上图的一个分割线效果,如果没有设置这个,那么将会有一个默认的灰色分割线:

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <gradient
        android:endColor="#19f5e7"
        android:startColor="#b486e2" />
    <size android:height="4dp" />
</shape>
复制代码

分割线的高度就是这个 Drawable 的高。

(3) 具体实现

用法很简单,但正所谓知其然,还要知其所以然,我们看一下这个 DividerItemDecoration 里面的具体实现是怎样的:

  • 先看 getItemOffsets 方法的具体实现

// DividerItemDecoration.java

private Drawable mDivider;

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
        RecyclerView.State state) {
    if (mDivider == null) {
        outRect.set(0, 0, 0, 0);
        return;
    }
    if (mOrientation == VERTICAL) {
        outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());  //注释1
    } else {
        outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
    }
}

复制代码

直接看注释1, mOrientation == VERTICA 的情况,在 getItemOffsets 方法中,也是用了我们第1个实战点中最简单的那种方式,只不过他的高度变成了 mDivider.getIntrinsicHeight() 而已,这个 mDivider 就是我们 setDrawable 中设置的一个 Drawable 对象,如果没有设置,那就会有一个默认的。

  • 再看 onDraw 方法

@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    if (parent.getLayoutManager() == null || mDivider == null) {
        return;
    }
    if (mOrientation == VERTICAL) {
        drawVertical(c, parent);
    } else {
        drawHorizontal(c, parent);
    }
}
复制代码

这里也分为两种情况,我们直接看 VERTICAL 下的,即 drawVertical(c, parent) 方法:

private void drawVertical(Canvas canvas, RecyclerView parent) {
    canvas.save();
    final int left;
    final int right;
    //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
    if (parent.getClipToPadding()) {
        left = parent.getPaddingLeft();
        right = parent.getWidth() - parent.getPaddingRight();
        canvas.clipRect(left, parent.getPaddingTop(), right,
                parent.getHeight() - parent.getPaddingBottom());
    } else {
        left = 0;
        right = parent.getWidth();
    }
    
    /***************分割***************/
    
    final int childCount = parent.getChildCount();
    for (int i = 0; i < childCount; i++) {
        final View child = parent.getChildAt(i);
        parent.getDecoratedBoundsWithMargins(child, mBounds);  //注释1
        final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
        final int top = bottom - mDivider.getIntrinsicHeight();
        mDivider.setBounds(left, top, right, bottom);
        mDivider.draw(canvas);
    }
    canvas.restore();
}
复制代码

我们先看注释分割线的上边,逻辑很简单,主要就是为了拿到 Child 最大可用空间的左右边界,如果我们没有设置, parent.getClipToPadding() 默认是返回 ture 的,即最大可用空间的要减去 RecyclerView 的padding,这是为了让padding不被分割线覆盖。

再看注释分割线的下边,这里遍历了所有的 Child 。先看 注释1 这句代码, parent.getDecoratedBoundsWithMargins(child, mBounds) ,这个方法有什么用呢,其实看名称就能大概猜出来,这个方法可以拿到

child边界+decoration + margin

所组成的Rect的边界值 mbounds ,即下图里面的橙色区域的外边框所对应的值。

ItemDecoration深入解析与实战(二)—— 实际运用

注意:此图不严谨,详细内容请看这系列的上一篇文章

然后便会将 mbounds 的 bottom 跟 top ,以及 上面得到的 left 跟 right 设置到 mDivider 的边界中,就获得的我们上图的 红色虚线边框 的矩形,如果我们没有为 itemView 设置 margin,那么就会得到 绿色虚线边框 的范围,再将这部分画出来,就得到了我们想要的分割线了。

3. (GridLayoutManager) 网格布局下的均分等距间距(分割线)

GridSpaceDecoration

(1) 实现效果

ItemDecoration深入解析与实战(二)—— 实际运用

效果如上图,解决了下面的常见问题:

  1. 某些 item 占用多个 span 情况
  2. item 之前的间距相等
  3. item 的宽高可以保持一致,不会有某个 item 被压扁的情况
  4. 上下左右的边框可以与中间的分割线宽度不一致,每个都可以单独设置

(2) 使用方法

public GridSpaceDecoration(int horizontal, int vertical){
    //...
}

public GridSpaceDecoration(int horizontal, int vertical, int left, int right){
    //...
}

/**
 * @param horizontal 内部水平距离(px)
 * @param vertical   内部竖直距离(px)
 * @param left       最左边距离(px),默认为0
 * @param right      最右边距离(px),默认为0
 * @param top        最顶端距离(px),默认为0
 * @param bottom     最底端距离(px),默认为0
 */
public GridSpaceDecoration(int horizontal, int vertical, int left, int right, int top, int bottom){
    //...
}
复制代码

该类提供了三个构造方法,直接设置相应的值,然后 add 到 RecyclerView 中即可。

(3) 具体实现

step1: 分析

要实现的功能很清晰,就是要解决上面的常见问题。其中,第2、3点比较麻烦,为什么呢?先分析一下

ItemDecoration深入解析与实战(二)—— 实际运用

先看下上图,当使用 GridLayoutManager 时, GridLayoutManager 会将每个 Item 的最大可用空间平均分配开来,就像上图黑线所对应的三个框就是3个 Item 的最大可分配空间。橙色区域就是 Decoration 设置的值跟 item 的 margin ,如果 margin 为0,那么橙色区域便是在 getItemOffsets 方法中设置的值( 下面简称 offsets )。绿色虚线所围成的区域就是我们 itemView 的实际空间。

通过上图,当我们为 item 设置相同的间距时,会发现 item 1 的空间被压缩了,那么怎么解决这一问题呢?

  1. 每个item 宽度相同
  2. item 之前的间距一样

我们要解决的就是上面的问题

  • 先讨论第1点,因为每个 item 的最大可用空间(黑色框格子)是一致的,所以想要让 item 的宽度一样,就是让每个 item 的 offsets 保持一致。我们可以得到下面的公式:

    sizeAvg = (left + right + center * (spanCount-1)) / spanCount
    

    其中,left 、right 为最左、左右边间距,center 为中间间距,spanCount 为每一行的 span 个数,就可以得出每个 item 需要设置的 offsets 大小 sizeAvg ,这样就可以保证每个 item 的宽度一致(均分)

  • 再看第2点,我们要保证每个中间间距都一样,左右间距达到我们设置的大小。首先,最左边的间距是已经确定了的,即 left,那么最左边 item 的右边 right1 就可以得出为 sizeAvg - left,第二个 item 左边间距 left2 就是 center - right1 同理可以推出接下来的 item ,看下图会更清晰:

    ItemDecoration深入解析与实战(二)—— 实际运用

    然后把中间的实体线给去掉:

    ItemDecoration深入解析与实战(二)—— 实际运用

    就可以看到每个 item 的宽度一样了,而且间距也是符合预期的效果。(图片是人工画的,可能会有点小误差)

step2 实现

上面分析完成,接着看看算法实现:

@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
                           @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
    if (isFirst) {
        init(parent);
        isFirst = false;
    }
    if (mManager.getOrientation() == LinearLayoutManager.VERTICAL) {
        handleVertical(outRect, view, parent, state);  //注释1
    } else {
        handleHorizontal(outRect, view, parent, state);
    }
}
复制代码

很简单,先是做了一点初始化,然后分两个方向进行不同处理。直接看注释1(orientation == VERTICAL)部分:

private void handleVertical(Rect outRect, View view, RecyclerView parent,
                            RecyclerView.State state) {
    GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams) view.getLayoutParams();
    int childPos = parent.getChildAdapterPosition(view);
    int sizeAvg = (int) ((mHorizontal * (mSpanCount - 1) + mLeft + mRight) * 1f / mSpanCount);
    int spanSize = lp.getSpanSize();
    int spanIndex = lp.getSpanIndex();
    outRect.left = computeLeft(spanIndex, sizeAvg);    //注释1
    if (spanSize == 0 || spanSize == mSpanCount) {
        outRect.right = sizeAvg - outRect.left;
    } else {
        outRect.right = computeRight(spanIndex + spanSize - 1, sizeAvg);
    }
    outRect.top = mVertical / 2;
    outRect.bottom = mVertical / 2;
    if (isFirstRaw(childPos)) {
        outRect.top = mTop;
    }
    if (isLastRaw(childPos)) {
        outRect.bottom = mBottom;
    }
}
复制代码

这里的 sizeAvg 就是我们上面分析的那个 sizeAvg,然后再调用 computeLeft 方法(注释1),先看下这个方法这怎样的实现:

private int computeLeft(int spanIndex, int sizeAvg) {
    if (spanIndex == 0) {
        return mLeft;
    } else if (spanIndex >= mSpanCount / 2) {
        //从右边算起
        return sizeAvg - computeRight(spanIndex, sizeAvg);
    } else {
        //从左边算起
        return mHorizontal - computeRight(spanIndex - 1, sizeAvg);
    }
}

private int computeRight(int spanIndex, int sizeAvg) {
    if (spanIndex == mSpanCount - 1) {
        return mRight;
    } else if (spanIndex >= mSpanCount / 2) {
        //从右边算起
        return mHorizontal - computeLeft(spanIndex + 1, sizeAvg);
    } else {
        //从左边算起
        return sizeAvg - computeLeft(spanIndex, sizeAvg);
    }
}
复制代码

其实就是一个递归的算法,用的就是上面分析的逻辑,不清楚可以回去翻翻上面的图。计算出水平的 offsets 后,后面的就很简单了,接下来会判断是否第一行跟最后一行来设置最顶部 top 跟最底部 bottom 。

这个 GridSpaceDecoration 就算完成了,主要就是完成一个 offsets 的设置,如果想要自定义一些分割线的效果,可以继承此类并实现 onDraw 方法即可。

4. ( StaggeredLayoutManger )瀑布流布局下均分等距间距(分割线)

(1) 实现效果

ItemDecoration深入解析与实战(二)—— 实际运用

(3) 具体实现

这个实现跟上面的基本差不多,所以贴一下代码就好了:

@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
                           @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
    RecyclerView.LayoutManager originalManager = parent.getLayoutManager();
    if (originalManager == null || !(originalManager instanceof StaggeredGridLayoutManager)) {
        return;
    }
    StaggeredGridLayoutManager manager = (StaggeredGridLayoutManager) originalManager;
    if (manager.getOrientation() == StaggeredGridLayoutManager.VERTICAL) {
        handleVertical(outRect, view, parent);
    } else {
        handleHorizontal(outRect, view, parent);
    }
}

private void handleVertical(@NonNull Rect outRect, @NonNull View view,
                            @NonNull RecyclerView parent) {
    StaggeredGridLayoutManager.LayoutParams params =
            (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
    int spanIndex = params.getSpanIndex();
    int adapterPos = parent.getChildAdapterPosition(view);
    int sizeAvg = (int) ((mHorizontal * (mSpanCount - 1) + mLeft + mRight) * 1f / mSpanCount);
    int left = computeLeft(spanIndex, sizeAvg);
    int right = computeRight(spanIndex, sizeAvg);
    outRect.left = left;
    outRect.right = right;
    outRect.top = mVertical / 2;
    outRect.bottom = mVertical / 2;
    if (isFirstRaw(adapterPos, spanIndex)) {
        //第一行
        outRect.top = mTop;
    }
    if (isLastRaw(spanIndex)) {
        //最后一行
        outRect.bottom = mBottom;
    }
}
复制代码

5. ( GridLayoutManager )网格布局下实现表格式边框

StaggeredSpaceDecoration

(1) 实现效果

ItemDecoration深入解析与实战(二)—— 实际运用

(2) 具体实现

TableDecoration

TableDecoration 是继承于上面第3点的 GridSpaceDecoration 来实现的, GridSpaceDecoration 负责间距处理, TableDecoration 则是将分割线给画出来。所以主要就是 onDraw 方法的实现:

先看构造方法:

public class TableDecoration extends GridSpaceDecoration {

    private Drawable mDivider;
    private int mSize;
    private Rect mBounds;

    /**
     * @param color 边框颜色
     * @param size 边框大小(px)
     */
    public TableDecoration(@ColorInt int color, int size) {
        super(size, size, size, size, size, size);
        mSize = size;
        mDivider = new ColorDrawable(color);
        mBounds = new Rect();
    }
}
复制代码

就是将 item 的所有边框都设置为 size ,然后根据传进来的 color 创建一个 Drawable 对象。接着看 onDraw 方法:

@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    int childCount = parent.getChildCount();
    for (int i = 0; i < childCount; i++) {
        View view = parent.getChildAt(i);
        draw(c, parent, view);
    }
    drawLast(c, parent);
}
复制代码

先是遍历所有 child ,然后进行每个 child 的绘制:

private void draw(Canvas canvas, RecyclerView parent, View view) {
    canvas.save();
    int translationX = Math.round(view.getTranslationX());
    int translationY = Math.round(view.getTranslationY());
    int viewLeft = view.getLeft() + translationX;
    int viewRight = view.getRight() + translationX;
    int viewTop = view.getTop() + translationY;
    int viewBottom = view.getBottom() + translationY;
    parent.getDecoratedBoundsWithMargins(view, mBounds);
    drawLeft(canvas, mBounds, viewLeft);
    drawRight(canvas, mBounds, viewRight);
    drawTop(canvas, mBounds, viewTop);
    drawBottom(canvas, mBounds, viewBottom);
    canvas.restore();
}

private void drawLeft(Canvas canvas, Rect bounds, int left) {
    mDivider.setBounds(bounds.left, bounds.top, left, bounds.bottom);
    mDivider.draw(canvas);
}
//...
复制代码

逻辑也不难,跟第2点 自定义分割线实现 里的逻辑差不多,将我们设置的 item 的所有间距画出来,这里就不细说了。画完所有 item 后,还会在 onDraw 调用一个 drawLast 方法,我们先看看没有调用这个方法是怎样的效果:

ItemDecoration深入解析与实战(二)—— 实际运用

可以很明显看出,最后那里如果 item 不是铺满整一行的话,会导致后面那里有一部分的缺陷,这个缺陷其实我们在第3点 网格布局下的均分等距间距(分割线) GridSpaceDecoration 时分析过程中就可以发现了,由于每个 item 的上下左右 offsets 并不一定一致,所以会导致当没有最后一行有空缺的话就会造成一个边框的缺陷。

原因了解了,那么问题解决应该也不难:

private void drawLast(Canvas canvas, RecyclerView parent) {
    View lastView = parent.getChildAt(parent.getChildCount() - 1);
    int pos = parent.getChildAdapterPosition(lastView);
    if (isLastColumn((GridLayoutManager.LayoutParams) lastView.getLayoutParams(),pos)){
        return;
    }
    int translationX = Math.round(lastView.getTranslationX());
    int translationY = Math.round(lastView.getTranslationY());
    int viewLeft = lastView.getLeft() + translationX;
    int viewRight = lastView.getRight() + translationX;
    int viewTop = lastView.getTop() + translationY;
    int viewBottom = lastView.getBottom() + translationY;
    parent.getDecoratedBoundsWithMargins(lastView, mBounds);
    canvas.save();
    if (mManager.getOrientation() == LinearLayoutManager.VERTICAL) {
        int contentRight = parent.getRight() - parent.getPaddingRight() - Math.round(parent.getTranslationX());
        //空白区域上边缘
        mDivider.setBounds(mBounds.right, mBounds.top, contentRight, viewTop);
        mDivider.draw(canvas);
        //空白区域左边缘
        mDivider.setBounds(viewRight, viewTop, viewRight + mSize, mBounds.bottom);
        mDivider.draw(canvas);
    }else {
        int contentBottom = parent.getBottom()-parent.getPaddingBottom()-Math.round(parent.getTranslationY());
        //空白区域上边缘
        mDivider.setBounds(mBounds.left,viewBottom,mBounds.right,viewBottom+mSize);
        mDivider.draw(canvas);
        //空白区域左边缘
        mDivider.setBounds(mBounds.left,mBounds.bottom,viewLeft,contentBottom);
        mDivider.draw(canvas);
    }
    canvas.restore();
}
复制代码

主要逻辑就是将空缺出来的地方给补齐。

6. ( GridLayoutManager )打造粘性头部

StickHeaderDecoration

(1) 实现效果

ItemDecoration深入解析与实战(二)—— 实际运用

(2) 具体实现

  • 分析

    上面的几个例子中, getItemOffsets 以及 onDraw 方法都用过了,Decoration 中三大方法还有一个 onDrawOver ,这个效果就是用 onDrawOver 来实现的。

    逻辑是这样的:要实现这样的效果,我们需要在 RecyclerView 的顶部画上一个 StickHeader,也就是我们的第一个 Child。 同时也有一个问题就是我们怎么知道哪个 item 是可以当成头部(StickHeader)的,这里我提供了一个接口来进行判断:

    public interface StickProvider {
        boolean isStick(int position);
    }
    复制代码

    这是 StickHeaderDecoration 的一个内部实现类,需要将它的一个对象作为 StickHeaderDecoration 的构造方法的参数,例如:

    StickHeaderDecoration decoration = new StickHeaderDecoration(new StickHeaderDecoration.StickProvider() {
    @Override
    public boolean isStick(int position) {
        return mList.get(position).type == StickBean.TYPE_HEADER;
    }
    });
    
    //使用labamda会更简洁
    StickHeaderDecoration decoration = 
        new StickHeaderDecoration(position -> mList.get(position).type == StickBean.TYPE_HEADER);
    复制代码

    然后我们就可以通过这个 StickProvider 对象进行判断是否是需要显示的头部了,接着看主要的方法 onDrawOver :

    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent,
                           @NonNull RecyclerView.State state) {
        RecyclerView.Adapter adapter = parent.getAdapter();
        if (adapter == null || !(adapter instanceof StickProvider)) {
            return;
        }
        int itemCount = adapter.getItemCount();
        if (itemCount == 1) {
            return;
        }
        //找到当前的StickHeader对应的position
        int currStickPos = currStickPos(parent);       //注释1
        if (currStickPos == -1) {
            return;
        }
        c.save();
        if (parent.getClipToPadding()) {
            //考虑padding的情况
            c.clipRect(parent.getPaddingLeft(), parent.getPaddingTop(),
                    parent.getWidth() - parent.getPaddingRight(),
                    parent.getHeight() - parent.getPaddingBottom());
        }
        int currStickType = adapter.getItemViewType(currStickPos);
        //当前显示的StickHeader相应的ViewHolder,先看有没有缓存
        RecyclerView.ViewHolder currHolder = mViewMap.get(currStickType);
        if (currHolder == null) {
            //没有缓存则新生成
            currHolder = adapter.createViewHolder(parent, currStickType);
            //主动测量并布局
            measure(currHolder.itemView, parent);
            mViewMap.put(currStickType, currHolder);
        }
        adapter.bindViewHolder(currHolder, currStickPos);
        c.translate(currHolder.itemView.getLeft(), currHolder.itemView.getTop());
        currHolder.itemView.draw(c);
        c.restore();
    }
    
    复制代码

    整体逻辑并不难,先是找到当前要显示的头部,这个头部怎么来的呢,看看注释1处的 currStickPos 方法:

    private int currStickPos(RecyclerView parent) {
        int childCount = parent.getChildCount();
        int paddingTop = parent.getPaddingTop();
        int currStickPos = -1;
        for (int i = 0; i < childCount; i++) {
            //考虑到parent padding 的情况,第一个item有可能不可见情况
            //从第1个child向后找
            View child = parent.getChildAt(i);
            if (child.getTop() >= paddingTop) {
                break;
            }
            int pos = parent.getChildAdapterPosition(child);
            if (mProvider.isStick(pos)) {
                currStickPos = pos;
            }
        }
        if (currStickPos != -1) {
            return currStickPos;
        }
        for (int i = parent.getChildAdapterPosition(parent.getChildAt(0)) - 1; i >= 0; i--) {
            //从第一个child的前一个开始找
            if (mProvider.isStick(i)) {
                return i;
            }
        }
        return -1;
    }
    复制代码

    主要逻辑分为两步:

    • 因为当 RecyclerView 设置 paddingTop 时,第一个 Item 有可能是不可见的(被padding盖住了),所以第一步是从当前第一个 child 开始向后找(child的top<paddingTop),当找到时则返回对应的 Adapter position,如果没有找到,则进行二步。
    • 第二步就是从第一个child的 Adapter 前一个 position 开始找,找到则返回,如果都没找到,则返回-1。

    再回到 onDrawOver 方法中,当找到当前要显示的 Header 后,并会为他进行测量,然后布局(具体看项目源码),接着再调用 Adapter 的 bindViewHolder 方法进行数据绑定,最后再画出来就ok了,接着看看效果:

ItemDecoration深入解析与实战(二)—— 实际运用

看到效果图并不是我们想要达到的效果,很明显缺少一个推动的效果,那么这个怎么实现呢:

@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent,
                    @NonNull RecyclerView.State state) {
 //...
 
 //寻找下一个StickHeader
 RecyclerView.ViewHolder nextStickHolder = nextStickHolder(parent, currStickPos);
 if (nextStickHolder != null) {
     RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) currHolder.itemView.getLayoutParams();
     int bottom = parent.getPaddingTop() + params.topMargin + currHolder.itemView.getMeasuredHeight();
     int nextStickTop = nextStickHolder.itemView.getTop();
     //下一个StickHeader如果顶部碰到了当前StickHeader的屁股,那么将当前的向上推
     if (nextStickTop < bottom && nextStickTop > 0) {
         c.translate(0, nextStickTop - bottom);
     }
 }
 adapter.bindViewHolder(currHolder, currStickPos);
 c.translate(currHolder.itemView.getLeft(), currHolder.itemView.getTop());
 currHolder.itemView.draw(c);
 c.restore();
}
复制代码

逻辑也不难,就是找到下一个 Header ,如果它碰到了上面那个的屁股的话,就将上面那个向上移动一点,就可以形成我们的推动效果啦。

三 总结

从决定说要学习这个开始,到写完Demo,写完文章,大概花了2个星期,其中有一些点也是深入了解了部分源码,掉了不少头发才总结出来。其中也碰到不少坑,而且这个系列目前网上的文章比较杂,很少有一个整体的分析,甚至有一些理解是错的,所以这篇文章写了相对详细很多。

由于编者水平有限,文章难免会有错漏的地方,如有发现,恳请指正,如果有更好的实现思路也可以提供。

要看项目源码或者Demo的戳 这里


以上所述就是小编给大家介绍的《ItemDecoration深入解析与实战(二)—— 实际运用》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Applications (Hacking Exposed)

Web Applications (Hacking Exposed)

Joel Scambray、Mike Shema / McGraw-Hill Osborne Media / 2002-06-19 / USD 49.99

Get in-depth coverage of Web application platforms and their vulnerabilities, presented the same popular format as the international bestseller, Hacking Exposed. Covering hacking scenarios across diff......一起来看看 《Web Applications (Hacking Exposed)》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具