I haven't been able to find quite what I am trying to do on SO, but I feel like it should be such a common interface need that there must be a straightforward way of accomplishing this that I'm missing.
In my style.xml I have two button styles, a standard "active" button and an "inactive" button.
<style name="ButtonStandard">
<item name="android:background">#color/colorGreen</item>
<item name="android:textColor">#android:color/white</item>
<item name="android:padding">#dimen/element_padding</item>
</style>
<style name="ButtonInactive">
<item name="android:background">#color/colorLight</item>
<item name="android:textColor">#android:color/black</item>
<item name="android:padding">#dimen/element_padding</item>
</style>
I am setting one button to ButtonStandard and the other to ButtonInactive. When I click the inactive button, I want to change it to use the ButtonStandard type and vice versa. I don't want to programmatically set the styles individually in case I decide to later change the button styles and it would be unreliable to have to change it in every location.
activeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
activeButton.[somehowsetstyle](R.style.ButtonInactive);
inactiveButton.[somehowsetstyle](R.style.ButtonStandard);
}
});
How can I change between these styles when users click on buttons? The most important is to not have to set specific styles within the code which is just a last resort imho.
Thanks!
Solution Notes
Generally I followed the solution below but instead I created the selector as a drawable and used android:drawable instead because it seems the button background needs that, even if just specifying a color. I also used state_activated rather than enabled so that it is only changing the look of the button and doesn't prevent clicks.
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_activated="false"
android:drawable="#color/colorPrimaryDark" />
<item android:state_activated="true"
android:drawable="#color/colorGreen" />
<item android:drawable="#color/colorGreen" />
In XML
android:background="#drawable/selector_btn_bkg"
android:state_activated="false"
In Java
myButton.setActivated(true);
What you are looking for is ColorStateList
drawable/my_selector.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:color="#color/enabled_color"/>
<item android:color="#color/enabled_color"
android:state_enabled = "true"/>
<item android:color="#color/disbaled_color"
android:state_enabled = "false"/>
</selector>
my_view.xml
...
<Button
android:id="#+id/my_button"
android:enabled="false"
android:background="#drawable/my_selector"/>
Java code
onClick(View v){
myButton.setEnabled(true);
}
Related
I am in the process of building my first Android app and am running into an issue that should seemingly be very simple, but I'm at a loss on why it won't work. I am trying to use a MaterialButtonGroup where a selection is both required and only one option can be selected at a time. The XML for this is:
<com.google.android.material.button.MaterialButtonToggleGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:selectionRequired="true"
android:layout_margin="10dp"
app:singleSelection="true"
app:checkedButton="#id/btnCases">
<com.google.android.material.button.MaterialButton
android:id="#+id/btnCases"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="#style/Theme.MyApp.Toggle.Selected"
android:onClick="#{() -> viewModel.changeScanningMode(true)}"
android:text="Cases" />
<com.google.android.material.button.MaterialButton
android:id="#+id/btnUnits"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="#style/Theme.MyApp.Toggle.NotSelected"
android:onClick="#{() -> viewModel.changeScanningMode(false)}"
android:text="Units" />
</com.google.android.material.button.MaterialButtonToggleGroup>
The two buttons inside represent the either/or option (scanning cases or units with a handheld scanner). There are two styles I've created - Selected and NotSelected - which have a parent style of OutlinedButton from MaterialComponents and look like this:
<style name="Theme.MyApp.Toggle.Selected" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="android:checked">true</item>
<item name="backgroundTint">#color/dark_blue</item>
<item name="android:textColor">#color/white</item>
<item name="strokeColor">#color/dark_blue</item>
</style>
<style name="Theme.MyApp.Toggle.NotSelected" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="android:checked">false</item>
<item name="backgroundTint">#color/white</item>
<item name="android:textColor">#color/dark_blue</item>
<item name="strokeColor">#color/dark_blue</item>
</style>
When either button is selected, I am trying to essentially swap the styles by having the Fragment this lives on using an Observer to watch for a property change on the ViewModel so it can flip the styles. The ViewModel side works fine, but swapping styles does not. The code for changing styles looks like this:
val btnCases = binding.root.findViewById<MaterialButton>(R.id.btnCases)
val btnUnits = binding.root.findViewById<MaterialButton>(R.id.btnUnits)
when (it) {
ScanningModes.CASE -> {
btnCases.setTextAppearance(R.style.Theme_MyApp_Toggle_Selected)
btnUnits.setTextAppearance(R.style.Theme_MyApp_Toggle_NotSelected)
}
ScanningModes.UNIT -> {
btnUnits.setTextAppearance(R.style.Theme_MyApp_Toggle_Selected)
btnCases.setTextAppearance(R.style.Theme_MyApp_Toggle_NotSelected)
}
}
The desired result is to flip the styles completely, but after a lot of trial and error (which got me to the above), what ends up happening is when Cases is selected (on initial load and by flipping back and forth between it and Units), I get this (desired result):
However, when I select Units, it does this:
This seems like it should be fairly easy to do, but I'm at a loss on how to accomplish this and hoping someone can assist.
Thanks in advance!
You don't need to switch style. You can just use one style with some selectors defining the android:state_checked state.
Something like:
<style name="App.Material3.Button.OutlinedButton" parent="Widget.Material3.Button.OutlinedButton">
<item name="backgroundTint">#color/app_m3_text_button_background_color_selector</item>
<item name="strokeColor">#color/app_button_outline_color_selector</item>
<item name="android:textColor">#color/app_text_button_foreground_color_selector</item>
</style>
with app_m3_text_button_background_color_selector:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#color/teal_200" android:alpha="0.12"
android:state_enabled="true" android:state_checked="true"/>
<item android:color="?attr/colorContainer"/>
</selector>
app_button_outline_color_selector:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="0.12" android:color="?attr/colorOnSurface" android:state_enabled="false" />
<item android:alpha="0.12" android:color="#color/blu500_dark" android:state_enabled="true" android:state_checked="true"/>
<item android:color="?attr/colorOutline" />
</selector>
app_text_button_foreground_color_selector:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Disabled -->
<item android:alpha="#dimen/material_emphasis_disabled" android:color="?attr/colorOnSurface" android:state_enabled="false" />
<!-- Uncheckable -->
<item android:color="?attr/colorOnContainer" android:state_checkable="false" />
<!-- Checked Buttons. -->
<item android:color="#color/blu500_dark" android:state_checked="true" />
<!-- Not-checked Buttons. -->
<item android:color="?attr/colorOnSurface" />
</selector>
After reviewing some additional details, I ended up solving the immediate problem by using setTextAppearance and setBackgroundColor since those are all I was really trying to change anyway. End result was this:
val btnCases = binding.root.findViewById<MaterialButton>(R.id.btnCases)
val btnUnits = binding.root.findViewById<MaterialButton>(R.id.btnUnits)
when (it) {
ScanningModes.CASE -> {
//change the text styles
btnCases.setTextAppearance(R.style.Theme_MyApp_Toggle_Selected)
btnUnits.setTextAppearance(R.style.Theme_MyApp_Toggle_NotSelected)
//change the background colors
btnCases.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.dark_blue))
btnUnits.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.white))
}
ScanningModes.UNIT -> {
btnUnits.setTextAppearance(R.style.Theme_MyApp_Toggle_Selected)
btnCases.setTextAppearance(R.style.Theme_MyApp_Toggle_NotSelected)
//change the background colors
btnUnits.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.dark_blue))
btnCases.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.white))
}
}
It's kind of amazing that changing a style directly isn't supported, but I'm sure there are reasons. Thankfully I didn't have to change more, or I'd have been stuck!
Is it possible to create a button by passing parameters in XML ?
doing it that way ?
<Button
button:typeParameter="primary"
button:size="md"/>
And after passing these 2 parameters the button is created as it should be, is it possible to do this?
I already have a button on which I created new attributes, now I want to know if it is possible to pass parameters and this button be called, without the need for the developer to have to code all these lines.
<customButton
android:fontFamily="#font/mondrian_family_font"
app:fontFamily="#font/mondrian_family_font"
android:id="#+id/mdnButtonPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:text="Button primary"
android:paddingLeft="40dp"
android:paddingRight="40dp"
android:textColor="#color/color_neutral_lightest"
android:layout_marginTop="10dp"
android:textSize="#dimen/font_size_XXS"
mdnbutton:radius="#dimen/border_radius_pill"
mdnbutton:defaultColor="#color/color_brand_primary_medium"
mdnbutton:focusColor="#color/color_brand_primary_darkest"
style="?android:attr/borderlessButtonStyle"/>
It is possible that he will use these attributes after he installs my library.
Now I want that when the developer is going to create his layout, instead of creating a button from scratch, he just passes parameters in the XML and the button is rendered.
I suggest for you to make it as a style. But having it depend on two attributes is a bit hard, so I suggest to make a style for every combination of the two, so you have a style called primaryMd for example.
Your button would be like this:
<Button
style="#style/primaryMd" />
you can define this style in you res folder in styles.xml like this for example then:
<resources>
<style name="primaryMd" parent="#style/Widget.AppCompat.Button.Borderless">
<item name="android:fontFamily">#font/mondrian_family_font</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textAllCaps">false</item>
<item name="android:text">Button primary</item>
<item name="android:paddingLeft">40dp</item>
<item name="android:paddingRight">40dp</item>
<item name="android:textColor">#color/color_neutral_lightest</item>
<item name="android:layout_marginTop">10dp</item>
<item name="android:textSize">#dimen/font_size_XXS</item>
<item name="mdnbutton:radius">#dimen/border_radius_pill</item>
<item name="mdnbutton:defaultColor">#color/color_brand_primary_medium</item>
<item name="mdnbutton:focusColor">#color/color_brand_primary_darkest</item>
</style>
</resources>
I'm not entirely sure if it works with these custom mdnbutton attributes, but you could try it out maybe
I want to color my button that is defined in a fragment. I created new style (which I use as a theme in the button) and defined "colorAccent" for enabled state, "colorButtonNormal" for disabled and parent of this style is "Widget.AppCompat.Button". I want it to be coloured exactly as it is written in colorButtonNormal when button is disabled.
<style name="Material.Button.Primary" parent="#style/Widget.AppCompat.Button">
<item name="android:colorButtonNormal">#color/color_disabled</item>
<item name="colorAccent">#color/color_primary</item>
</style>
<Button
style="#style/Widget.AppCompat.Button.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/button_text"
android:textAppearance="#style/AppTheme.Text"
android:theme="#style/Material.Button.Primary" />
When the button is enabled it has correct color from colorAccent. When user clicks on it, it becomes disabled and should be gray (#b2b2b2) but it becomes a little bit lighter (#E7E7E7). It seems like it takes color that I defined and mixes with white color.
I tried to change style's parent and did some changes in style and button's attributes as it is written in some guides from the internet but nothing worked. My current solution is to set colorButtonNormal to #000000. When button is disabled, it becomes #B9B9B9.
Forgive me if I am wrong but as far I understand correctly, you want to achieve different colours for enabled/disabled states.
UPDATE
For keeping the material effect you can use styling with Widget.AppCompat.Button.Colored and create theme with specified colours:
<Button
.
.
.
style="#style/Widget.AppCompat.Button.Colored"
android:theme="#style/CustomButton"/>
And create theme, where colorButtonNormal is for disabled state
<style name="CustomButton" parent="Widget.AppCompat.Button.Colored">
<item name="colorButtonNormal">#color/color_disabled</item>
<item name="colorAccent">#color/color_enabled</item>
</style>
Old without material effect
You can try using selector like:
custom_button.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:drawable="#color/color_disabled" />
<item android:state_enabled="true" android:drawable="#color/color_enabled/>
</selector>
and then in your xml for button:
<Button
.
.
.
android:background="#drawable/custom_button" />
I need to add customised Switch to my application. I know that usually to display it in a way you like you need to create a selector for thumb and track resources and set it in the XML, which I did and it works. Code for selectors is as follows:
1. thumb_selector.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/switch_thumb_disabled" android:state_enabled="false"/>
<item android:drawable="#drawable/switch_thumb_pressed" android:state_pressed="true"/>
<item android:drawable="#drawable/switch_thumb_activated" android:state_checked="true"/>
<item android:drawable="#drawable/switch_thumb"/>
</selector>
bg_selector.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/switch_bg_disabled" android:state_enabled="false"/>
<item android:drawable="#drawable/switch_bg_focused" android:state_focused="true"/>
<item android:drawable="#drawable/switch_bg_on" android:state_checked="true"/>
<item android:drawable="#drawable/switch_bg"/>
</selector>
These selectors are located in drawable resource folder, so when I use
android:thumb="#drawable/thumb_selector"
android:track="#drawable/bg_selector"
switch is displayed as expected. However, there are other properties and many Views I have to create dynamically, so it is better to add a custom control (which extends Switch), and I need to set ThumbResource and TrackResource. Documentation says there are two methods, which can set it: setThumbResource(int resId) and setTrackResource(int resId), but when I try to reference selector as setThumbResource(R.drawable.thumb_selector) it doesn't recognise this selector as drawable. It's a silly problem and I can't seem to be able to solve it, did I add it to the wrong folder? One way to work around this is to extend layout and inflate xml version of Switch, but do I have to do this? How can I reference selector within the code?
I need to prevent seekbar from user inputs in special cases. If I use setEnabled(false) it becomes gray instead of white.
Is there any method to disable seekbar without dimming or set another drawable for progress in disabled seekbar ?
Yes. It is possible! But you need to override SeekBar's drawableStateChanged function, with something like this:
#Override
protected void drawableStateChanged() {
super.drawableStateChanged();
final Drawable progressDrawable = getProgressDrawable();
if(isEnabled() == false && progressDrawable != null) progressDrawable.setAlpha(255);
}
Actually I was very angry, when I saw hardcoded alpha value in AbsSeekBar:
mDisabledAlpha = a.getFloat(com.android.internal.R.styleable.Theme_disabledAlpha, 0.5f);
Because there is no function, which will turn off, or even change disabled alpha value for SeekBar. Just take a look at those lines of code in drawableStateChanged function:
if (progressDrawable != null) {
progressDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha));
}
I'm not sure why you'd want to change this, and I don't believe it's good practice to override the visual queue for a user that something is disabled. If it looks active, but doesn't interact, I'm going to be mad at your app.
Regardless, to answer your question, you should look at StateListDrawable this question outlines it specifically for seek bars.
Better solution here....
seekBar.setOnTouchListener(new OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
You can use setEnabled(false) and set the Theme_disabledAlpha attribute as #Ilya Pikin mentioned above like this:
<item name="android:disabledAlpha">1.0</item>
Android changes alpha value of color to 127 from 255 in disabled state. Just make sure you set it back to 255 after disabling seekbar.
seekBar.post(new Runnable(){
#Override
public void run()
{
seekBar.getProgressDrawable().setAlpha(255);
}
});
view.post is required only if you are unsure that seekbar is rendered yet or not, otherwise just
seekBar.getProgressDrawable().setAlpha(255);
is enough.
I´m a bit late to the party, but here is a solution to solve your question.
First of all I recommand to use com.google.android.material.slider.Slider nowadays. It is much more flexible than the old seekbar was. To do so, use the following import in your gradle file
implementation 'com.google.android.material:material:1.6.0' //1.4.0 or newer
Now create your layout file, let´s it call layout_slider
e.g.:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/white">
<com.google.android.material.slider.Slider
android:id="#+id/sbSize"
style="#style/sliderTheme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:value="25"
android:valueFrom="0.0"
android:valueTo="100.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Now open your styles.xml file and create the sliderTheme. It should look like this:
<style name="sliderTheme" parent="Widget.MaterialComponents.Slider">
<item name="trackColorInactive">#color/slider_track_inactive</item>
<item name="thumbColor">#color/slider_thumb_color</item>
<item name="trackColorActive">#color/slider_track_active</item>
</style>
The next step we need to do is create a new Resource Directory named color inside the res folder. (Resource type is color)
Now create a file to change the trackColor for active and inactive state. Let´s name it slider_track_inactive.xml
Copy the following content inside that file:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="0.24" android:color="?attr/colorPrimary" android:state_enabled="true"/>
<item android:alpha="0.12" android:color="?attr/colorOnSurface"/>
</selector>
the first value is for the active and the second is for the inactive color. Change alpha and color to a value to want. All my sample values are the Google default values.
Now create a file called slider_thumb_color.xml inside the color folder. It should look like this:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorPrimary" android:state_enabled="true"/>
<item android:alpha="0.38" android:color="?attr/colorOnSurface"/>
</selector>
Again, change these values as you want.
Last, create a file called slider_track_active.xml inside the color folder. It should look like this:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorPrimary" android:state_enabled="true"/>
<item android:alpha="0.32" android:color="?attr/colorOnSurface"/>
</selector>
Now you have customized all colors for the disabled slider. There are much more values you can customize with the same technique. All default keys you can customize inside your styles.xml file are:
<item name="haloColor">#color/material_slider_halo_color</item>
<item name="haloRadius">#dimen/mtrl_slider_halo_radius</item>
<item name="labelStyle">#style/Widget.MaterialComponents.Tooltip</item>
<item name="thumbColor">#color/material_slider_thumb_color</item>
<item name="thumbElevation">#dimen/mtrl_slider_thumb_elevation</item>
<item name="thumbRadius">#dimen/mtrl_slider_thumb_radius</item>
<item name="tickColorActive">#color/material_slider_active_tick_marks_color</item>
<item name="tickColorInactive">#color/material_slider_inactive_tick_marks_color</item>
<item name="trackColorActive">#color/material_slider_active_track_color</item>
<item name="trackColorInactive">#color/material_slider_inactive_track_color</item>
<item name="trackHeight">#dimen/mtrl_slider_track_height</item>
<item name="minSeparation">0dp</item>