Изменение состояния переключения без анимации

В моем проекте Android у меня есть ListView с строками, содержащими SwitchCompat элементы (виджет AppCompat для Switch).

Моя проблема возникает, когда я просматриваю список и getView(...) метод MyAdapter вызывается с представлением recycled. Я переопределяю правильное состояние Switch, но анимация видна.

Есть ли решение предотвратить анимацию в этом случае?

enter image description here

Ответы

Ответ 1

Наконец-то я нашел решение, но кажется не совсем чистым:

ViewGroup viewGroup = (ViewGroup) view; // the recycled view
viewGroup.removeView(switch);
switch.setChecked(states[index]);
viewGroup.addView(switch);

Если существует лучшее решение, поделитесь им.

Ответ 2

Вызовите jumpDrawablesToCurrentState() чтобы пропустить анимацию

switchCompat.setChecked(true);
switchCompat.jumpDrawablesToCurrentState();

Ответ 3

У меня была та же проблема, и мне удалось ее решить, используя некоторое минимальное отражение.

Применение:

Чтобы изменить состояние переключателя без анимации, вызовите метод setChecked(boolean checked, boolean animate) с false для параметра animate. Если коммутатор уже анимационирует в данный момент, этот метод будет вызван, анимация будет остановлена, и коммутатор переместится в нужную позицию.

SwitchCompatFix.java

import android.content.Context;
import android.support.v7.widget.SwitchCompat;
import android.util.AttributeSet;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Work around for: http://stackoverflow.com/questions/27139262/change-switch-state-without-animation
 * Possible fix for bug 101107: https://code.google.com/p/android/issues/detail?id=101107
 *
 * Version 0.2
 * @author Rolf Smit
 */
public class SwitchCompatFix extends SwitchCompat {

    public SwitchCompatFix(Context context) {
        super(context);
        initHack();
    }

    public SwitchCompatFix(Context context, AttributeSet attrs) {
        super(context, attrs);
        initHack();
    }

    public SwitchCompatFix(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initHack();
    }

    private Method methodCancelPositionAnimator = null;
    private Method methodSetThumbPosition = null;

    private void initHack(){
        try {
            methodCancelPositionAnimator = SwitchCompat.class.getDeclaredMethod("cancelPositionAnimator");
            methodSetThumbPosition = SwitchCompat.class.getDeclaredMethod("setThumbPosition", float.class);
            methodCancelPositionAnimator.setAccessible(true);
            methodSetThumbPosition.setAccessible(true);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    public void setChecked(boolean checked, boolean animate){
        // Java does not support super.super.xxx calls, a call to the SwitchCompat default setChecked method is needed.
        super.setChecked(checked);
        if(!animate) {

            // See original SwitchCompat source:
            // Calling the super method may result in setChecked() getting called
            // recursively with a different value, so load the REAL value...
            checked = isChecked();

            // Cancel any running animations (started by super.setChecked()) and immediately move the thumb to the new position
            try {
                if(methodCancelPositionAnimator != null && methodSetThumbPosition != null) {
                    methodCancelPositionAnimator.invoke(this);
                    methodSetThumbPosition.invoke(this, checked ? 1 : 0);
                }
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}

Примечание для пользователей proguard:

Поскольку этот метод использует отражение, может потребоваться дополнительное правило proguard (если оно еще не представлено).

-keep class android.support.v7.widget.SwitchCompat {
    private void cancelPositionAnimator();
    private void setThumbPosition(float);
}

Это дополнительное правило не требуется, если вы используете один из следующих правил proguard (или похожих):

-keep class android.support.v7.widget.** { *; }
-keep class android.support.v7.** { *; }

Ответ 4

Использование SwitchCompat и DataBinding

@BindingAdapter({"bind:checkedState"})
public static void setCheckedState(SwitchCompat switchView, boolean checked) {
    int visibility = switchView.getVisibility();
    switchView.setVisibility(View.INVISIBLE);
    switchView.setChecked(checked);
    switchView.setVisibility(visibility);
}

Тогда в xml:

<android.support.v7.widget.SwitchCompat
    android:id="@+id/my_switch"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:checkedState="@{my_data.checked}"/>

И не забудьте вызвать executePendingBindings() (спасибо А.А.верин)

Ответ 5

Проблема с анимацией, играющей в списке, может присутствовать, если вы используете привязку данных для Android.

Чтобы решить эту проблему, запустите метод binding.executePendingBindings() после установки данных - он обновит состояние привязки для компонента в текущем фрейме и не будет ждать следующего следующего.

Как вы уже догадались - следующий кадр - это анимация

Ответ 6

Для разработчика Kotlin:

fun SwitchCompat.setCheckedWithoutAnimation(checked: Boolean) {
    val beforeVisibility = visibility
    visibility = View.INVISIBLE
    isChecked = checked
    visibility = beforeVisibility
}

И использование:

mySwitch.setCheckedWithoutAnimation(true)

Ответ 7

В моем случае я использую новую библиотеку материалов:

implementation 'com.google.android.material:material:1.1.0-alpha07'

и в методе setChecked этого класса есть это условие:

if (getWindowToken() != null && ViewCompat.isLaidOut(this))

Так что я сделал, чтобы создать класс, который выходит из этого SwitchMaterial, и работать с "isLaidOut". Код следующий (без конструкторов):

class SwitchCustomView : SwitchMaterial {

  private var laidOutForAnimation: Boolean

  init {
    laidOutForAnimation = false
  }

  fun setChecked(checked: Boolean, animate: Boolean) {
    if (!animate) {
      laidOutForAnimation = true
    }
    super.setChecked(checked)
    laidOutForAnimation = false
  }

  override fun isLaidOut(): Boolean {
    return if (laidOutForAnimation) {
      return false
    } else {
      super.isLaidOut()
    }
  }
}

Тогда просто используйте этот класс в вашем xml и вызывайте программно

setChecked(checked: Boolean, animate: Boolean)