Multi-State Toggle Button - android

In the app I've been working on, I would like to have a multiple-state (in my case, three) toggle button, instead of the two that ToggleButton provides. I've tried to start my own that extends Button, following the CompoundButton source, but quite honestly reading over its source got a bit overwhelming.
Is there a way to do a three-state toggle button using just a selector xml or something, or perhaps another method I haven't thought of? I'm rather at a loss of how to do this.

I implemented a multi-state toggle button, the source code is here
This is how it looks:
And it's quite easy to use it:
<org.honorato.multistatetogglebutton.MultiStateToggleButton
android:id="#+id/mstb_multi_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
mstb:values="#array/planets_array" />
In your activity:
MultiStateToggleButton button2 = (MultiStateToggleButton) this.findViewById(R.id.mstb_multi_id);
button2.setOnValueChangedListener(new ToggleButton.OnValueChangedListener() {
#Override
public void onValueChanged(int value) {
Log.d(TAG, "Value: " + value);
}
});

You can create a custom ImageButton to achieve this, you need 3 different images in this case.
You can also add more states if you want.
public class FlashButton extends ImageButton {
public enum FlashEnum {
AUTOMATIC, ON, OFF
}
public interface FlashListener {
void onAutomatic();
void onOn();
void onOff();
}
private FlashEnum mState;
private FlashListener mFlashListener;
public FlashButton(Context context, AttributeSet attrs) {
super(context, attrs);
//Sets initial state
setState(FlashEnum.AUTOMATIC);
}
#Override
public boolean performClick() {
super.performClick();
int next = ((mState.ordinal() + 1) % FlashEnum.values().length);
setState(FlashEnum.values()[next]);
performFlashClick();
return true;
}
private void performFlashClick() {
if(mFlashListener == null)return;
switch (mState) {
case AUTOMATIC:
mFlashListener.onAutomatic();
break;
case ON:
mFlashListener.onOn();
break;
case OFF:
mFlashListener.onOff();
break;
}
}
private void createDrawableState() {
switch (mState) {
case AUTOMATIC:
setImageResource(R.drawable.ic_flash_auto);
break;
case ON:
setImageResource(R.drawable.ic_flash_on);
break;
case OFF:
setImageResource(R.drawable.ic_flash_off);
break;
}
}
public FlashEnum getState() {
return mState;
}
public void setState(FlashEnum state) {
if(state == null)return;
this.mState = state;
createDrawableState();
}
public FlashListener getFlashListener() {
return mFlashListener;
}
public void setFlashListener(FlashListener flashListener) {
this.mFlashListener = flashListener;
}
}

You can certainly define a selector to use as a background that has three entries. The question is what button attributes you can use for the selector. You can have two boolean attributes, say A and B, and define the selector in terms of A, B, and default. (A && B will satisfy A, so more properly they could be thought of as A, !A && B, and !A && !B.) You can overload existing attributes (selected, focused, etc.) or, more elegantly, define your own custom attributes using the recipe described in this thread.

Why not use RadioGroup and style radios inside?
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:background="#drawable/your_drawable_selector"
android:button="#android:color/transparent"
android:gravity="center_horizontal" //center text
android:text="text"
/>
...

Chip is a very good native option.
<com.google.android.material.chip.ChipGroup
android:id="#+id/chip_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:singleSelection="true">
<com.google.android.material.chip.Chip
android:id="#+id/first_chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Todo"
android:textAppearance="?android:attr/textAppearance"
android:textColor="#color/black"
android:checkable="true"
style="#style/Widget.MaterialComponents.Chip.Choice"
app:chipBackgroundColor="#color/colorAccent"/>
<!-- Second Chip -->
<!-- Third Chip -->
</com.google.android.material.chip.ChipGroup>
binding.chipGroup.setOnCheckedChangeListener { chipGroup, i ->
when (i) {
binding.firstChip -> {
binding.firstChip.setChipBackgroundColorResource(R.color.colorAccent)
}
else -> {}
}
}
binding.firstChip.isChecked = true //default
GL
Source

