Android Tutorial StateListDrawable example

StateListDrawable is a drawable objects that holds other drawables. Based on the view's current state a child drawable is selected.
StateListDrawable holds a list of states. States can be "disabled", "enabled", "checked" and so on.
A state can also be a combination of other states (like enabled and pressed).

The StateListDrawable holds a list of states and drawables pairs. Whenever the view's state changes the list is scanned and the first item that matches the current state is chosen.
Matching means that all elements in the state item are included in the current state.
This means that an empty state item will match all current states.
The order of states added is very important as the first item matches will be chosen.

This example creates a ToggleButton and sets its two states (checked and unchecked) to two ColorDrawables.
B4X:
Sub Process_Globals

End Sub

Sub Globals

End Sub

Sub Activity_Create(FirstTime As Boolean)
    Dim tb As ToggleButton
    tb.Initialize("") 'no events will be caught
    Dim checked, unchecked As ColorDrawable
    checked.Initialize(Colors.Green, 10dip)
    unchecked.Initialize(Colors.Red, 10dip)

    Dim sld As StateListDrawable
    sld.Initialize
    sld.AddState(sld.State_Checked, checked)
    sld.AddState(sld.State_Unchecked, unchecked)
    tb.Background = sld
    tb.Checked = True
    tb.TextColor = Colors.Blue
    tb.TextSize = 20
    tb.Typeface = Typeface.DEFAULT_BOLD
    Activity.AddView(tb, 100dip, 100dip, 100dip, 100dip)
End Sub
Note that in this case the order is not important as no state is contained in another.

state_list1.png


state_list2.png
 

eatyourpeas

Member
Licensed User
Longtime User
Sorry - how do you apply this to say the panel_touched event, which does not have a .checked suffix applied to it?
 

eatyourpeas

Member
Licensed User
Longtime User
sorry - i wanted to change the state of a panel based on whether it had been touched or not. I solved my own problem though. sorry to clog up this thread....
many thanks
eyp
 

Roger Garstang

Well-Known Member
Licensed User
Longtime User
In trying to understand how this works, I made a little function to aid in creating a Gradient Button:

B4X:
Sub ButtonGradient(ColorList() As Int) As StateListDrawable
   ' Define a GradientDrawable for Enabled state
   Dim gdwEnabled As GradientDrawable
   gdwEnabled.Initialize("TOP_BOTTOM",ColorList)
   gdwEnabled.CornerRadius = 5
   ' Define a GradientDrawable for Pressed state
   Dim gdwPressed As GradientDrawable
   gdwPressed.Initialize("BOTTOM_TOP",ColorList)
   gdwPressed.CornerRadius = 7
   ' Define a GradientDrawable for Disabled state
   Dim gdwDisabled As GradientDrawable
   gdwDisabled.Initialize("TOP_BOTTOM", Array As Int(Colors.LightGray, Colors.DarkGray))
   gdwDisabled.CornerRadius = 5
   ' Define a StateListDrawable
   Dim stdGradient As StateListDrawable
   stdGradient.Initialize
   stdGradient.AddState2(Array As Int(stdGradient.State_enabled, -stdGradient.State_Pressed), gdwEnabled)
   stdGradient.AddState(stdGradient.State_Pressed, gdwPressed)
   stdGradient.AddState(stdGradient.State_Disabled, gdwDisabled)
   Return stdGradient
End Sub

This can be used like:

B4X:
newBtn.Background = ButtonGradient(Array As Int(Colors.RGB(50, 205, 50), Colors.RGB(0,128,0)))

to create a nice green button that flips the gradient when pressed and has a disabled state.

I used a few things as examples from both here and the manual as well as my own tweaking and making it create/dim the least amount of variables.

One thing I had some confusion understanding at first though is the -stdGradient.state_pressed stuff. I got that they are some type of int flags and for some reason they are combined in an array instead of using OR. So, that sets the enabled state when NOT Pressed. I would have expected to see Not() used though. This just negates an integer. Using some toast messages it appears the opposite values are negatives of each other like -State_Enabled = State_Disabled. It was a little confusing without testing to see how they worked. We have Checked/Unchecked and Enabled/Disabled, but Pressed and Selected are missing matching pairs. It might be a good thing to add the matching pairs to the StateListDrawable class in B4A so code can be cleaner.
 
Last edited:

Ben

Member
Licensed User
Longtime User
StateListDrawable with buttons

Hello!
I have tried to turn on and off a button following your example below, but it only appears the "on image" when I click on the button. How could I get the state-pressed image?
Thank you

Dim bdwOn,bdwoff As BitmapDrawable
Dim sldmotor As StateListDrawable
bdwOn.Initialize(LoadBitmap(File.DirAssets, "motoron.png"))
bdwoff.Initialize(LoadBitmap(File.DirAssets, "motoroff.png"))
sldmotor.Initialize
sldmotor.AddState(sldmotor.State_enabled, bdwOn)
sldmotor.AddState(sldmotor.State_pressed, bdwoff)
buttons(1).Background=sldmotor


