Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SwitchButton used in a custom SwitchPreference #69

Open
Calagahn opened this issue Aug 29, 2016 · 1 comment
Open

SwitchButton used in a custom SwitchPreference #69

Calagahn opened this issue Aug 29, 2016 · 1 comment

Comments

@Calagahn
Copy link

Hi there,

I am using the SwitchButton in a subclass of SwitchPreference, and then this preference instance is added with other standard ones (EditTextPreference, Preference,...) in a PreferenceFragment.
My SwitchButton is implemented using a widget layout like this :

<?xml version="1.0" encoding="utf-8"?>
<com.kyleduo.switchbutton.SwitchButton
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    style="@style/SwitchButton"
    android:id="@+id/switch_button"
    android:layout_width="50dp"
    android:layout_height="30dp"
    android:layout_gravity="center_vertical|end">
</com.kyleduo.switchbutton.SwitchButton>

The customization of preferences can be a little bit tricky, but I think my implementation is correct. Here is what this CustomSwitchPreference looks like :

public class CustomSwitchPreference extends SwitchPreference implements SwitchButton.OnCheckedChangeListener {

    private SwitchButton mSwitchButton;
    private boolean mChecked;

    public CustomSwitchPreference(Context context) {
        this(context, null);
    }

    public CustomSwitchPreference(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        // Inflate the custom preference layout and its custom widget.
        setLayoutResource(R.layout.pref_layout);
        setWidgetLayoutResource(R.layout.pref_switch_widget);
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        boolean defaultValue = a.getBoolean(index, false);
        return defaultValue;
    }

    @Override
    protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
        if (restorePersistedValue) {
            // Restore existing state
            boolean persistingCheck = getPersistedBoolean(mChecked);
            setChecked(persistingCheck);
        } else {
            // Set default state from the XML attribute
            setChecked((Boolean)defaultValue);
        }
    }

    @Override
    protected void onBindView(View view) {
        super.onBindView(view);

        mSwitchButton = (SwitchButton) view.findViewById(R.id.switch_button);
        mSwitchButton.setOnCheckedChangeListener(this);
    }

    public boolean isChecked() {
        return mChecked;
    }

    public void setChecked(boolean checked) {
        if (checked != mChecked) {
            mChecked = checked;
            persistBoolean(mChecked);

            notifyChanged();
            notifyDependencyChange(mChecked);
        }
    }

    /*
     * SwitchButton.OnCheckedChangeListener implementation.
     */
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if (callChangeListener(isChecked)) {
            setChecked(isChecked);
        }
    }

    /*
     * Preference's lifecycle to save and restore the state.
     */
    @Override
    protected Parcelable onSaveInstanceState() {
        final Parcelable superState = super.onSaveInstanceState();
        // Check whether this Preference is persistent (continually saved).
        if (isPersistent()) {
            // No need to save instance state since it's persistent, use superclass state.
            return superState;
        }
        // Create instance of custom BaseSavedState
        final SavedState myState = new SavedState(superState);
        // Set the state's value with the class member that holds current setting value.
        myState.checked = mChecked;
        return myState;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        // Check whether we saved the state in onSaveInstanceState.
        if (state == null || !state.getClass().equals(SavedState.class)) {
            // Didn't save the state, so call superclass.
            super.onRestoreInstanceState(state);
            return;
        }
        // Cast state to custom BaseSavedState and pass to superclass
        SavedState myState = (SavedState) state;
        super.onRestoreInstanceState(myState.getSuperState());

        // Set this Preference's widget to reflect the restored state
        setChecked(myState.checked);
    }

    /*
     * Saving and restoring the Preference's state.
     */
    private static class SavedState extends BaseSavedState {
        // Member that holds the setting's value.
        boolean checked;

        public SavedState(Parcelable superState) {
            super(superState);
        }

        public SavedState(Parcel source) {
            super(source);
            // Get the current preference's value.
            checked = (Boolean) source.readValue(getClass().getClassLoader());
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            // Write the preference's value.
            dest.writeValue(checked);
        }

        // Standard creator object using an instance of this class.
        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {

            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }
}

When I test all this in my app, everything is fine only if notifyChanged() and/or notifyDependencyChanged() are not called in method setChecked(boolean checked). If one of these notification methods is called, the SwitchButton starts to misbehave :

  • if state is changed from true to false, the state changes immediately even if the SwitchButton is set to have an animation during transition.
  • then if the state is changed from false to true, nothing happens... but in fact, internally, the state has changed.
  • finally, I have to click one more time on the SwitchButton to see the animation occur (the internal state remain to true).

I have tried many workaround, but cannot find one to fix this.
As the behavior is not the same depending on the state of the SwitchButton, I suspect that internally something is managed differently when the UI of the Preferences objects is updated.
I hope you could have a look at this and try to reproduce the case. Using the SwitchButton in Preferences is something quite normal and natural, but it is then important that the Switch behaves as expected when notifyChanged() or notifyDependencyChanged() are called.

Thanks in advance!

@Calagahn
Copy link
Author

In complement to the original post, I just want to add that one of the workaround I have tested was to set a PreferenceChangeListener on my instance of the CustomSwitchPreference during the onCreateView() of the PreferenceFragment. This listener will respond to the call to Preference.callChangeListener() during onCheckedChanged().
By doing this, if I omit to call notifyChanged() and notifyDependencyChange(), I still can try to, let's say, change the status of other preferences in the onPreferenceChange() event of the PreferenceChangeListener.
I have tried to do so, to disable some other Preferences. Unfortunately, the SwitchButton misbehave exactly the same way... This is why I suppose that the UI update is involved in the issue.

Hope this help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants