Vivek Sasidharan
Vivek Sasidharan

Reputation: 1295

Programmatically create a button with a gradient stroke?

I am working on a creating a button with no fill, but a gradient stroke. For reference, here is the end result I am after:

enter image description here

I would like to know how to create such a button with a gradient stroke and no fill programmatically. I looked over GradientDrawable class and the setStroke() method in it. None seem to allow this. Is there any way to programmatically perform this or is it not possible at all?

Upvotes: 2

Views: 2706

Answers (3)

Augusto Carmo
Augusto Carmo

Reputation: 4964

Here is a further step from what @lamat8 contributed:

import android.graphics.*
import android.graphics.Paint.ANTI_ALIAS_FLAG
import android.graphics.Path.FillType
import android.graphics.drawable.Drawable
import androidx.annotation.NonNull
import java.lang.RuntimeException


class GradientDrawable(
    private var strokeStartColor: Int,
    private var strokeEndColor: Int,
    private var fillStartColor: Int,
    private var fillEndColor: Int,
    private var strokeWidth: Float,
    private var radius: Float,
    private var strokeGradientDirection: Direction,
    private var fillGradientDirection: Direction
) : Drawable() {

    enum class Direction {
        LEFT_RIGHT,
        TOP_BOTTOM,
        RIGHT_LEFT,
        BOTTOM_TOP,
        TL_BR,
        TR_BL,
        BR_TL,
        BL_TR
    }

    private val strokePaint: Paint = Paint(ANTI_ALIAS_FLAG)
    private val fillPaint: Paint = Paint(ANTI_ALIAS_FLAG)
    private val strokeOuterRect = RectF()
    private val fillRect = RectF()
    private val path = Path()

    init {
        strokePaint.style = Paint.Style.FILL
        path.fillType = FillType.EVEN_ODD
    }

    override fun onBoundsChange(bounds: Rect) {
        super.onBoundsChange(bounds)

        path.reset()

        strokeOuterRect.set(bounds)
        fillRect.set(
            bounds.left + strokeWidth,
            bounds.top + strokeWidth,
            bounds.right - strokeWidth,
            bounds.bottom - strokeWidth
        )

        path.addRoundRect(
            strokeOuterRect,
            radius,
            radius,
            Path.Direction.CW
        )

        path.addRoundRect(
            fillRect,
            radius,
            radius,
            Path.Direction.CW
        )
    }

    override fun draw(@NonNull canvas: Canvas) {
        var x0: Float
        var y0: Float
        var x1: Float
        var y1: Float

        // drawing the stroke

        when (strokeGradientDirection) {
            Direction.LEFT_RIGHT -> {
                x0 = strokeOuterRect.left
                y0 = strokeOuterRect.centerY()
                x1 = strokeOuterRect.right
                y1 = strokeOuterRect.centerY()
            }
            Direction.TOP_BOTTOM -> {
                x0 = strokeOuterRect.centerX()
                y0 = strokeOuterRect.top
                x1 = strokeOuterRect.centerX()
                y1 = strokeOuterRect.bottom
            }
            Direction.RIGHT_LEFT -> {
                x0 = strokeOuterRect.right
                y0 = strokeOuterRect.centerY()
                x1 = strokeOuterRect.left
                y1 = strokeOuterRect.centerY()
            }
            Direction.BOTTOM_TOP -> {
                x0 = strokeOuterRect.centerX()
                y0 = strokeOuterRect.bottom
                x1 = strokeOuterRect.centerX()
                y1 = strokeOuterRect.top
            }
            Direction.TL_BR -> {
                x0 = strokeOuterRect.left
                y0 = strokeOuterRect.top
                x1 = strokeOuterRect.right
                y1 = strokeOuterRect.bottom
            }
            Direction.TR_BL -> {
                x0 = strokeOuterRect.right
                y0 = strokeOuterRect.top
                x1 = strokeOuterRect.left
                y1 = strokeOuterRect.bottom
            }
            Direction.BR_TL -> {
                x0 = strokeOuterRect.right
                y0 = strokeOuterRect.bottom
                x1 = strokeOuterRect.left
                y1 = strokeOuterRect.top
            }
            Direction.BL_TR -> {
                x0 = strokeOuterRect.left
                y0 = strokeOuterRect.bottom
                x1 = strokeOuterRect.right
                y1 = strokeOuterRect.top
            }
        }

        strokePaint.shader = LinearGradient(
            x0,
            y0,
            x1,
            y1,
            strokeStartColor,
            strokeEndColor,
            Shader.TileMode.MIRROR
        )

        canvas.drawPath(path, strokePaint)

        // filling the shape

        when (fillGradientDirection) {
            Direction.LEFT_RIGHT -> {
                x0 = fillRect.left
                y0 = fillRect.centerY()
                x1 = fillRect.right
                y1 = fillRect.centerY()
            }
            Direction.TOP_BOTTOM -> {
                x0 = fillRect.centerX()
                y0 = fillRect.top
                x1 = fillRect.centerX()
                y1 = fillRect.bottom
            }
            Direction.RIGHT_LEFT -> {
                x0 = fillRect.right
                y0 = fillRect.centerY()
                x1 = fillRect.left
                y1 = fillRect.centerY()
            }
            Direction.BOTTOM_TOP -> {
                x0 = fillRect.centerX()
                y0 = fillRect.bottom
                x1 = fillRect.centerX()
                y1 = fillRect.top
            }
            Direction.TL_BR -> {
                x0 = fillRect.left
                y0 = fillRect.top
                x1 = fillRect.right
                y1 = fillRect.bottom
            }
            Direction.TR_BL -> {
                x0 = fillRect.right
                y0 = fillRect.top
                x1 = fillRect.left
                y1 = fillRect.bottom
            }
            Direction.BR_TL -> {
                x0 = fillRect.right
                y0 = fillRect.bottom
                x1 = fillRect.left
                y1 = fillRect.top
            }
            Direction.BL_TR -> {
                x0 = fillRect.left
                y0 = fillRect.bottom
                x1 = fillRect.right
                y1 = fillRect.top
            }
        }

        fillPaint.shader = LinearGradient(
            x0,
            y0,
            x1,
            y1,
            fillStartColor,
            fillEndColor,
            Shader.TileMode.MIRROR
        )

        canvas.drawRoundRect(
            fillRect,
            radius,
            radius,
            fillPaint
        )
    }

    override fun setAlpha(alpha: Int) {
        strokePaint.alpha = alpha
        fillPaint.alpha = alpha

        invalidateSelf()
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        throw RuntimeException("Color filter cannot be set to this Drawable")
    }

    override fun getOpacity(): Int {
        return PixelFormat.TRANSLUCENT
    }

    fun setStrokeColors(startColor: Int, endColor: Int) {
        this.strokeStartColor = startColor
        this.strokeEndColor = endColor

        invalidateSelf()
    }

    fun setFillColors(startColor: Int, endColor: Int) {
        this.fillStartColor = startColor
        this.fillEndColor = endColor

        invalidateSelf()
    }

    fun setRadius(radius: Float) {
        this.radius = radius

        invalidateSelf()
    }

    fun setStrokeWidth(width: Float) {
        this.strokeWidth = width

        invalidateSelf()
    }
}

