【问题标题】:Change Switch state without animation在没有动画的情况下更改 Switch 状态
【发布时间】:2014-11-26 00:20:55
【问题描述】:

在我的 Android 项目中,我有一个 ListView 行,其中包含 SwitchCompat 项(Switch 小部件的 AppCompat)。

当我滚动到列表中并使用recycled 视图调用MyAdaptergetView(...) 方法时,就会出现我的问题。我重新定义了正确的Switch 状态,但动画是可见的。

这种情况下有没有办法阻止动画?

【问题讨论】:

    标签: android android-listview android-switch


    【解决方案1】:

    调用jumpDrawablesToCurrentState()跳过动画

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

    【讨论】:

    • 我发现这会将开关的颜色更改为处于活动状态,但仍处于“未选中”位置..
    • 使用数据绑定,在jumpDrawablesToCurrentState()之前调用executePendingBindings()
    • 如果您在列表中滚动,这对于单选按钮/复选框非常有用。
    【解决方案2】:

    我终于找到了解决方案,但似乎不是很干净:

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

    如果有更好的解决方案,请分享。

    【讨论】:

    • 不幸的是,您的方式似乎是唯一的方式。查看 API 21 中 setChecked() 的代码 Switch: if (isAttachedToWindow() && isLaidOut()) { animateThumbToCheckedState(checked); }
    • @kcoppock,此代码已在 SwitchCompat 中更改为 if (isAttachedToWindow() && isLaidOut() && isShown()),因此可以暂时隐藏它:switch.setVisibility(View.INVISIBLE); switch.setChecked(); switch.setVisibility(View.VISIBLE);
    【解决方案3】:

    我遇到了同样的问题,我设法通过一些最小的反射解决了它。

    用法:

    要在没有动画的情况下更改开关状态,请调用 setChecked(boolean checked, boolean animate) 方法,并将 animate 参数设置为 false。如果在调用此方法的那一刻开关已经开始动画,则动画将停止并且开关跳转到所需的位置。

    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.** { *; }
    

    【讨论】:

    • 请问您是否知道为什么在某些情况下它实际上会跳过被选中/取消选中的动画?我制作了一个支持材料设计偏好的库(这里:github.com/AndroidDeveloperLB/MaterialPreferenceLibrary),我不知道为什么它会跳过动画并直接切换到切换状态。
    • 哦哦反射
    【解决方案4】:

    如果您使用 Android 数据绑定,可能会出现列表中播放动画的问题。

    要解决这个问题,请在设置数据后运行binding.executePendingBindings() 方法——它会刷新当前帧中组件的绑定状态,并且不会等待下一个到来。

    您可能已经猜到了——下一帧是动画

    【讨论】:

      【解决方案5】:

      使用 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

      【讨论】:

      • 还可以考虑将binding.executePendingBindings() 用于列表元素,因为这是此动画在屏幕上运行的主要原因——额外执行一帧
      【解决方案6】:

      对于 Kotlin 开发人员:

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

      及用法:

      mySwitch.setCheckedWithoutAnimation(true)
      

      【讨论】:

      • 您介意解释一下为什么会跳过动画吗?因为如果您查看SwitchCompat#setChecked 的代码,可见性对动画没有影响。
      • 根据您在父布局上启用的LayoutTransition,我认为这可能仍然是动画,但有所不同。它应该从不可见动画到可见:)
      【解决方案7】:

      Rolf ツ 答案的 Kotlin 示例。

      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
          }
      }
      

      【讨论】:

        【解决方案8】:

        就我而言,我正在使用新的材料库:

        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)
        

        【讨论】:

        • @Vlad 目前我正在使用 com.google.android.material:material:1.1.0-beta02 它对我来说工作得很好。它不适合您的原因可能有很多。很遗憾,我无法帮助您提供您提供的信息。
        猜你喜欢
        • 2014-03-08
        • 2018-07-04
        • 1970-01-01
        • 1970-01-01
        • 2015-03-05
        • 2018-06-20
        • 2020-01-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多