圆角ItemDecoration

5

我有一个带有GridLayoutManagerRecyclerView,其中包含具有各种ViewTypes(以及SpanSize)的项目。 我需要为所有R.layout.item_image类型的项目设置圆角,如下图所示。

Round corners example

所以我创建了一个ItemDecoration,计算出这些项将被绘制的Rect。然后将Canvas裁剪到这个Rect(使用Path来圆角):

public class RoundCornersDecoration extends RecyclerView.ItemDecoration {
    private final float radius;
    private final RectF defaultRectToClip;

    public RoundCornersDecoration(float radius) {
        this.radius = radius;
        defaultRectToClip = new RectF(Float.MAX_VALUE, Float.MAX_VALUE, 0, 0);
    }

    @Override
    public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
        final RectF rectToClip = getRectToClip(parent);

        // has no items with ViewType == `R.layout.item_image`
        if (rectToClip.equals(defaultRectToClip)) {
            return;
        }

        final Path path = new Path();
        path.addRoundRect(rectToClip, radius, radius, Path.Direction.CW);
        canvas.clipPath(path);
    }

    private RectF getRectToClip(RecyclerView parent) {
        final RectF rectToClip = new RectF(defaultRectToClip);
        final Rect childRect = new Rect();
        for (int i = 0; i < parent.getChildCount(); i++) {
            if (!isImage(parent, i)) {
                continue;
            }

            final View child = parent.getChildAt(i);
            parent.getDecoratedBoundsWithMargins(child, childRect);

            rectToClip.left = Math.min(rectToClip.left, childRect.left);
            rectToClip.top = Math.min(rectToClip.top, childRect.top);
            rectToClip.right = Math.max(rectToClip.right, childRect.right);
            rectToClip.bottom = Math.max(rectToClip.bottom, childRect.bottom);
        }
        return rectToClip;
    }

    private boolean isImage(RecyclerView parent, int viewPosition) {
        final RecyclerView.Adapter adapter = parent.getAdapter();
        final int viewType = adapter.getItemViewType(viewPosition);
        return viewType == R.layout.item_image;
    }
}

除了图像下方没有绘制其他项之外,一切都正常。我猜这是因为在实际绘制任何项之前,我剪辑了画布。那么我应该如何剪辑画布以保存圆角并显示所有其他项?

onCreateViewHolder 方法中,您需要创建根 ViewViewHolder。使用自定义的 FrameLayout 作为您的 "根视图",并覆盖其 dispatchDraw 方法。在那里,您可以剪切画布,并且所有子视图也将被剪切。 - pskink
如果我理解正确的话,我需要检查那里视图的顺序(例如剪辑左上角或剪辑右下角)。 - Nikolay Kulachenko
是的,在 onBindViewHolder 中你有 int position,所以你可以检查它是否为顶部、中间或底部视图,并相应地更改路径。 - pskink
@pskink,那么不剪辑而是在根视图上设置一个带有适当 <corners .. /> 形状的背景将更简单。无论如何,我不想将该逻辑暴露到 ViewHolder 中,因为我在许多地方重用它。因此,我正在寻找通过 ItemDecoration 解决它的方法。 但是感谢您的建议。如果没有其他选择,我会使用它。 - Nikolay Kulachenko
你不需要操作你的 ViewHolder,你只需要传递一个根 View 来裁剪它们的子视图。 - pskink
@pskink,你能发一个回答(带代码)吗?谢谢。 - Nikolay Kulachenko
1个回答

0

我终于找到了一个解决方案,而且它非常简单。我所需要做的就是提供canvas另一部分

pic

正如您所看到的,topleftright点与第一个矩形相同,因此我们只需要找到bottom

    int maxBottom = 0;
    final Rect childRect = new Rect();
    for (int i = 0; i < parent.getChildCount(); i++) {
        final View child = parent.getChildAt(i);
        parent.getDecoratedBoundsWithMargins(child, childRect);
        maxBottom = Math.max(maxBottom, childRect.bottom);
    }

创建一个新的矩形:
    final RectF otherItemsRect = new RectF(rectToClip);
    otherItemsRect.top = otherItemsRect.bottom;
    otherItemsRect.bottom = maxBottom;

将其包含在路径中,然后剪切:

    final Path path = new Path();
    path.addRoundRect(rectToClip, radius, radius, Path.Direction.CW);
    path.addRect(otherItemsRect, Path.Direction.CW);
    canvas.clipPath(path);

完成了。现在我们已经为图像添加了圆角的所有项目。

附注:我没有提供优化代码,以保持简单。


你能否详细说明一下这个问题,并展示一下你是如何构建以上的代码的? - Vinay Gaba
此外,“创建新矩形”部分是否存在错误? - Vinay Gaba
@VinayGaba 你可以在这个 gist 中找到最终版本:https://gist.github.com/NikolayKul/67450c10d443fe680f73f896211a7d3d - Nikolay Kulachenko
"Create a new rect"部分没有错误。它只是这个更短的版本:otherItemsRect.top = rectToClip.bottom; otherItemsRect.left = rectToClip.left; otherItemsRect.right = rectToClip.right; otherItemsRect.bottom = maxBottom;``` - Nikolay Kulachenko
抱歉,我后来能理解你的逻辑了 :) 谢谢! - Vinay Gaba
我尝试了这个,但是角落有点儿粗糙,不够平滑。你有什么想法可以解决吗? - ibraizQ9

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接