That class adds the following features:

  • direction to the stroke gradient;
  • fill gradient, and;
  • direction to the fill gradient;

Upvotes: 1

Iamat8
Iamat8

Reputation: 3906

I have tried something for you.. Use mRect.set to set path and mPath.addRoundRectadd rectangle.Use setShader for strock purpose link

Drawable class:

public class CustomDrawable extends Drawable {
Paint mPaint;
int startColor, endColor, mBorderWidth, mBorderRadius;
RectF mRect;
Path mPath;

public CustomDrawable(int startColor, int endColor, int borderWidth, int borderRadius) {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setStyle(Paint.Style.FILL);

    mPath = new Path();
    mPath.setFillType(Path.FillType.EVEN_ODD);

    mRect = new RectF();
    this.startColor = startColor;
    this.endColor = endColor;

    mBorderWidth = borderWidth;
    mBorderRadius = borderRadius;
}

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

    // out rect
    mRect.set(bounds.left + mBorderWidth, bounds.top + mBorderWidth, bounds.right - mBorderWidth, bounds.bottom - mBorderWidth);
    mPath.addRoundRect(mRect, mBorderRadius, mBorderRadius, Path.Direction.CW);

    // inner rect
    mRect.set(bounds.left + 20, bounds.top + 20, bounds.right - 20, bounds.bottom - 20);
    mPath.addRoundRect(mRect, mBorderRadius, mBorderRadius, Path.Direction.CW);
}

@Override
public void draw(@NonNull Canvas canvas) {
    // kind of strock 
    mPaint.setShader(new LinearGradient(0, 0, 0, 100, startColor, endColor, Shader.TileMode.MIRROR));
    canvas.drawPath(mPath, mPaint);
}
@Override
public void setAlpha(int alpha) { mPaint.setAlpha(alpha);}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {mPaint.setColorFilter(colorFilter);}
@Override
public int getOpacity() {return PixelFormat.TRANSLUCENT;}
}

Main :

Button but = ((Button)findViewById(R.id.but));
but.setBackground(new CustomDrawable(Color.parseColor("#FD659B"),
       Color.parseColor("#F76E63"),
       but.getPaddingLeft(), 100));

layout :

<Button
    android:id="@+id/but"
    android:layout_width="300dp"
    android:layout_height="80dp"
    android:background="@android:color/transparent"
    android:layout_centerInParent="true"
    android:text="Signin"/>

Upvotes: 4

rafa
rafa

Reputation: 1369

I don't think we can set gradient for Stroke but we can hack for the same using layer list.

you can create below drawable xml with the gradient you wanted

<item>
    <shape android:shape="rectangle">
        <!-- gradient for the stroke is set below -->
        <gradient
            android:angle="180"
            android:startColor="#555994"
            android:endColor="#b5b6d2"
            android:type="linear" />
        <corners android:radius="4dp"></corners>
    </shape>

</item>
<!-- stroke width has to be adjusted by setting left, right , top and bottom -->
<item android:left="4dp" android:right="4dp"
    android:top="4dp" android:bottom="4dp">
    <shape
        android:shape="rectangle">
        <solid android:color="@android:color/white"/>
    </shape>
</item>

you can set the above drawable in layout/programmatically

button.setBackgroundResource(R.drawable.button_background);

Upvotes: -1

Related Questions