Android Code Snippet Draw objects on Canvas with gradients

SubNames: DrawTextLinearGradient, DrawTextLinearGradient1, DrawLineLinearGradient, DrawLineLinearGradient1

Description:

It is possible to apply gradients directly to objects drawn on a canvas using the Andorid Shader subclasses: LinearGradient, RadialGradient, SweepGradient and JavaObject.

These Subroutines are examples of doing so using LinearGradient:

B4X:
Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.

End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.
    Dim Canvas As Canvas
End Sub

Sub Activity_Create(FirstTime As Boolean)
    'Do not forget to load the layout file created with the visual designer. For example:
    'Activity.LoadLayout("Layout1")
    Canvas.Initialize(Activity)

    DrawTextLinearGradient(Canvas,100,100,"Test of LinearGradient",50,Colors.Blue,Colors.Cyan,"CLAMP")

    DrawTextLinearGradient1(Canvas,100,200,"Second Test of LinearGradient",50,Array As Int(Colors.Blue,Colors.Yellow,Colors.Red,Colors.Cyan),Null,"CLAMP")

    DrawLineLinearGradient(Canvas,100,200,300,400,5dip,Colors.Blue,Colors.Cyan,"CLAMP")

    DrawLineLinearGradient1(Canvas,100,300,300,400,5dip,Array As Int(Colors.Blue,Colors.Yellow,Colors.Cyan),Null,"CLAMP")

    Activity.Invalidate


End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub
Sub DrawTextLinearGradient(Canv As Canvas,X As Float,Y As Float,Text As String,TextSize As Float,StartColor As Int, EndColor As Int,TileMode As String)

    Dim x1 As Float
    'Arbitrary Y point somewhere in the displayed view
    Dim y1 As Float = y + 20
    Dim Paint,LinearGradient,CanvJO As JavaObject
    CanvJO = Canv
    CanvJO = CanvJO.GetField("canvas")

    Paint.InitializeNewInstance("android.graphics.Paint",Null)
    Paint.RunMethod("setTextSize",Array As Object(TextSize))

    'Set Length of shader to match text
    x1 = x + (Paint.RunMethod("measureText",Array(Text)))

    LinearGradient.InitializeNewInstance("android.graphics.LinearGradient",Array As Object(X,Y,x1,y1,StartColor,EndColor,TileMode))
    Paint.RunMethod("setShader",Array As Object(LinearGradient))

    CanvJO.RunMethod("drawText",Array As Object(Text,X,Y,Paint))


End Sub


Sub DrawTextLinearGradient1(Canv As Canvas,X As Float,Y As Float,Text As String,TextSize As Float,Color() As Int,Positions() As Float,TileMode As String)

    Dim x1 As Float
    'Arbitrary Y point somewhere in the displayed view
    Dim y1 As Float = Y + 20
    Dim Paint,LinearGradient,CanvJO As JavaObject
    CanvJO = Canv
    CanvJO = CanvJO.GetField("canvas")

    Paint.InitializeNewInstance("android.graphics.Paint",Null)
    Paint.RunMethod("setTextSize",Array As Object(TextSize))

    'Set Length of shader to match text
    x1 = x + (Paint.RunMethod("measureText",Array(Text)))


    LinearGradient.InitializeNewInstance("android.graphics.LinearGradient",Array As Object(X,Y,x1,y1,Color,Positions,TileMode))
    Paint.RunMethod("setShader",Array As Object(LinearGradient))

    CanvJO.RunMethod("drawText",Array As Object(Text,X,Y,Paint))


End Sub

Sub DrawLineLinearGradient(Canv As Canvas,X As Float,Y As Float,X1 As Float,Y1 As Float,StrokeWidth As Float,StartColor As Int, EndColor As Int,TileMode As String)

    Dim Paint,LinearGradient,CanvJO As JavaObject
    CanvJO = Canv
    CanvJO = CanvJO.GetField("canvas")

    Paint.InitializeNewInstance("android.graphics.Paint",Null)
    Paint.RunMethod("setStrokeWidth",Array(StrokeWidth))
    LinearGradient.InitializeNewInstance("android.graphics.LinearGradient",Array As Object(X,Y,X1,Y1,StartColor,EndColor,TileMode))
    Paint.RunMethod("setShader",Array As Object(LinearGradient))

    CanvJO.RunMethod("drawLine",Array As Object(X,Y,X1,Y1,Paint))
