Skip to main content

Command Palette

Search for a command to run...

Take Control of Linear gradient with positions array

Published
4 min read
Take Control of Linear gradient with positions array
T

Professional android developer since 7+ years. https://www.linkedin.com/in/tejaswib91/

Applying gradient through XML, has a limitation on number of colors to three, and the position of colors like startColor,centerColor and endColor, whereas with LinearGradient object applied to a shader for Paint in a canvas, we can overcome them.

Let’s see how what can we do with it.

In canvas, a Paint object takes a shader/gradient for applying multiple colors [colors array].

Here’s the Api of LinearGradient from android.graphics package:

image.png

image.png

A linear gradient object takes a nullable array.

The topic under discussion here is the “positions” array.

If one specifies null for positions, the colors are evenly distributed [linear growth of gradient] along the length of the gradient as show below:

even distribution.png

The above effect is achieved using 4 colors used as gradient, and with null passed as argument for positions. If you closely observe, each color has got equal portion, here 25% of length, along the rect Here's the code for it:

class CustomView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    private val trackHeight=50f
    // set left, top, right, bottom positions/bounds of the rectangle
    private val rect=RectF(100f,100f,600f,150f)
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        strokeWidth = trackHeight
    }
    private val colorsArray =
        intArrayOf(Color.RED, Color.BLUE, Color.YELLOW,
            Color.parseColor("#FFA500"))// orange color hex code

    // apply gradient from left to right of the rectangle along X axis , from top to bottom along Y axis
    private val paintShader = LinearGradient(
        rect.left,
        rect.top,
        rect.right,
        rect.bottom,
        colorsArray,
        null,//evenly distribute colors along the whole length of gradient
        Shader.TileMode.CLAMP
    )

Now, what if we need to specify which color should occupy how much portion of the available space i.e. distribution of colors? / non-linear growth of gradient? Here's the position values array comes into play.

Suppose, I want red color to have 60% of length from starting X co-ordinate, then blue color to have next 10% of space from previous color's ending X co-ordinate, then yellow color to occupy next 15% of space and then Orange color to have 15% of the next space till the end, this is how you specify the positions array:

private val colorsArray =
        intArrayOf(Color.RED, Color.BLUE, Color.YELLOW,
            Color.parseColor("#FFA500"))// orange color hex code

 private val colorPositions= floatArrayOf(0.6f,0.7f,0.85f,1f)

Note: No of Colors in colorsArray and values in positions array must be of equal length, increasing in value and only even no of entries in both of them. [can't specify 1, or 3 or so on no of colors in odd no entries]

Non conforming to the above requirement throws an exception!

Let's pass colorPositions to LinearGradient as shader to paint instead of null as follows:

// apply gradient from left to right of the rectangle along X axis , from top to bottom along Y axis
    private val paintShader = LinearGradient(
        rect.left,
        rect.top,
        rect.right,
        rect.bottom,
        colorsArray,
        colorPositions,// distribution of colors along the length of gradient.
        Shader.TileMode.CLAMP
    )

This is what you would notice:

image.png

Also, instead of using multiple colors, we can use a multiple opacity values for a single color code.

In this case too, if you pass null, you'd get even distribution of colors. But for requirement having non-linear growth of the opacity of a single color you can specify the positions array with the requirement.

Here's how we would do it:

//pink color, also show with two colors too.
    private val singleColorWithVaryingOpacitiesArray = intArrayOf(
        Color.parseColor("#00BF6793"),//pink with 0% opacity
        Color.parseColor("#CCBF6793"),//pink with 80% opacity
        Color.parseColor("#FFBF6793")//pink with 100% opacity
    )
    private val colorPositions= floatArrayOf(0.6f,0.9f,1f)

    // apply gradient from left to right of the rectangle along X axis , from top to bottom along Y axis
    private val paintShader = LinearGradient(
        rect.left,
        rect.top,
        rect.right,
        rect.bottom,
        singleColorWithVaryingOpacitiesArray,
        colorPositions,// distribution of colors along the length of gradient.
        Shader.TileMode.CLAMP
    )
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.drawRect(rect, paint.apply {
            color = Color.BLACK
        })

            canvas?.drawRect(rect, paint.apply {
            shader = paintShader
       // Used blending with porter-duff mode so that underlying rectangle's black color is visible for 
      //portion having for 0% opacity values
            xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)
        })

Had we passed null, the output would be like: image.png and if we passed positions with requirement : like till 60% of space from starting X co-ordinate has 0% opacity(not visible), followed by 80% opacity till next 30% of space, and the 100% opacity(fully visible) for remaining 10% of space or till the end, the output would have been: image.png