Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a custom drawable with diagonal gradient with round edges?

Am trying to make a custom drawable using xml, like the attached image enter image description here

Below are two approach i have done,

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="30dp" />
<solid android:color="@color/colorAccent" />
<stroke
    android:width="2dp"
    android:color="@color/colorAccent" />
<gradient
    android:angle="135"
    android:endColor="#000"
    android:startColor="#ffff"
    android:type="linear" />
</shape>

By doing like this i can get the effect right, but the colors seems to be merged, I want two colors without any merge effect, then i tried like this,

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/white">
    <shape android:shape="oval">
        <corners android:radius="@dimen/size_30dp" />
    </shape>
</item>
<item>
    <rotate
        android:fromDegrees="35"
        android:pivotX="0%"
        android:pivotY="100%">
        <shape android:shape="rectangle">
            <solid android:color="@color/textColorGreyExtraLight" />
        </shape>
    </rotate>
</item>
</layer-list>

This approach actually messed up the UI, also i have compromise on the rounded edges,

So i there any way i can draw this effect using XML?

Any kind of help will be greatly appreciated.

FYI The width and height of drawable will vary, so the diagonal should be always left bottom edge to right top edge.

Thanks

like image 442
Sanoop Surendran Avatar asked Oct 12 '17 07:10

Sanoop Surendran


3 Answers

Try this:

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

<stroke android:color="#0a0909"
    android:width="2dp"/>

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

<gradient android:angle="45"
    android:type="linear"
    android:startColor="#d61a1a"
    android:endColor="#19f269"
    android:useLevel="false"/>

<size android:height="250dp"
    android:width="250dp"/>
</shape>

you will get output like this

like image 170
Raghav Sharma Avatar answered Oct 23 '22 16:10

Raghav Sharma


While you asked for an XML based solution, I was intrigued to build a custom Drawable to solve your issue. It basically just draws two shapes using custom paths and a border around them. If you add setters for the colors or make them constructor parameters, you'll get the flexibility you need for when the applications theme changes. Feel free to ignore this if XML is the way to go for you, I had fun building it. :)

It looks like this:

Screenshot of custom drawable

public class CustomDrawable extends Drawable {

    private final int mBorderWidth;
    private final int mCornerRadius;

    private final Paint mBorderPaint = new Paint();
    private final Paint mTopLeftShapePaint = new Paint();
    private final Paint mBottomRightShapePaint = new Paint();

    private final RectF mBorderRect = new RectF();

    private final Path mTopLeftShapePath = new Path();
    private final Path mBottomRightShapePath = new Path();

    public CustomDrawable() {
        mBorderWidth = 8;
        mCornerRadius = 64;

        mTopLeftShapePaint.setColor(Color.parseColor("#00a2e8"));
        mTopLeftShapePaint.setStyle(Paint.Style.FILL);
        mTopLeftShapePaint.setAntiAlias(true);

        mBottomRightShapePaint.setColor(Color.parseColor("#3f48cc"));
        mBottomRightShapePaint.setStyle(Paint.Style.FILL);
        mBottomRightShapePaint.setAntiAlias(true);

        mBorderPaint.setColor(Color.parseColor("#3f48cc"));
        mBorderPaint.setStyle(Paint.Style.STROKE);
        mBorderPaint.setStrokeWidth(mBorderWidth);
        mBorderPaint.setAntiAlias(true);
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);

        mBorderRect.set(bounds);
        mBorderRect.inset(mBorderWidth / 2, mBorderWidth / 2);

        calculatePaths();
    }

    private void calculatePaths() {
        // Calculate position of the four corners
        RectF topLeftCorner = new RectF(mBorderRect.left, mBorderRect.top, mBorderRect.left + 2 * mCornerRadius, mBorderRect.top + 2 * mCornerRadius);
        RectF topRightCorner = new RectF(mBorderRect.right - 2 * mCornerRadius, mBorderRect.top, mBorderRect.right, mBorderRect.top + 2 * mCornerRadius);
        RectF bottomLeftCorner = new RectF(mBorderRect.left, mBorderRect.bottom - 2 * mCornerRadius, mBorderRect.left + 2 * mCornerRadius, mBorderRect.bottom);
        RectF bottomRightCorner = new RectF(mBorderRect.right - 2 * mCornerRadius, mBorderRect.bottom - 2 * mCornerRadius, mBorderRect.right, mBorderRect.bottom);

        // Calculate position of intersections of diagonal line and top-right / bottom-left corner
        PointF topRightCornerIntersection = calculateCircleCoordinate(topRightCorner.centerX(), topRightCorner.centerY(), 315);
        PointF bottomLeftCornerIntersection = calculateCircleCoordinate(bottomLeftCorner.centerX(), bottomLeftCorner.centerY(), 135);

        // Build top left shape
        mTopLeftShapePath.reset();
        mTopLeftShapePath.moveTo(topLeftCorner.left, topLeftCorner.centerY());
        mTopLeftShapePath.lineTo(bottomLeftCorner.left, bottomLeftCorner.centerY());
        mTopLeftShapePath.arcTo(bottomLeftCorner, -180, -45, false);
        mTopLeftShapePath.lineTo(topRightCornerIntersection.x, topRightCornerIntersection.y);
        mTopLeftShapePath.arcTo(topRightCorner, -45, -45, false);
        mTopLeftShapePath.lineTo(topLeftCorner.centerX(), topLeftCorner.top);
        mTopLeftShapePath.arcTo(topLeftCorner, -90, -90, false);

        // Build bottom right shape
        mBottomRightShapePath.reset();
        mBottomRightShapePath.moveTo(bottomLeftCorner.centerX(), bottomLeftCorner.bottom);
        mBottomRightShapePath.lineTo(bottomRightCorner.centerX(), bottomRightCorner.bottom);
        mBottomRightShapePath.arcTo(bottomRightCorner, 90, -90, false);
        mBottomRightShapePath.lineTo(topRightCorner.right, topRightCorner.centerY());
        mBottomRightShapePath.arcTo(topRightCorner, 0, -45, false);
        mBottomRightShapePath.lineTo(bottomLeftCornerIntersection.x, bottomLeftCornerIntersection.y);
        mBottomRightShapePath.arcTo(bottomLeftCorner, 135, -45, false);
    }

    private PointF calculateCircleCoordinate(float centerX, float centerY, double angdeg) {
        double angle = Math.toRadians(angdeg);
        double x = centerX + mCornerRadius * Math.cos(angle);
        double y = centerY + mCornerRadius * Math.sin(angle);
        return new PointF((float) x, (float) y);
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        canvas.drawPath(mTopLeftShapePath, mTopLeftShapePaint);
        canvas.drawPath(mBottomRightShapePath, mBottomRightShapePaint);
        canvas.drawRoundRect(mBorderRect, mCornerRadius, mCornerRadius, mBorderPaint);
    }

    @Override
    public void setAlpha(int alpha) {
    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {
    }

    @Override
    public int getOpacity() {
        return PixelFormat.UNKNOWN;
    }
}

Disclaimer: this quick draft doesn't cover edge-cases, e.g. a corner radius that is too large for the given view bounds.

like image 29
Henning Dodenhof Avatar answered Oct 23 '22 16:10

Henning Dodenhof


I have a library to do that. Basicilly I draw 2 Triangles and you can specify the direction left corner to right corner or right corner to left corner.

Library

like image 1
extmkv Avatar answered Oct 23 '22 17:10

extmkv