环绕其内容角的自定义布局 [英] Custom Layout that rounds the corners of its content

查看:29
本文介绍了环绕其内容角的自定义布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个通用的 ViewGroup,然后可以在 XML 布局中重用它来圆化放入其中的任何东西的角.

I would like to create a generic ViewGroup which can then be reused in XML layouts to round the corners of anything that is put into it.

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

For some reason canvas.clipPath() doesn't seem to have an effect. What am I doing wrong?

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 中的使用:

Usage in 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>

推荐答案

创建一个 ViewGroup 来剪辑它的孩子的正确方法是在 dispatchDraw(Canvas)代码>方法.

The right way to create a ViewGroup that clip its children is to do it in the dispatchDraw(Canvas) method.

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

This is an example on how you can clip any children of a ViewGroup with a circle:

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).

the dispatchDraw method is the one called to clip children. No need to setWillNotDraw(false) if your layout just clip its children.

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

This image is obtained with the code above, I just extended Facebook ProfilePictureView (which is a FrameLayout including a square ImageView with the facebook profile picture):

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

So to achieve a round border you do something like this:

@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);
}

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

You can actually create any complex path :)

请记住,您可以使用Op"操作多次调用clipPath,以您喜欢的方式将多个剪辑相交.

Remember you can call clipPath multiple times with the "Op" operation you please to intersect multiple clipping in the way you like.

注意:我在 onSizeChanged 中创建了 Path,因为在 onDraw 中这样做对性能不利.

NOTE: I created the Path in the onSizeChanged because doing so in the onDraw is bad for performance.

注意2:在没有抗锯齿的情况下剪切路径:/所以如果你想要平滑的边框,你需要以其他方式来做.我现在不知道有什么方法可以让剪辑使用抗锯齿.

NOTE2: clipping a Path is done without anti-aliasing :/ so if you want smooth borders you'll need to do it in some other way. I'm not aware of any way of making clipping use anti-aliasing right now.

更新(大纲)

由于 Android Lollipop (API 21) 高程和阴影可以应用于视图.引入了一个名为 Outline 的新概念.这是一个路径,告诉框架要用于的视图的形状计算阴影和其他东西(比如波纹效果).

Since Android Lollipop (API 21) elevation and shadows can be applied to views. A new concept called Outline has been introduced. This is a path that tells the framework the shape of the view to be used to compute the shadow and other things (like ripple effects).

视图的默认Outline 是一个与视图大小相同的矩形,但可以很容易地制成椭圆/圆形或圆角矩形.要定义自定义 Outline,您必须使用方法 setOutlineProvider() 在视图上,如果它是自定义视图,您可能希望使用自定义 ViewOutlineProvider 定义为自定义视图的内部类.您可以使用您选择的 Path 定义您自己的 Outline 提供程序,只要它是一个 凸面路径(数学概念意味着没有凹槽和孔的封闭路径,例如星形和齿轮形状都不是凸形的).

The default Outline of the view is a rectangular of the size of the view but can be easily made an oval/circle or a rounded rectangular. To define a custom Outline you have to use the method setOutlineProvider() on the view, if it's a custom View you may want to set it in the constructor with your custom ViewOutlineProvider defined as inner class of your custom View. You can define your own Outline provider using a Path of your choice, as long as it is a convex path (mathematical concept meaning a closed path with no recess and no holes, as an example neither a star shape nor a gear shape are convex).

您也可以使用方法 setClipToOutline(正确) 使 Outline 也剪辑(我认为这也适用于抗锯齿,有人可以在评论中确认/反驳吗?),但这仅适用于非Path Outline.

You can also use the method setClipToOutline(true) to make the Outline also clip (and I think this also works with anti-aliasing, can someone confirm/refute in comments?), but this is only supported for non-Path Outline.

祝你好运

这篇关于环绕其内容角的自定义布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