Related

Android - Update view outside a custom control (from inside custom control)

I have a custom control with buttons (image) that should update an activity (fragment) control (big 0 string).
May be it is too simple but I don't know how to do it because onClickListener buttons are inside the custom control class. Which is the best approach?
Listener inside custom control class is:
binding.counterSelectorViewPrevious.setOnClickListener(OnClickListener {
decreaseValue()}
fun decreaseValue() {
if (mSelectedIndex > 0) {
val newSelectedIndex = mSelectedIndex - 1
setSelectedIndex(newSelectedIndex)
}
}
Activity control is just a TextView outside of custom control class.
<TextView
android:id="#+id/workout_length"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="0"
android:textAlignment="textEnd"
android:textAppearance="#style/TextAppearance.AppCompat.Display2"
tools:text="99:99:99" />
Thanks in advance.
You need to accept a listener in your custom view what get's some callback from your activity/fragment
something like this
private OnValueChangeListener onValueChangeListener;
public void addOnValueChangeListener(OnValueChangeListener onValueChangeListener)
{
this.onValueChangeListener = onValueChangeListener;
}
public void decreaseValue() {
onValueChangeListener.onValueChange(newValue);
}
public interface OnValueChangeListener {
void onValueChange(int newValue);
}
then you need to add invoke this somewhere in your activity/fragment (like in onCreate/onCreateView)
void setupListener() {
addOnValueChangeListener(newValue -> {
findViewById(R.id.workout_length).setText("" + newValue);
});
}

Required single selection on MaterialButtonToggleGroup

Is there an option to make the MaterialButtonToggleGroup have a required selected button when using app:singleSelection="true"?
When clicking to a different button works fine (all other buttons are deselected), but when you click the same (already selected) button it deselects it itself and I want to remain selected.
My example:
<com.google.android.material.button.MaterialButtonToggleGroup
android:layout_width="wrap"
android:layout_height="wrap_content"
app:singleSelection="true">
<com.google.android.material.button.MaterialButton
android:id="#+id/filterA"
style="#style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="A"/>
<com.google.android.material.button.MaterialButton
android:id="#+id/filterB"
style="#style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="B"/>
<com.google.android.material.button.MaterialButton
android:id="#+id/filterC"
style="#style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="C"/>
</com.google.android.material.button.MaterialButtonToggleGroup>
You can define it in the layout using the app:selectionRequired attribute:
<com.google.android.material.button.MaterialButtonToggleGroup
app:selectionRequired="true"
app:checkedButton="#id/..."
app:singleSelection="true">
You can also use the method setSelectionRequired:
buttonGroup.setSelectionRequired(true);
Note: This requires a minimum of version 1.2.0-alpha03
I got this working with the following:
toggle_group.addOnButtonCheckedListener { group, checkedId, isChecked ->
if (group.checkedButtonId == -1) group.check(checkedId)
}
If you have singleSelection enabled, the conditional will only evaluate to true when the user has clicked on the button which is already checked, making it so no button is checked. When this happens, we just need to check the button they unchecked.
I also came across this issue and I found this is working with the app:singleSelection="true"
String selectedValue = "Male";
genderBtnToggle.addOnButtonCheckedListener(new MaterialButtonToggleGroup.OnButtonCheckedListener() {
#Override
public void onButtonChecked(MaterialButtonToggleGroup group, int checkedId, boolean isChecked) {
MaterialButton btn = genderBtnToggle.findViewById(checkedId);
if (!isChecked && btn.getText().toString().equals(selectedValue)) {
genderBtnToggle.check(checkedId);
}
if (isChecked) {
selectedValue = btn.getText().toString();
}
}
});
I also came across this issue and will be waiting for a permanent fix from google. In the meantime, I did the following to make sure that at least one button is checked.
final MaterialButtonToggleGroup tGroup = view.findViewById(R.id.toggleGroup);
final MaterialButton breast = tGroup.findViewById(R.id.breast);
final MaterialButton bottle = tGroup.findViewById(R.id.bottle);
final MaterialButton solids = tGroup.findViewById(R.id.solids);
View.OnClickListener onClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
tGroup.check(v.getId());
}
};
breast.setOnClickListener(onClickListener);
bottle.setOnClickListener(onClickListener);
solids.setOnClickListener(onClickListener);
Hope this helps.