End Sub

Sub DrawLineLinearGradient1(Canv As Canvas,X As Float,Y As Float,X1 As Float,Y1 As Float,StrokeWidth As Float,Color() As Int,Positions() As Float,TileMode As String)

    Dim Paint,LinearGradient,CanvJO As JavaObject
    CanvJO = Canv
    CanvJO = CanvJO.GetField("canvas")

    Paint.InitializeNewInstance("android.graphics.Paint",Null)
    Paint.RunMethod("setStrokeWidth",Array(StrokeWidth))
    LinearGradient.InitializeNewInstance("android.graphics.LinearGradient",Array As Object(X,Y,X1,Y1,Color,Positions,TileMode))
    Paint.RunMethod("setShader",Array As Object(LinearGradient))

    CanvJO.RunMethod("drawLine",Array As Object(X,Y,X1,Y1,Paint))
End Sub

Copy and paste the whole code block into a new project for a demo.

See the documentation in the above links and the associated Paint and Canvas object documentation for more information.

Depends on: JavaObject

Tags: Canvas, Gradient Draw
 
Last edited:

Chris Lee

Member
Licensed User
Great code, I just wondered if you could help me with StrokeWidth? Reason I ask is that all the other dimensions work as anticipated.
For StrokeWidth on my Galaxy S6 I'm getting exactly 50% of the screen if I select 100%x.

For example:

B4X:
DrawLineLinearGradient(Canvas,0,0,0,100%y,100%x,Colors.Magenta,Colors.LightGray,"CLAMP")

If I supply the width of the screen in pixels instead, again 50%

B4X:
DrawLineLinearGradient(Canvas,0,0,0,100%y,1440,Colors.Magenta,Colors.LightGray,"CLAMP")

I can enter 100%x*2 which works fine, but I've no idea if that will work consistently across all devices?

Thanks.
 

NeoTechni

Well-Known Member
Licensed User
Longtime User
I tried

B4X:
'Sets the paint method of a canvas to a gradient, the next thing drawn will be in the gradient
'GradientType: 0=Linear (needs X1,Y1/X2,Y2), 1=Radial (needs X1,Y1 and X2=Radius), 2=Sweep (needs X1,Y1)
'TileMode:   0=CLAMP (replicate the edge color if the shader draws outside of its original bounds)
'           1=MIRROR (repeat the shader's image horizontally and vertically, alternating mirror images so that adjacent images always seam)
'           2=REPEAT (repeat the shader's image horizontally and vertically)
Sub DrawGradient(BG As Canvas, X1 As Int, Y1 As Int, X2 As Int, Y2 As Int, TheColors() As Int,Positions() As Float, StrokeWidth As Float, TileMode As String, GradientType As Int)
    Dim Paint As JavaObject,LinearGradient As JavaObject,CanvJO As JavaObject = BG
    CanvJO = CanvJO.GetField("canvas")   
   Select Case GradientType
       Case 0: LinearGradient.InitializeNewInstance("android.graphics.LinearGradient",Array As Object(X1,Y1, X2,Y2,TheColors,Positions,TileMode))'Linear
       Case 1: LinearGradient.InitializeNewInstance("android.graphics.RadialGradient",Array As Object(X1,Y1, X2,TheColors,Positions,TileMode))'Radial
       Case 2: LinearGradient.InitializeNewInstance("android.graphics.SweepGradient", Array As Object(X1,Y1,TheColors,Positions,TileMode))'Sweep
   End Select
   Paint.InitializeNewInstance("android.graphics.Paint",Null)
   Paint.RunMethod("setStrokeWidth",    Array As Object(StrokeWidth))
   Paint.RunMethod("setAntiAlias",    Array As Object(True))
    Paint.RunMethod("setShader",       Array As Object(LinearGradient))
