且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何创建带有不同颜色的不同部分的圆角矩形

更新时间:2022-10-17 17:24:27

您可以创建自定义的 View ,它将在 Canvas 的裁剪部分上绘制矩形:

 公共类RoundedCornersSegmentedView扩展了View {专用漆面漆A,漆面B,漆面C;私人浮点角半径;私有浮动实测宽度,实测高度;私有RectF rect =新RectF(0,0,0,0);私有路径rectPath = new Path();公共RoundedCornersSegmentedView(上下文上下文){超级(上下文);在里面();}public RoundedCornersSegmentedView(Context context,@Nullable AttributeSet attrs){超级(上下文,attrs);在里面();}public RoundedCornersSegmentedView(Context context,@Nullable AttributeSet attrs,int defStyleAttr){超级(上下文,attrs,defStyleAttr);在里面();}私人无效init(){setWillNotDraw(false);//添加此代码,以便Canvas.clipPath()还将为运行低于17的Api级别的设备提供所需的结果//参见https://***.com/a/30354461/5015207如果(Build.VERSION.SDK_INT< = Build.VERSION_CODES.JELLY_BEAN_MR2){setLayerType(LAYER_TYPE_SOFTWARE,空);}paintA =新的Paint(Paint.ANTI_ALIAS_FLAG);paintA.setColor(Color.GREEN);paintA.setStyle(Paint.Style.FILL);paintB =新的Paint(Paint.ANTI_ALIAS_FLAG);paintB.setColor(Color.YELLOW);paintB.setStyle(Paint.Style.FILL);paintC =新的Paint(Paint.ANTI_ALIAS_FLAG);paintC.setColor(Color.MAGENTA);paintC.setStyle(Paint.Style.FILL);//使用< dimen name ="corner_radius"> 60dp</dimen>在res/values/dimens.xml中cornerRadius = getResources().getDimensionPixelSize(R.dimen.corner_radius);}@Override受保护的void onLayout(布尔值已更改,左侧为int,顶部为int,右侧为int,底部为int){super.onLayout(已更改,左,上,右,下)测量宽度=右-左;测量的高度=底部-顶部;rect.set(0,0,measuredWidth,measuredHeight);rectPath.reset();rectPath.addRoundRect(rect,cornerRadius,cornerRadius,Path.Direction.CW);}@Override保护无效的onDraw(Canvas canvas){super.onDraw(画布);canvas.clipPath(rectPath);canvas.drawRect(0,0,measuredWidth/3f,measuredHeight,paintA);canvas.drawRect(measuredWidth/3f,0,2 * measuredWidth/3f,measuredHeight,paintB);canvas.drawRect(2 * measuredWidth/3f,0,measuredWidth,measuredHeight,paintC);}} 

如果要添加某种半透明的边缘,可以使用具有透明颜色的 Paint 并填充类型为 Paint.Style.STROKE 并绘制一个圆角矩形.

  Paint shadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//材质颜色蓝灰色400",//参见https://material.io/design/color/the-color-system.htmlshadowPaint.setColor(Color.argb(30,120,144,156));shadowPaint.setStyle(Paint.Style.STROKE);shadowPaint.setStrokeWidth(30); 

矩形(在 onLayout()外部实例化,以获得更好的性能):

  private RectF shadowRect = new RectF(0,0,0,0); 

onLayout()中:

  int inset = 20;shadowRect.set(插入,插入,measuredWidth-插入,measuredHeight-插入); 

您应该切换阴影 Paint 的颜色/alpha值以及笔划宽度和插图的值,直到您认为它看起来不错为止.

绘制彩色线段后,在 onDraw()中应用:

  canvas.drawRoundRect(shadowRect,cornerRadius,cornerRadius,shadowPaint); 

如果您堆叠半透明的 Paint (它们具有减小的笔触宽度和增大的内陷,例如构建自己的颜色渐变),它也可能看起来不错(更多3D).

感谢@wblaschko在 ViewOutlineProvider 上共享代码段!我将其添加到示例中,并得到以下效果:

更改我的代码(注意:仅适用于Api级别21 +)

自定义视图的内部类:

  @TargetApi(21)静态类ScalingOutlineProvider扩展了ViewOutlineProvider {private int cornerRadius;ScalingOutlineProvider(int cornerRadius){this.cornerRadius = cornerRadius;}@Overridepublic void getOutline(View view,Outline outline){outline.setRoundRect(0,0,view.getWidth(),view.getHeight(),cornerRadius);}} 

init()的末尾:

  if(Build.VERSION.SDK_INT> = Build.VERSION_CODES.LOLLIPOP){//海拔高度为4dp(cornerRadius为60dp)setElevation(cornerRadius/15);setOutlineProvider(new ScalingOutlineProvider(cornerRadius));} 

REQUIREMENT

How do I create a view looking like this.

I would like to draw a view on the screen which is a line broken into segments showing values percentage of the whole view. My requirements are

  • view has different sections which are different colors
  • the view might not have all sections rendered, it might only have first 2 or first and last or just a single color etc - this is only known at runtime
  • the size of the different sections are only known at runtime therefore need to be specified programmatically
  • left and right corners of the whole view are rounded

IDEAS/THINGS I HAVE TRIED

(1) Custom view rendering 3 rectangles side by side

I have tried a custom view which renders 3 rectangles side by side. But these obviously have square corners.

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    int viewHeight = 50;

    canvas.drawrect(0,  0, 60,  viewHeight, paint); // A
    canvas.drawrect(60, 0, 120, viewHeight, paint); // B
    canvas.drawrect(120,0, 180, viewHeight, paint); // C
}

(2) Shape with rounded corners

I know I can use a Shape to define a rectangle with rounded corners using the following, but this is a single color.

<shape xmlns:android="http://schemas.android.com/apk/res/android">
     ...        
    <corners
        android:radius="4dp" />
    ....
</shape>

(3) Layer-list

From Android rectangle shape with two different color, I see I can use a layer-list to specify each item in the shape to have different colors.

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">       

    <item>
        <shape android:shape="rectangle">
            <size
                android:width="40dp"
                android:height="40dp" />
            <solid android:color="#F86F05" />
        </shape>
    </item>

    <item android:top="10dp">
        <shape android:shape="rectangle">
            <size
                android:width="30dp"
                android:height="30dp" />
            <solid android:color="#B31F19" />
        </shape>
    </item>

</layer-list>

(4) Layer-list with corners??

Can I add the "corners" tag to the whole layer-list to get the rounded main corners? I assume not, and that the corners part has to be in the "Item"s shape tags.

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <corners
        android:radius="4dp" />

    <item>
        <shape android:shape="rectangle">
            <size
                android:width="40dp"
                android:height="40dp" />
            <solid android:color="#F86F05" />
        </shape>
    </item>

    <item android:top="10dp">
        <shape android:shape="rectangle">
            <size
                android:width="30dp"
                android:height="30dp" />
            <solid android:color="#B31F19" />
        </shape>
    </item>

</layer-list>

SUMMARY

This last one is getting closer to my requirement however

  • how do i specify each "item"s width programmatically
  • how do i show/hide "item"s programmatically
  • how would i round just the top most visible "item"s top corners and the bottom most "item"s bottom corners

UPDATE: HOW DO I ADD ELEVATION/GREY BORDER

Thank you to "@0X0nosugar" for your solution. I am now wanting to add an elevation or a slight grey border as one of the colors is faily light and close to the background color. When I add the following I get a rectangular shadow which looks terrible with the curved corners.

android:elevation="2dp"
android:outlineProvider="bounds"

I would like it to appear like below

You can create a custom View which will draw rectangles on a clipped part of the Canvas:

public class RoundedCornersSegmentedView extends View {

    private Paint paintA, paintB, paintC;
    private float cornerRadius;
    private float measuredWidth, measuredHeight;
    private RectF rect = new RectF(0, 0, 0,0);
    private Path rectPath = new Path();

    public RoundedCornersSegmentedView(Context context) {
        super(context);
        init();
    }

    public RoundedCornersSegmentedView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public RoundedCornersSegmentedView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        setWillNotDraw(false);

        // add this so Canvas.clipPath() will give the desired result also for devices running Api level lower than 17,
        // see https://***.com/a/30354461/5015207
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            setLayerType(LAYER_TYPE_SOFTWARE, null);
        }
        paintA = new Paint(Paint.ANTI_ALIAS_FLAG);
        paintA.setColor(Color.GREEN);
        paintA.setStyle(Paint.Style.FILL);
        paintB = new Paint(Paint.ANTI_ALIAS_FLAG);
        paintB.setColor(Color.YELLOW);
        paintB.setStyle(Paint.Style.FILL);
        paintC = new Paint(Paint.ANTI_ALIAS_FLAG);
        paintC.setColor(Color.MAGENTA);
        paintC.setStyle(Paint.Style.FILL);

        // with  <dimen name="corner_radius">60dp</dimen> in res/values/dimens.xml
        cornerRadius = getResources().getDimensionPixelSize(R.dimen.corner_radius);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        measuredWidth = right - left;
        measuredHeight = bottom - top;
        rect.set(0, 0, measuredWidth, measuredHeight);
        rectPath.reset();
        rectPath.addRoundRect(rect, cornerRadius, cornerRadius, Path.Direction.CW);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.clipPath(rectPath);
        canvas.drawRect(0,0,measuredWidth/3f, measuredHeight, paintA);
        canvas.drawRect(measuredWidth/3f,0,2 * measuredWidth/3f, measuredHeight, paintB);
        canvas.drawRect(2 * measuredWidth/3f,0,measuredWidth, measuredHeight, paintC);
    }
}