android databinding: how to avoid onCheckedChanged triggered by programmatically

I am now trying to use android data-binding in my project, and encounter this kind of issue, for example: I have 3 checkbox as a checkbox group, if first checkbox is checked, then a variable type is 1. the second makes type to 2, the 3rd makes type to 3. so I implement the code in this way.
// layout.xml
<android.support.v7.widget.AppCompatCheckBox
android:layout_width="50dp"
android:layout_height="50dp"
android:checked="#{userInfoViewModel.type == 1}"
android:onCheckedChanged="#{(compoundButton, checked) -> userInfoViewModel.onTypeChecked(checked, 1)}"
/>
<android.support.v7.widget.AppCompatCheckBox
android:layout_width="50dp"
android:layout_height="55dp"
android:checked="#{userInfoViewModel.type == 2}"
android:onCheckedChanged="#{(compoundButton, checked) -> userInfoViewModel.onTypeChecked(checked, 2)}"
/>
<android.support.v7.widget.AppCompatCheckBox
android:layout_width="50dp"
android:layout_height="55dp"
android:checked="#{userInfoViewModel.type == 3}"
android:onCheckedChanged="#{(compoundButton, checked) -> userInfoViewModel.onTypeChecked(checked, 3)}"
/>
// viewModel
public void onTypeChecked(boolean checked, int i) {
if (checked) {
// if it is a check. set the type
type.set(i);
} else {
// if it is a uncheck. set type to unknown
type.set(0);
}
}
Now the problem is that, if I have checked 1st checkbox, then I check the 2nd. type should be set to 2, and the UI should update correctly. But the reality is that uncheck event also occur on the 1st checkbox, after type is set to 2, then type.set(0) is triggered, so no checkbox is checked.
In fact, this issue is same to onCheckedChanged called automatically. What I need is a solution for data-binding.
In non-data-binding project, I think the best solution is using setCheckedSilent(answer by #Emanuel Andrada).
public void setCheckedSilent(boolean checked) {
super.setOnCheckedChangeListener(null);
super.setChecked(checked);
super.setOnCheckedChangeListener(listener);
}
But in data-binding, I can not do this. So is there any expert can help me out?
According to #Arpan Sharma's answer, listen to onClick instead of onCheckedChanged. This solution works currently, But I am worried about the value of checked, is it always right?
public void onTypeChecked(View view, int i) {
Boolean checked = ((CheckBox) view).isChecked();
if (checked) {
type.set(i);
} else {
type.set(0);
}
}
Expose an ObservableBoolean from the ViewModel, then use two-way databinding over that boolean.
Then you can use the ObservableBoolean's values to decide what you want to do, rather than encode it in the XML.
android:checked="#={vm.checkedValue}"
This is very simple with data binding
In xml checkbox component
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onCheckedChanged="#{(compoundButton, checked) ->
changeKeyboardVM.onCheckedChange(compoundButton, checked)}" />
In ViewModel or Activity
public void onCheckedChange(CompoundButton button, boolean check) {
Log.d("Z1D1", "onCheckedChange: " + check);
}
now Boolean check true on checked
and false on unchecked
I faced the same problem and i used onCLick listener instead onCHeck
listener .That way the listener wont change the check state when it is set programatically.
In your problem you should try setting different check change listeners to your check boxes.
I came across this question for first time and I think it's better to be implemented using binding adapter.
Here is the code for binding adapter
interface OnUserCheckedChangeListener {
fun onUserCheckChange(view:CompoundButton, isChecked:Boolean)
}
#BindingAdapter("onUserCheckedChange")
fun setUserCheckedChangeListener(view:CompoundButton, listener: OnUserCheckedChangeListener?){
if(listener == null){
view.setOnClickListener(null)
}else{
view.setOnClickListener {
listener.onUserCheckChange(view, view.isChecked)
}
}
}
And we can use it on any compound button
<CheckBox
android:id="#+id/finish_check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
android:checked="#{your_condition}"
onUserCheckedChange="#{(view, checked) -> vm.onItemChecked(todo, checked)}"
/>
Using onClick instead of onCheckedChanged to prevent 2-ways binding.
From item_order_view.xml:
<data>
<variable
name="viewModel"
type="com.package.name.OrderItemViewModel" />
</data>
<CheckBox
android:id="#+id/cb_selected"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="8dp"
android:buttonTint="#color/white"
android:checked="#{viewModel.isSelect}"
android:onClick="#{() -> viewModel.onClickedCheckBox()}"
android:textColor="#color/white" />
From OrderItemViewModel.java
public class OrderItemViewModel {
public final ObservableField<Boolean> isSelect;
public final OrderItemViewModelListener mListener;
private final Order mOrder;
public OrderItemViewModel(Order order, OrderItemViewModelListener listener) {
this.mListener = listener;
this.mOrder = order;
isSelect = new ObservableField<>(mOrder != null ? mOrder.isSelect() : false);
}
/**
* Using onClick instead of onCheckedChanged
* to prevent 2-ways binding issue.
*/
public void onClickedCheckBox() {
mListener.onCheckChanged();
}
public interface OrderItemViewModelListener {
void onCheckChanged();
}
}
See https://stackoverflow.com/a/52606437/2914140:
Write inside OnCheckedChangeListener:
if (button.isPressed()) {
// A user pressed Switch.
}
Maybe some answers from How to use data binding for Switch onCheckedChageListener event? may help, for instance, defining android:onCheckedChanged listener, but I didnt test.