End Sub

But I get

java.lang.RuntimeException: Constructor not found.
at anywheresoftware.b4j.object.JavaObject.InitializeNewInstance(JavaObject.java:94)
at com.omnicorp.scifiui.lcar._drawgradient(lcar.java:2430)
at java.lang.reflect.Method.invoke(Native Method)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:186)
at anywheresoftware.b4a.keywords.Common$11.run(Common.java:1135)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
java.lang.RuntimeException: Constructor not found.

And this is testing with gradient type = 0 (android.graphics.LinearGradient)
 

DonManfred

Expert
Licensed User
Longtime User
You should always create a new thread for each question you have. Posting to an existing thread is not the right way.

How are you calling this sub? Post the code please.

I guess it crashes as your TheColors and Positions needs to be
Public constructors
LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile)
Create a shader that draws a linear gradient along a line.
 

NeoTechni

Well-Known Member
Licensed User
Longtime User
I figured it'd be better to post directly to the person who made the code, as it's more likely he'd find it.

TheColors and Positions already match those types...
TheColors() As Int,Positions() As Float

I'll recheck the code.
 

NeoTechni

Well-Known Member
Licensed User
Longtime User
Turns out it was cause I keep assuming enums work like they did in VB6 (auto-incrementing numbers starting from 0) when Android/Google uses strings. Which is odd cause they'd be slower to use than numbers...
 

NeoTechni

Well-Known Member
Licensed User
Longtime User
I also figured he'd want the code to draw the other 2 gradient types

B4X:
'Sets the paint method of a canvas to a gradient, the next thing drawn will be in the gradient
'GradientType: 0=Linear (needs X1,Y1/X2,Y2), 1=Radial (needs X1,Y1 and X2=Radius), 2=Sweep (needs X1,Y1)
'TileModeInt:   0=CLAMP (replicate the edge color if the shader draws outside of its original bounds)
'               1=MIRROR (repeat the shader's image horizontally and vertically, alternating mirror images so that adjacent images always seam)
'               2=REPEAT (repeat the shader's image horizontally and vertically)
'TheColors: There must be at least 2 colors in the array. This value must never be Null.
'Positions: If NULL, then the colors are automatically spaced evenly.
Sub DrawGradient4(BG As Canvas, X1 As Float, Y1 As Float, X2 As Float, Y2 As Float, TheColors() As Int,Positions() As Float, StrokeWidth As Float, TileModeInt As Int, GradientType As Int)
    Dim Paint As JavaObject,LinearGradient As JavaObject, CanvJO As JavaObject = BG, TileMode As String = "CLAMP"
   Select Case TileModeInt
       Case 1: TileMode = "MIRROR"
       Case 2: TileMode = "REPEAT"
   End Select
   'LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile)
   'RadialGradient(float centerX, float centerY, float radius, int[] colors, float[] stops, Shader.TileMode tileMode)
   'SweepGradient(float cx, float cy, int[] colors, float[] positions)
   Select Case GradientType
       Case 0: LinearGradient.InitializeNewInstance("android.graphics.LinearGradient",   Array As Object(X1,Y1, X2,Y2,TheColors,Positions,TileMode))'Linear
       Case 1: LinearGradient.InitializeNewInstance("android.graphics.RadialGradient",   Array As Object(X1,Y1, X2,TheColors,Positions,TileMode))'Radial
       Case 2: LinearGradient.InitializeNewInstance("android.graphics.SweepGradient",    Array As Object(X1,Y1,TheColors,Positions,TileMode))'Sweep
   End Select
   Paint.InitializeNewInstance("android.graphics.Paint",Null)
   Paint.RunMethod("setStrokeWidth",    Array As Object(StrokeWidth))
   Paint.RunMethod("setAntiAlias",    Array As Object(True))
    Paint.RunMethod("setShader",       Array As Object(LinearGradient))
   CanvJO = CanvJO.GetField("canvas")
   CanvJO.RunMethod("drawLine",       Array As Object(X1,Y1,X2,Y2,Paint))
End Sub
 
Top