Extending Preference classes in Android Lollipop = losing animation

Just for extending CheckBoxPreference or SwitchPreference on Android Lollipop, the widget (the checkbox or the switch) won’t have animation anymore.

I’d like to extend SwitchPreference to force api < 21 to use SwitchCompat instead of the default one they are using (which is obviously wrong).

  • How to Fix unplug usb cable error in android with usb web cam?
  • Android Studio: Unable to obtain result of 'adb version'
  • Adding a “rate my app” button in libgdx game
  • How to fix 'cordova' is not recognized in command line - windows 8?
  • Android dependency is ignored for release
  • Android WebView Zoom out limited
  • I am using the new AppCompatPreferenceActivity with appcompat-v7:22.1.1 but that doesn’t seem to affect the switches.

    The thing is that with just extending those classes, without adding any custom layout or widget resource layout, the animation is gone.

    I know I can write two instances of my preference.xml (on inside values-v21) and it will work… But I’d like to know why is this happening and if somebody knows a solution without having two preference.xml.

    Code example:

    public class SwitchPreference extends android.preference.SwitchPreference {
    
        public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
    
        public SwitchPreference(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public SwitchPreference(Context context) {
            super(context);
        }
    }
    

    This or the same for CheckBoxPreference and then using:

    <com.my.package.SwitchPreference />

    Will make the animation in a Lollipop device to be gone.

    Another thing I tried for the SwitchPreference (that I can with CheckBoxPreference) is to give a layout with the default id but @android:id/switchWidgetis not public while @android:id/checkbox is. I also know I can use a <CheckBoxPreference /> and give a widget layout that is in fact a SwitchCompat, but I’d like to avoid that (confusing the names).

    Related posts:

    Detect wifi IP address on Android?
    How generify class with T and List&lt;T&gt;
    Slide a View over (on top) of Another view
    Where the heck is Bitmap getByteCount()?
    null value in entry: otherfileoutputs=null
    Why Glide blink the item ImageView when notifydatasetchanged
  • Set text of spinner before item is selected
  • Android: Robolectric does not support API level 1
  • How do we control an Android sync adapter preference?
  • How to download an HttpResponse into a file?
  • Writing external storage doesn't work until I restart the application on Android M
  • When to use Android Loaders
  • 4 Solutions collect form web for “Extending Preference classes in Android Lollipop = losing animation”

    It seems I found a fix for your issue.

    Extensive Explanation

    In SwitchCompat, when toggling the the switch, it tests a few functions before playing the animation: getWindowToken() != null && ViewCompat.isLaidOut(this) && isShown().

    Full method:

    @Override
    public void setChecked(boolean checked) {
        super.setChecked(checked);
        // Calling the super method may result in setChecked() getting called
        // recursively with a different value, so load the REAL value...
        checked = isChecked();
        if (getWindowToken() != null && ViewCompat.isLaidOut(this) && isShown()) {
            animateThumbToCheckedState(checked);
        } else {
            // Immediately move the thumb to the new position.
            cancelPositionAnimator();
            setThumbPosition(checked ? 1 : 0);
        }
    }
    

    By using a custom view extending SwitchCompat, I found out, that isShown() always returns false, because the at third iteration of the while, parent == null.

    public boolean isShown() {
        View current = this;
        //noinspection ConstantConditions
        do {
            if ((current.mViewFlags & VISIBILITY_MASK) != VISIBLE) {
                return false;
            }
            ViewParent parent = current.mParent;
            if (parent == null) {
                return false; // We are not attached to the view root
            }
            if (!(parent instanceof View)) {
                return true;
            }
            current = (View) parent;
        } while (current != null);
    
        return false;
    }
    

    Interestingly, the third parent is the second attribute passed to getView(View convertView, ViewGroup parent) in Preference, means the PreferenceGroupAdapter didn’t get a parent passed to its own getView(). Why this happens exactly and why this happens only for custom preference classes, I don’t know.

    For my testing purposes, I used the CheckBoxPreference with a SwitchCompat as widgetLayout, and I also didn’t see animations.

    Fix

    Now to the fix: simply make your own view extending SwitchCompat, and override your isShown() like this:

    @Override
    public boolean isShown() {
        return getVisibility() == VISIBLE;
    }
    

    Use this SwitchView for your widgetLayout style, and animations work again 😀

    Styles:

    <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
        …
        <item name="android:checkBoxPreferenceStyle">@style/Preference.SwitchView</item>
        …
    </style>
    
    <style name="Preference.SwitchView">
        <item name="android:widgetLayout">@layout/preference_switch_view</item>
    </style>
    

    Widget layout:

    <de.Maxr1998.example.preference.SwitchView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@android:id/checkbox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@null"
        android:clickable="false"
        android:focusable="false" />
    

    Sometimes Extending from a Class is not the best solution. To avoid loosing the animations you could instead Compose it, I meant creating a Class where you have a SwitchPreference field variable and apply the new logic to it. It’s like a wrapper. This worked for me.

    i manage to fix it like this and animations is working before it was going to the state directly without animation:

    FIX:

    CustomSwitchCompat.class

    public class CustomSwitchCompat extends SwitchCompat {
    
        public CustomSwitchCompat(Context context) {
            super(context);
        }
    
        public CustomSwitchCompat(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public CustomSwitchCompat(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        public boolean isShown() {
            return getVisibility() == VISIBLE;
        }
    
    }
    

    In your layout do this: preference_switch_layout.xml

    <com.example.CustomSwitchCompat
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@android:id/checkbox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@null"
        android:clickable="false"
        android:focusable="false"
        app:switchMinWidth="55dp"/>
    

    and in your preference.xml do this:

    <CheckBoxPreference
       android:defaultValue="false"
       android:key=""
       android:widgetLayout="@layout/preference_switch_layout"
       android:summary=""
       android:title="" />
    

    public class SwitchPreference extends android.preference.SwitchPreference {

    public SwitchPreference(Context context) {
        this(context, null);
    }
    
    public SwitchPreference(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.checkBoxPreferenceStyle);
    }
    
    public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }
    
    public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    
        try {
            Field canRecycleLayoutField = Preference.class.getDeclaredField("mCanRecycleLayout");
            canRecycleLayoutField.setAccessible(true);
            canRecycleLayoutField.setBoolean(this, true);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    
    Android Babe is a Google Android Fan, All about Android Phones, Android Wear, Android Dev and Android Games Apps and so on.