Use two buttons (positive/negative) or one and change text?

I'm curious about how other people solve this problem:
When having an application which allows users to subscribe to each other, should you better use two buttons, one with a "Subscribe" text and the other with a "Unsubscribe" text or just use one button in the layout and change the text after clicking the button?
2 buttons solution
XML:
<Button
android:id="#+id/subscribe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Subscribe"
android:visibility="gone" />
<Button
android:id="#+id/unsubscribe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Unsubscribe"
android:visibility="gone" />
Java:
// update subscribe buttons
if (userProfile.getID() != user.getUserID()) {
if (userProfile.hasSubscribed()) {
unsubscribeView.setVisibility(View.VISIBLE);
}
else {
subscribeView.setVisibility(View.VISIBLE);
}
}
subscribeView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
subscribe = 1;
subscribe();
}
});
unsubscribeView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
subscribe = -1;
subscribe();
}
});
1 button solution
XML:
<Button
android:id="#+id/subscribe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />
Java:
// update subscribe buttons
if (userProfile.getID() != user.getUserID()) {
if (userProfile.hasSubscribed()) {
subscribeView.setText("Unsubscribe");
}
else {
subscribeView.setText("Subscribe");
}
}
subscribeView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (userProfile.hasSubscribed()) {
subscribe = -1;
}
else {
subscribe = 1;
}
subscribe();
}
});
Use the one-button approach.
Try to keep the UI as clean as possible, and avoid having buttons that are visible, but don't have any effect.

android custom Switch widget for SwitchPreferenc

