10

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

Sanoop Surendran
  • 3,484
  • 4
  • 28
  • 49

5 Answers5

17

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

AskNilesh
  • 67,701
  • 16
  • 123
  • 163
Raghav Sharma
  • 324
  • 1
  • 7
  • 3
    See Raghav, I totally appreciate your effort, in case you haven't read my question properly, i want to have a diagonal line, not the merged effect, Thanks :) – Sanoop Surendran Oct 12 '17 at 09:09
3

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.

1

Seems, simplest way is to draw background in SVG format and directly use it as background, or, if you want to change colors programmatically, you can convert SVG to vector drawable (for example with this tool) and than use as background. For your example VectorDrawable (two_colored_roundrect.xml) can be like this:

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="550dp"
        android:height="390dp"
        android:viewportWidth="550"
        android:viewportHeight="390">

    <path
        android:fillColor="#00bfbf"
        android:strokeColor="#003f7f"
        android:strokeWidth="5"
        android:pathData="M1.50016,66.028801l0,0c0,-35.411501 28.03274,-64.118481
62.611443,-64.118481l28.463097,0l0,0l136.615303,0l256.152985,0c16.605011,0
32.529999,6.75751 44.274994,18.780581c11.744995,12.0231 18.338013,28.3319
18.338013,45.3379l0,160.295204l0,0l0,96.177002l0,0c0,35.410004
-28.03302,64.11499
-62.613007,64.11499l-256.152985,0l-136.615303,0l-28.463097,0c-34.578703,0
-62.611443,-28.704987 -62.611443,-64.11499l0,0l0,-96.177002l0,0l0,-160.295204z" />
    <path
        android:strokeColor="#003f7f"
        android:strokeWidth="5"
        android:pathData="M 19.5 368.149994 L 529.5 21.149994" />
    <path
        android:fillColor="#003f7f"
        android:strokeColor="#003f7f"
        android:strokeWidth="5"
        android:pathData="M529.617981,20.690901c11.744995,12.0231 18.338013,28.3319
18.338013,45.3379l0,160.295204l0,0l0,96.177002l0,0c0,35.410004
-28.03302,64.11499
-62.613007,64.11499l-256.152985,0l-136.615303,0l-28.463097,0c-17.289352,0
-32.942213,-7.176239 -44.272736,-18.778748l509.779114,-347.146349z" />
</vector>

and layout xml file like that:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="{YOUR_CONTEXT}">

    <View
        android:layout_width="300dp"
        android:layout_height="200dp"
        android:layout_centerInParent="true"
        android:background="@drawable/two_colored_roundrect"
        />

</RelativeLayout>

(position and dimensions of View - just for example) and as result you should get something like this:

Two-colored round rect background

And you can change colors of path stroke and fill, like in answers for this question.

Andrii Omelchenko
  • 13,183
  • 12
  • 43
  • 79
1

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

extmkv
  • 1,991
  • 1
  • 18
  • 36
  • 1
    Awesome!! I have one suggestion though, I don't know if it's already possible (haven't tried the code or viewed yet). Instead of taking colors, can I use drawables? – Sanoop Surendran Feb 27 '18 at 16:48
0

So, according to this post it's now possible to use colour resources with vector drawables. So what you have to do is:

  1. Create the shape you want with your favourite software (Illustrator for example)
  2. export it as an SVG file
  3. Convert it into an XML using tools like this one or Android Studio directly.

Now that you have your drawable, replace all the colours by the resources you want to use. (e.g: android:fillColor="#0000" become android:fillColor="@color/custom_black").

Then in your ImageView, instead of android:src="" use app:srcCompat to set the drawable.

Don't forget to add vectorDrawables.useSupportLibrary = true under defaultConfig in your gradle file.

I've tested on both API 19 and 24, and it works.

EDIT: I'm using Support Library version 26.0.2

Eselfar
  • 3,759
  • 3
  • 23
  • 43
  • Thanks, Will try this out and will let you know. Also can you post the screenshot of the output you have received? – Sanoop Surendran Oct 12 '17 at 09:27
  • Can you please post the screenshot of your output? – Sanoop Surendran Oct 12 '17 at 09:35
  • I haven't generated the exact same output. I've tried with another drawable that I have. But it will work the same. The only thing you need to do is to create the SVG and to follow the instructions. The SVG need obviously to look exactly like the image in your post. – Eselfar Oct 12 '17 at 12:55