Изменить состояние переключателя без анимации
В моем проекте Android у меня есть ListView
со строками, содержащими SwitchCompat
элементы (AppCompat для Switch
виджет).
Моя проблема возникает, когда я прокручиваю в список и getView(...)
метод MyAdapter
вызывается с recycled
Посмотреть. Я переопределяю правильный Switch
состояние, но анимация видна.
Есть решение для предотвращения анимации в этом случае?
8 ответов
Я наконец нашел решение, но, кажется, не очень чистый:
ViewGroup viewGroup = (ViewGroup) view; // the recycled view
viewGroup.removeView(switch);
switch.setChecked(states[index]);
viewGroup.addView(switch);
Если есть лучшее решение, пожалуйста, поделитесь им.
Проблема с воспроизведением анимации в списке может возникнуть, если вы используете привязку данных Android.
Чтобы решить это, запустите binding.executePendingBindings()
метод после установки данных - он обновит состояние привязки для компонента в текущем кадре и не будет ждать следующего.
Как вы уже, наверное, догадались, следующий кадр - это анимация.
У меня была та же проблема, и мне удалось решить ее, используя минимальное отражение.
Использование:
Чтобы изменить состояние переключателя без анимации, вызовите setChecked(boolean checked, boolean 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://stackru.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.** { *; }
Использование 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()
(спасибо AAverin)
Для разработчика Kotlin:
fun SwitchCompat.setCheckedWithoutAnimation(checked: Boolean) {
val beforeVisibility = visibility
visibility = View.INVISIBLE
isChecked = checked
visibility = beforeVisibility
}
И использование:
mySwitch.setCheckedWithoutAnimation(true)
Kotlin образец user1052697answer.
class SwitchImproved(context: Context, attributeSet: AttributeSet) : SwitchCompat(context, attributeSet) {
private lateinit var methodCancelPositionAnimator: Method
private lateinit var methodSetThumbPosition: Method
init {
initHack()
}
fun setChecked(checked: Boolean, animate: Boolean = true) {
super.setChecked(checked)
if (!animate) {
methodCancelPositionAnimator.invoke(this)
methodSetThumbPosition.invoke(this, if (isChecked) 1 else 0)
}
}
private fun initHack() {
methodCancelPositionAnimator = SwitchCompat::class.java.getDeclaredMethod("cancelPositionAnimator")
methodSetThumbPosition = SwitchCompat::class.java.getDeclaredMethod("setThumbPosition", Float::class.javaPrimitiveType)
methodCancelPositionAnimator.isAccessible = true
methodSetThumbPosition.isAccessible = true
}
}
В моем случае я использую новую библиотеку материалов:
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 = 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)