I search around stackoverflow and find next related topics:
How can i style an Android Switch?
Custom switch widget in Android 4
Set switchStyle - get error resource not found - why?
I also find bugreport on google group: Issue 36636: Unable to override style switchStyle
And at last find new probles with Switch widget:
I tried to make my own Preference.SwitchPreference and define layout with Switch widget
android:id="#+android:id/switchWidget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:thumb="#drawable/switch_thumb"
android:layout_gravity="center"
android:padding="16dip"
android:focusable="false" />
but I get an compliation error: Error: Resource is not public. (at 'id' with value '#+android:id/switchWidget'). So I can't use this way.
Second way I tried to extend Switch class add set resources from code. But I find that method setThumbResource is availible only from API 16. But I still can't apply #+android:id/switchWidget because it's not public.
So, How can I get custom Switch Preference for SDK API 15 ??? Or how can I customize Switch in Preferences?
Just found an awful way to achieve this.
First, src/com/myapp/views/preference/MySwitchPreference.java
public class MySwitchPreference extends SwitchPreference {
public MySwitchPreference(Context context) {
super(context);
}
public MySwitchPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MySwitchPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onBindView(View view) {
super.onBindView(view);
if (view instanceof ViewGroup) {
setLayout((ViewGroup) view);
}
}
#SuppressLint("NewApi")
private void setLayout(ViewGroup viewGroup) {
int count = viewGroup.getChildCount();
for(int n = 0; n < count; ++n) {
View childView = viewGroup.getChildAt(n);
if(childView instanceof Switch) {
final Switch switchView = (Switch) childView;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
switchView.setThumbResource(com.myapp.R.drawable.switch_inner);
switchView.setTrackResource(com.myapp.R.drawable.switch_track);
}
return;
}
else if (childView instanceof ViewGroup)
setLayout((ViewGroup) childView);
}
}
}
And now, res/xml/preferences.xml
<com.myapp.views.preference.MySwitchPreference
android:switchTextOff="Off"
android:switchTextOn="On"
android:title="whatever"
android:key="switch" />
A little bit tricky, and only working with Android > 16.
Don't know much about the switch issues but you could use a ToggleButton as follows:
Define the button in your layout:
<ToggleButton
android:id="#+id/your_awesome_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:gravity="center_vertical|center_horizontal"
android:layout_marginRight="15dp"
android:textOn=""
android:textOff=""
android:background="#drawable/toggle_button"
/>
Create a selector:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:state_checked="false"
android:state_focused="false"
android:drawable="#drawable/switch_off_btn" />
<item
android:state_checked="true"
android:state_focused="false"
android:drawable="#drawable/switch_on_btn" />
<item
android:drawable="#drawable/switch_off_btn" />
</selector>
OnClickListener:
toggleOnOff = (ToggleButton) findViewById(R.id.your_awesome_toggle);
toggleOnOff.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
updateButtons();
if(toggleOnOff.isChecked()){
SharedPreferences emailPrefs = getSharedPreferences(rememberToggleOnOff,MODE_PRIVATE);
SharedPreferences.Editor editor = yourPrefs.edit();
editor.putBoolean("mon", true);
editor.commit();
}
else {
SharedPreferences emailPrefs = getSharedPreferences(rememberToggleOnOff,MODE_PRIVATE);
SharedPreferences.Editor editor = yourPrefs.edit();
editor.putBoolean("mon", false);
editor.commit();
}
}
});
checkToggleState();
checkToggleState method:
/**
* Checks the state of the Toggle button preferences.
* If preferences are true set the toggle to on, if false set the toggle off.
*
*/
private void checkToggleState() {
SharedPreferences yourPrefs = getSharedPreferences(rememberToggleOnOff,MODE_PRIVATE);
boolean mON = yourPrefs.getBoolean("mon", true);
if(mON) {
toggleOnOff.setChecked(true);
}
else {
toggleOnOff.setChecked(false);
}
}
Change:
android:id="#+android:id/switchWidget"
To:
android:id="#+id/switchWidget"
A simple switch example can be found here.
Switch widget supports 14 and above API level only, but if you want to use Switch Preference pre API level 14, check this.
UPDATE: If you want to style your own switch, try this
Inherit SwitchPreference class and use it in preferences.xml with layout pointing to your custom layout. Then in the onBind method of inherited SwitchPreference class you can find corresponding view by id and set listeners. Don't forget to call super in onBind().
Change:
android:id="#+android:id/switchWidget"
To:
android:id="#*android:id/switchWidget"

Categories

Resources