StateListDrawable is a drawable objects that holds other drawables. Based on the view's current state a child drawable is selected.
StateListDrawable holds a list of states. States can be "disabled", "enabled", "checked" and so on.
A state can also be a combination of other states (like enabled and pressed).

The StateListDrawable holds a list of states and drawables pairs. Whenever the view's state changes the list is scanned and the first item that matches the current state is chosen.
Matching means that all elements in the state item are included in the current state.
This means that an empty state item will match all current states.
The order of states added is very important as the first item matches will be chosen.

This example creates a ToggleButton and sets its two states (checked and unchecked) to two ColorDrawables.
B4X:
Sub Process_Globals

End Sub

Sub Globals

End Sub

Sub Activity_Create(FirstTime As Boolean)
    Dim tb As ToggleButton
    tb.Initialize("") 'no events will be caught
    Dim checked, unchecked As ColorDrawable
    checked.Initialize(Colors.Green, 10dip)
    unchecked.Initialize(Colors.Red, 10dip)

    Dim sld As StateListDrawable
    sld.Initialize
    sld.AddState(sld.State_Checked, checked)
    sld.AddState(sld.State_Unchecked, unchecked)
    tb.Background = sld
    tb.Checked = True
    tb.TextColor = Colors.Blue
    tb.TextSize = 20
    tb.Typeface = Typeface.DEFAULT_BOLD
    Activity.AddView(tb, 100dip, 100dip, 100dip, 100dip)
End Sub
Note that in this case the order is not important as no state is contained in another.

state_list1.png


state_list2.png
 

Ben

Member
Licensed User
Longtime User
Please, I'd like to confirm the correct code for the statelistdrawables example in page 93 of users's guide. It seems that the last lines contain some errors.

' Define a StateListDrawable
Dim stdBitmap As StateListDrawable
stdBitmap.Initialize
Dim states(2) As Int
states(0) = stdBitmap.state_enabled
states(1) = -stdBitmap.state_pressed ?
stdBitmap.addState2(states, bdwEnabled) ?
Dim states(1) As Int ?
states(0) = stdBitmap.state_enabled ?
stdBitmap.addState2(states, bdwPressed)
' Set stdBitmap to button btnBitmap
btnBitmap.Background = stdBitmap

Thank you


Hello!
I have tried to turn on and off a button following your example below, but it only appears the "on image" when I click on the button. How could I get the state-pressed image?
Thank you

Dim bdwOn,bdwoff As BitmapDrawable
Dim sldmotor As StateListDrawable
bdwOn.Initialize(LoadBitmap(File.DirAssets, "motoron.png"))
bdwoff.Initialize(LoadBitmap(File.DirAssets, "motoroff.png"))
sldmotor.Initialize
sldmotor.AddState(sldmotor.State_enabled, bdwOn)
sldmotor.AddState(sldmotor.State_pressed, bdwoff)
buttons(1).Background=sldmotor
 

Wembly

Member
Licensed User
Longtime User
Hi

I've used this method with majority success but still a slight issue.

I am attempting to apply this approach to replace the graphics on a check box. My custom graphics are appearing as expected for both states (checked & unchecked) however what I am also seeing is the original android check box graphics over the top my custom graphics.

Any ideas why?

Thanks.

B4X:
    ckb.Initialize("")
    ckb.Checked = False
 
    Dim sld As StateListDrawable
    Dim bmpUnticked, bmpTicked As BitmapDrawable
 
    bmpUnticked.Initialize(LoadBitmap(File.DirAssets,"unticked_box.png"))
    bmpTicked.Initialize(LoadBitmap(File.DirAssets,"ticked_box.png"))
 
    sld.Initialize
    sld.AddState(sld.State_Checked, bmpTicked)
    sld.AddState(sld.State_Unchecked, bmpUnticked)
    ckb.Background = sld
 

Wembly

Member
Licensed User
Longtime User
Just re-checked my code and no there is only one line which adds the check box to the view - this is the entire procedure:

B4X:
Sub addCheckBox

    ckb.Initialize("")
    ckb.Checked = False
   
    Dim sld As StateListDrawable
    Dim bmpUnticked, bmpTicked As BitmapDrawable
   
    bmpUnticked.Initialize(LoadBitmap(File.DirAssets,"unticked_box.png"))
    bmpTicked.Initialize(LoadBitmap(File.DirAssets,"ticked_box.png"))
   
    sld.Initialize
    sld.AddState(sld.State_Checked, bmpTicked)
    sld.AddState(sld.State_Unchecked, bmpUnticked)
    ckb.Background = sld
   
    pnlBackGround.AddView(ckb,(cellWidth/100)*75, 5dip, (cellWidth/100)*20, (cellHeight/100)*20)

End Sub
 

phukol

