How to create a rectangle with rounded corners with different sections in different colors

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

enter image description here

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://stackoverflow.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);

enter image description here

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:

enter image description here

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

For the shadow follow-up question, you have two options. Use built-in methods or draw your own (depending on needs). The former is likely the correct way to do it, unless you need a custom shadow.

Built-In Methods

See the section on Outline in this blog post: https://android.jlelse.eu/mastering-shadows-in-android-e883ad2c9d5b

Example code from the post:

Create an OutlineProvider

public class ScalingLayoutOutlineProvider extends ViewOutlineProvider {

    @Override
    public void getOutline(View view, Outline outline) {
        outline.setRoundRect(0, 0, width, height, radius);
    }
}

Add the Outline Provider to your View

public class ScalingLayout extends FrameLayout {

    //...
    viewOutline = new ScalingLayoutOutlineProvider(w, h, currentRadius);
    setOutlineProvider(viewOutline);
    //..

}

Drawing Your Own

For the border, you can use View.getElevation() to get the desired height: https://developer.android.com/reference/android/view/View#getElevation() and then draw another shape behind the bar for the shadow.

Tags:

Android