18

我想创建一个通用的 ViewGroup,然后可以在 XML 布局中重用它,以绕过放入其中的任何内容的角落。

由于某种原因canvas.clipPath()似乎没有效果。我究竟做错了什么?

这是Java代码:

package rounded;

import static android.graphics.Path.Direction.CCW;
public class RoundedView extends FrameLayout {
    private float radius;
    private Path path = new Path();
    private RectF rect = new RectF();

    public RoundedView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.radius = attrs.getAttributeFloatValue(null, "corner_radius", 0f);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int savedState = canvas.save();
        float w = getWidth();
        float h = getHeight();
        path.reset();
        rect.set(0, 0, w, h);
        path.addRoundRect(rect, radius, radius, CCW);
        path.close();
        boolean debug = canvas.clipPath(path);
        super.onDraw(canvas);
        canvas.restoreToCount(savedState);
    }
}

XML 中的用法:

<?xml version="1.0" encoding="utf-8"?>
<rounded.RoundedView   xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    corner_radius="40.0" >
    <RelativeLayout 
        android:id="@+id/RelativeLayout1"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
        ...
    </RelativeLayout>
</rounded.RoundedView>
4

7 回答 7

55

创建ViewGroup子剪辑的正确方法是在dispatchDraw(Canvas)方法中进行。

这是一个关于如何ViewGroup用圆圈剪辑 a 的任何子项的示例:

private Path path = new Path();

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    // compute the path
    float halfWidth = w / 2f;
    float halfHeight = h / 2f;
    float centerX = halfWidth;
    float centerY = halfHeight;
    path.reset();
    path.addCircle(centerX, centerY, Math.min(halfWidth, halfHeight), Path.Direction.CW);
    path.close();

}

@Override
protected void dispatchDraw(Canvas canvas) {
    int save = canvas.save();
    canvas.clipPath(circlePath);
    super.dispatchDraw(canvas);
    canvas.restoreToCount(save);
}

dispatchDraw方法是用来剪辑子项的方法。setWillNotDraw(false)如果您的布局只是剪辑其子级,则无需这样做。

这张图片是用上面的代码获得的,我只是扩展了 Facebook ProfilePictureView(这是一个包含facebook 个人资料图片FrameLayout的正方形):ImageView

圆形剪裁

因此,要实现圆形边框,您可以执行以下操作:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    // compute the path
    path.reset();
    rect.set(0, 0, w, h);
    path.addRoundRect(rect, radius, radius, Path.Direction.CW);
    path.close();

}

@Override
protected void dispatchDraw(Canvas canvas) {
    int save = canvas.save();
    canvas.clipPath(path);
    super.dispatchDraw(canvas);
    canvas.restoreToCount(save);
}

圆形边框剪裁

您实际上可以创建任何复杂的路径:)

请记住,您可以使用您喜欢的“Op”操作多次调用 clipPath,以便以您喜欢的方式与多个剪辑相交。

注意:我在 中创建了路径,onSizeChanged因为这样做onDraw不利于性能。

注意2:剪切路径是在没有抗锯齿的情况下完成的:/所以如果你想要平滑的边框,你需要以其他方式进行。我现在不知道有任何方法可以使剪辑使用抗锯齿。

更新(大纲)

由于 Android Lollipop (API 21) 高程和阴影可以应用于视图。引入了一个称为大纲的新概念。这是一条告诉框架用于计算阴影和其他东西(如涟漪效应)的视图形状的路径。

视图的默认值Outline是视图大小的矩形,但可以很容易地制作成椭圆形/圆形或圆角矩形。要定义自定义Outline,您必须在视图上使用该方法setOutlineProvider(),如果它是自定义视图,您可能希望在构造函数中将其设置为自ViewOutlineProvider定义视图的内部类。Outline您可以使用您选择的 a定义您自己的提供者Path,只要它是凸路径(数学概念意味着没有凹槽和孔的封闭路径,例如星形和齿轮形状都不是凸形的)。

您也可以使用该方法setClipToOutline(true)使大纲也剪辑(我认为这也适用于抗锯齿,有人可以在评论中确认/反驳吗?),但这仅支持非Path大纲。

祝你好运

于 2015-09-24T15:36:31.597 回答
23

您可以覆盖该draw(Canvas canvas)方法:



    public class RoundedLinearLayout extends LinearLayout {
        Path mPath;
        float mCornerRadius;

        public RoundedLinearLayout(Context context) {
            super(context);
        }

        public RoundedLinearLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        public RoundedLinearLayout(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }

        @Override
        public void draw(Canvas canvas) {
            canvas.save();
            canvas.clipPath(mPath);
            super.draw(canvas);
            canvas.restore();
        }

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            RectF r = new RectF(0, 0, w, h);
            mPath = new Path();
            mPath.addRoundRect(r, mCornerRadius, mCornerRadius, Direction.CW);
            mPath.close();
        }

        public void setCornerRadius(int radius) {
            mCornerRadius = radius;
            invalidate();
        }
      }

于 2013-08-16T16:29:59.320 回答
3

如果未设置 Ur Layout 的背景,则不会调用FrameLayout 的onDraw

你应该重写 dispatchDraw;

于 2015-12-23T09:13:06.053 回答
2

Don't forget to ask for onDraw to be called with setWillNotDraw(false); and set a value to mRadius then just do something like that :

@Override
protected void onDraw(Canvas canvas) {
    mPath.reset();
    mRect.set(0, 0, canvas.getWidth(), canvas.getHeight());
    mPath.addRoundRect(mRect, mRadius, mRadius, Direction.CCW);
    mPath.close();
    canvas.clipPath(mPath);
}
于 2014-01-13T19:50:09.173 回答
2

ViewGroup(以及因此它的子类)设置一个标志,表明它默认不进行任何绘图。在源代码中,它看起来有点像这样:

// ViewGroup doesn't draw by default
if (!debugDraw()) {
    setFlags(WILL_NOT_DRAW, DRAW_MASK);
}

所以你onDraw(...)现在可能根本不会受到打击。如果您想做任何手工绘图,请致电setWillNotDraw(false).

于 2012-11-22T18:44:25.997 回答
-2

你需要重写 drawChild() 方法来剪辑 childViews。

@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    int flag = canvas.save();
    canvas.clipPath(pathToClip);
    boolean result=super.drawChild(canvas, child, drawingTime);
    canvas.restoreToCount(flag);
    return result;
}

如果您也想剪辑 ViewGroup 的背景,请改写 draw()。像这样

@Override
public void draw(Canvas canvas) {
    int flag = canvas.save();
    canvas.clipPath(pathToClip);
    super.draw(canvas);
    canvas.restoreToCount(flag);
}
于 2013-11-13T05:15:39.910 回答
-3

为什么不将 ShapeDrawable 定义为布局的背景,并在运行时更改可绘制对象的圆角半径。

于 2012-11-22T17:10:59.230 回答