Active Member
Licensed User
Longtime User
Im surprised that you were not quite aware of this problem. Yes the toggleButton will work the same, but how about for the radiobutton, it behaves the same like that of the Checkbox. If i were to implement the togglebutton for the functionality of the radiobutton, i need to make a code to check the state of my other radiobutton for it to work properly so that only one option can be selected, can there be any other alternative for radiobutton statelistdrawable?
 

stevel05

Expert
Licensed User
Longtime User
You could set the statelistdrawable to a radiobutton or checkbox using JavaObject as:

B4X:
Dim JO As JavaObject = CheckBox2
        JO.RunMethod("setButtonDrawable",Array As Object(sld))

Which seems to avoid the problem. The disadvantage of this is that the image will not be resized, so you need to provide the images already correctly sized.
 

phukol

Active Member
Licensed User
Longtime User
so will this affect my application if im going to use it for multi-resolution devices right?
 

stevel05

Expert
Licensed User
Longtime User
Possibly, although you could scale the bitmap before adding it to the statelistdrawable. Something like:

B4X:
Sub SetCBDrawable2FromAssets(CB As CheckBox,UncheckedImgFile As String,CheckedImgFile As String,Size As Int)
    Dim SLD As StateListDrawable
    SLD.Initialize
    Dim Unchecked,Checked As Bitmap
    Dim UncheckedBMD,CheckedBMD As BitmapDrawable
    Unchecked.Initialize(File.DirAssets,UncheckedImgFile)
    Checked.Initialize(File.DirAssets,CheckedImgFile)

    'Scale image
    Dim JO As JavaObject
    JO.InitializeStatic("android.graphics.Bitmap")
  
    UncheckedBMD.Initialize(JO.RunMethod("createScaledBitmap",Array As Object(Unchecked,Size,Size,True)))
    CheckedBMD.Initialize(JO.RunMethod("createScaledBitmap",Array As Object(Checked,Size,Size,True)))
 
    'Add to the StateList Drawable
    SLD.AddState(SLD.State_Checked,CheckedBMD)
    SLD.AddState(SLD.State_Unchecked,UncheckedBMD)
    SLD.AddCatchAllState(UncheckedBMD)
    'Add SLD to the Checkbox
    Dim JO As JavaObject = CB
    JO.RunMethod("setButtonDrawable",Array As Object(SLD))
    'CB.Background = SLD
End Sub

I tried it on a few devices with 32px images and it looked OK.
 

phukol

Active Member
Licensed User
Longtime User
Thanks, basically i
Possibly, although you could scale the bitmap before adding it to the statelistdrawable. Something like:

B4X:
Sub SetCBDrawable2FromAssets(CB As CheckBox,UncheckedImgFile As String,CheckedImgFile As String,Size As Int)
    Dim SLD As StateListDrawable
    SLD.Initialize
    Dim Unchecked,Checked As Bitmap
    Dim UncheckedBMD,CheckedBMD As BitmapDrawable
    Unchecked.Initialize(File.DirAssets,UncheckedImgFile)
    Checked.Initialize(File.DirAssets,CheckedImgFile)

    'Scale image
    Dim JO As JavaObject
    JO.InitializeStatic("android.graphics.Bitmap")
 
    UncheckedBMD.Initialize(JO.RunMethod("createScaledBitmap",Array As Object(Unchecked,Size,Size,True)))
    CheckedBMD.Initialize(JO.RunMethod("createScaledBitmap",Array As Object(Checked,Size,Size,True)))

    'Add to the StateList Drawable
    SLD.AddState(SLD.State_Checked,CheckedBMD)
    SLD.AddState(SLD.State_Unchecked,UncheckedBMD)
    SLD.AddCatchAllState(UncheckedBMD)
    'Add SLD to the Checkbox
    Dim JO As JavaObject = CB
    JO.RunMethod("setButtonDrawable",Array As Object(SLD))
    'CB.Background = SLD
End Sub

I tried it on a few devices with 32px images and it looked OK.

Thanks basically i used this for Checkbox and Radiobutton to remove their "default" marks.
 

LucaMs

Expert
Licensed User
Longtime User
The "expert" needs help for a basilar thing!

Why this function does not work as expected?

B4X:
Public Sub CreateButtonColor(EnabledColor As Int, _
                             DisabledColor As Int, _
                             PressedColor As Int) As StateListDrawable
    Dim res As StateListDrawable
    res.Initialize
 
    Dim drwEnabledColor, drwDisabledColor, drwPressedColor As ColorDrawable
    drwEnabledColor.Initialize2(EnabledColor, 5, 0, Colors.Black)
    drwDisabledColor.Initialize2(DisabledColor, 5, 0, Colors.Black)
    drwPressedColor.Initialize2(PressedColor, 5, 0, Colors.Black)
 
    res.AddState(res.State_Enabled, drwEnabledColor)
    res.AddState(res.State_Disabled, drwDisabledColor)
    res.AddState(res.State_Pressed, drwPressedColor)
    res.AddCatchAllState(drwEnabledColor)
 
    Return res
End Sub

Button1.Background = CreateButtonColor(Colors.Gray, Colors.LightGray, Colors.White)
 
Top