Extending Preference classes in Android Lollipop = losing animation
Asked Answered
T

5

7

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).

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).

Thorn answered 19/5, 2015 at 21:40 Comment(4)
Did you find a solution?Whipstall
No, I did not. Actually, I have a library for material preferences before Lollipop: github.com/ferrannp/material-preferences and the only bug it has is this one: github.com/ferrannp/material-preferences/issues/9Thorn
code.google.com/p/android/issues/detail?id=85392 is relevant.Salesgirl
There's a good answer here that worked for me.Exhilarant
C
0

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 :D

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" />
Chapiter answered 17/11, 2015 at 21:1 Comment(2)
This sounds promising! However, I did try it in my lib (github.com/ferrannp/material-preferences) and I didn't get it to work (no animation still on pre-Lollipop). Do you have maybe a working repo? . By the way, I don't get animation on pre-Lollipop neither on a Switch or on a Checkbox.Thorn
Animation works on Lollipop and before. I don't have set up a repo for this, it's only the code listed here anyway. Btw, this solution has a bug as well, since the animation gets played sometimes while scrolling through the list. Additionally, the switch's thumb is sometimes on the wrong side, though showing the correct color..Chapiter
M
0

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.

Muniments answered 11/2, 2017 at 0:13 Comment(0)
C
0

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="" />
Chiachiack answered 13/5, 2017 at 18:31 Comment(0)
C
0

I was having this issue, when I was using custom layout (app:layout) for SwitchPreference. At first, switch animation was triggered, but after a little scrolling it stopped and switch was jumping without animation. I tried every solution from stackoverflow, but nothing helped.

After debugging of SwitchCompat.setChecked method I found out that this condition is failing:

    public void setChecked(boolean checked) {
        ...

        if (getWindowToken() != null && ViewCompat.isLaidOut(this)) {
            animateThumbToCheckedState(checked);
        } else {
            // Immediately move the thumb to the new position.
            cancelPositionAnimator();
            setThumbPosition(checked ? 1 : 0);
        }
    }

Concretely ViewCompat.isLaidOut(this) returned false. I guess this is a bug either in View or Preference (or subclasses). Anyway, I was able to fix this with little hack.

I created a subclass of SwitchCompat and did override setChecked method, where I call requestLayout() and in onNextLayout I call SwitchCompat's setChecked method. This guarantees that isLaidOut condition is true when changing checked state.

Full code of custom SwitchCompat:

class SwitchCompatFix @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = androidx.appcompat.R.attr.switchStyle,
): SwitchCompat(context, attrs, defStyleAttr) {

    override fun setChecked(checked: Boolean) {
        doOnNextLayout {
            post { super.setChecked(checked) }
        }
        requestLayout()
    }
}
Cherian answered 14/4, 2022 at 10:44 Comment(0)
G
-1

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();
    }
}
Gurolinick answered 6/10, 2015 at 2:49 Comment(1)
Hi, can you elaborate on this? I tried and I am getting a java.lang.NoSuchFieldException: mCanRecycleLayout error.Thorn

© 2022 - 2024 — McMap. All rights reserved.