If you want to add some kind of semi-transparent edge, you can use a Paint with a transparent color and fill type Paint.Style.STROKE and draw a rounded rectangle.

Paint shadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
// material color "Blue Gray 400",
// see https://material.io/design/color/the-color-system.html
shadowPaint.setColor(Color.argb(30, 120, 144, 156));
shadowPaint.setStyle(Paint.Style.STROKE);
shadowPaint.setStrokeWidth(30);

The rectangle (instantiate outside of onLayout() for better performance):

private RectF shadowRect = new RectF(0,0,0,0);

In onLayout():

int inset = 20;
shadowRect.set(inset, inset, measuredWidth - inset, measuredHeight - inset);

You should toggle the color/ the alpha value for the shadow Paint as well as the values for stroke width and inset until you think it looks good.

Apply in onDraw() after you've drawn the colored segments:

canvas.drawRoundRect(shadowRect, cornerRadius, cornerRadius, shadowPaint);

It can also look nice (more 3D) if you stack semi-transparent Paints with decreasing stroke width and increasing inset, like building your own color gradient.

Thanks to @wblaschko for sharing the code snippet on ViewOutlineProvider! I added it to my example and got the following effect:

Changes to my code (note: only possible for Api level 21+)

An inner class of the custom View:

@TargetApi(21)
static class ScalingOutlineProvider extends ViewOutlineProvider {
    private int cornerRadius;
    ScalingOutlineProvider(int cornerRadius){
        this.cornerRadius = cornerRadius;
    }
    @Override
    public void getOutline(View view, Outline outline) {
        outline.setRoundRect(0, 0, view.getWidth(), view.getHeight (), cornerRadius);
    }
}

And at the end of init():

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
    // elevation of 4dp (cornerRadius was 60dp)
    setElevation(cornerRadius/15);
    setOutlineProvider(new ScalingOutlineProvider(cornerRadius));
}