【问题标题】:Animated Icon for ActionItemActionItem 的动画图标
【发布时间】:2012-04-01 15:52:58
【问题描述】:

我一直在到处寻找合适的解决方案来解决我的问题,但似乎还没有找到。我有一个 ActionBar (ActionBarSherlock),它的菜单是从 XML 文件扩展而来的,并且该菜单包含一个项目,并且该项目显示为一个 ActionItem。

菜单:

<menu xmlns:android="http://schemas.android.com/apk/res/android" >    
    <item
        android:id="@+id/menu_refresh"       
        android:icon="@drawable/ic_menu_refresh"
        android:showAsAction="ifRoom"
        android:title="Refresh"/>    
</menu>

活动:

[...]
  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getSupportMenuInflater().inflate(R.menu.mymenu, menu);
    return true;
  }
[...]

ActionItem 显示为带有图标且没有文本,但是当用户单击 ActionItem 时,我希望图标开始动画,更具体地说,是原地旋转。有问题的图标是一个刷新图标。

我意识到 ActionBar 支持使用自定义视图 (Adding an Action View) 但是此自定义视图已扩展为覆盖 ActionBar 的整个区域并实际上阻止了除应用程序图标之外的所有内容,在我的情况下不是寻找。

所以我的下一个尝试是尝试使用 AnimationDrawable 并逐帧定义我的动画,将 drawable 设置为菜单项的图标,然后在 onOptionsItemSelected(MenuItem item) 中获取图标并使用 ((AnimationDrawable)item.getIcon()).start() 开始制作动画.然而这并不成功。有谁知道实现这种效果的任何方法?

【问题讨论】:

    标签: android android-animation actionbarsherlock


    【解决方案1】:

    你在正确的轨道上。以下是 GitHub Gaug.es 应用程序将如何实现它。

    首先他们定义了一个动画 XML:

    <rotate xmlns:android="http://schemas.android.com/apk/res/android"
        android:fromDegrees="0"
        android:toDegrees="360"
        android:pivotX="50%"
        android:pivotY="50%"
        android:duration="1000"
        android:interpolator="@android:anim/linear_interpolator" />
    

    现在为动作视图定义一个布局:

    <ImageView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_action_refresh"
        style="@style/Widget.Sherlock.ActionButton" />
    

    我们需要做的就是在点击项目时启用此视图:

     public void refresh() {
         /* Attach a rotating ImageView to the refresh item as an ActionView */
         LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         ImageView iv = (ImageView) inflater.inflate(R.layout.refresh_action_view, null);
    
         Animation rotation = AnimationUtils.loadAnimation(getActivity(), R.anim.clockwise_refresh);
         rotation.setRepeatCount(Animation.INFINITE);
         iv.startAnimation(rotation);
    
         refreshItem.setActionView(iv);
    
         //TODO trigger loading
     }
    

    加载完成后,只需停止动画并清除视图:

    public void completeRefresh() {
        refreshItem.getActionView().clearAnimation();
        refreshItem.setActionView(null);
    }
    

    你就完成了!

    一些额外的事情要做:

    • 缓存动作视图布局膨胀和动画膨胀。它们很慢,因此您只想执行一次。
    • 添加null 签入completeRefresh()

    这是应用程序的拉取请求:https://github.com/github/gauges-android/pull/13/files

    【讨论】:

    • 完美!我一直在遵循 Android 文档页面上使用的方法:Adding an ActionView 并且使用该方法,整个 AB 都被视图阻止了。再次感谢您!
    • 很好的答案,但是 getActivity() 不再可用,请改用 getApplication()。
    • @Alborz 这完全针对您的应用程序,而不是一般规则。这完全取决于您放置刷新方法的位置。
    • 如果你的图片是方形的并且尺寸适合一个动作项,那么应该不会跳转。
    • 我在实现这个时也遇到了按钮跳到一边的问题。这是因为我没有使用 Widget.Sherlock.ActionButton 样式。我通过在我自己的主题中添加 android:paddingLeft="12dp"android:paddingRight="12dp" 来纠正这个问题。
    【解决方案2】:

    我在使用 ActionBarSherlock 的解决方案上做了一些工作,我想出了这个:

    res/layout/indeterminate_progress_action.xml

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="48dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:paddingRight="12dp" >
    
        <ProgressBar
            style="@style/Widget.Sherlock.ProgressBar"
            android:layout_width="44dp"
            android:layout_height="32dp"
            android:layout_gravity="left"
            android:layout_marginLeft="12dp"
            android:indeterminate="true"
            android:indeterminateDrawable="@drawable/rotation_refresh"
            android:paddingRight="12dp" />
    
    </FrameLayout>
    

    res/layout-v11/indeterminate_progress_action.xml

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center" >
    
        <ProgressBar
            style="@style/Widget.Sherlock.ProgressBar"
            android:layout_width="32dp"
            android:layout_gravity="left"
            android:layout_marginRight="12dp"
            android:layout_marginLeft="12dp"
            android:layout_height="32dp"
            android:indeterminateDrawable="@drawable/rotation_refresh"
            android:indeterminate="true" />
    
    </FrameLayout>
    

    res/drawable/rotation_refresh.xml

    <?xml version="1.0" encoding="utf-8"?>
    <rotate xmlns:android="http://schemas.android.com/apk/res/android"
        android:pivotX="50%"
        android:pivotY="50%"
        android:drawable="@drawable/ic_menu_navigation_refresh"
        android:repeatCount="infinite" >
    
    </rotate>
    

    活动中的代码(我在 ActivityWithRefresh 父类中有)

    // Helper methods
    protected MenuItem refreshItem = null;  
    
    protected void setRefreshItem(MenuItem item) {
        refreshItem = item;
    }
    
    protected void stopRefresh() {
        if (refreshItem != null) {
            refreshItem.setActionView(null);
        }
    }
    
    protected void runRefresh() {
        if (refreshItem != null) {
            refreshItem.setActionView(R.layout.indeterminate_progress_action);
        }
    }
    

    在创建菜单项的活动中

    private static final int MENU_REFRESH = 1;
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        menu.add(Menu.NONE, MENU_REFRESH, Menu.NONE, "Refresh data")
                .setIcon(R.drawable.ic_menu_navigation_refresh)
                .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS);
        setRefreshItem(menu.findItem(MENU_REFRESH));
        refreshData();
        return super.onCreateOptionsMenu(menu);
    }
    
    private void refreshData(){
        runRefresh();
        // work with your data
        // for animation to work properly, make AsyncTask to refresh your data
        // or delegate work anyhow to another thread
        // If you'll have work at UI thread, animation might not work at all
        stopRefresh();
    }
    

    还有图标,这是drawable-xhdpi/ic_menu_navigation_refresh.png

    这可以在http://developer.android.com/design/downloads/index.html#action-bar-icon-pack找到

    【讨论】:

    • 仅供参考,为了正确显示,我还必须添加带有 android:layout_marginRight="16dp" 的 layout-tvdpi-v11/indeterminate_progress_action.xml。我不知道这种不一致是否是我的代码、ABS 或 SDK 中的错误。
    • 我已经用我的几个应用程序测试了这个解决方案,它们都使用相同的代码。所以我想这是您的代码中的一些不一致之处,因为 ABS(4.2.0)和 SDK(API 14 及更高版本)是共享的;-)
    • 你在 Nexus 7 上试过了吗? (不是鸸鹋,真正的设备)它唯一的设备显示不正确,因此是 tvdpi 设置。
    • @Iraklis 不,我没有这样的设备。所以是的,现在我明白了,你调试了什么。太好了,随时添加它来回答。
    【解决方案3】:

    除了 Jake Wharton 所说的之外,您还应该适当地执行以下操作,以确保动画在加载完成后顺利停止并且不会跳来跳去

    首先,创建一个新的布尔值(用于整个类):

    private boolean isCurrentlyLoading;
    

    找到开始加载的方法。当 Activity 开始加载时,将布尔值设置为 true。

    isCurrentlyLoading = true;
    

    找到加载完成时启动的方法。不要清除动画,而是将布尔值设置为 false。

    isCurrentlyLoading = false;
    

    在你的动画上设置一个 AnimationListener:

    animationRotate.setAnimationListener(new AnimationListener() {
    

    然后,动画每执行一次,即当你的图标旋转一圈时,检查加载状态,如果不再加载,动画将停止。

    @Override
    public void onAnimationRepeat(Animation animation) {
        if(!isCurrentlyLoading) {
            refreshItem.getActionView().clearAnimation();
            refreshItem.setActionView(null);
        }
    }
    

    这样,动画只有在已经旋转到最后才会停止,并且会很快重复并且不再加载。

    这至少是我想实现杰克的想法时所做的。

    【讨论】:

      【解决方案4】:

      还有一个选项可以在代码中创建旋转。完整片段:

          MenuItem item = getToolbar().getMenu().findItem(Menu.FIRST);
          if (item == null) return;
      
          // define the animation for rotation
          Animation animation = new RotateAnimation(0.0f, 360.0f,
                  Animation.RELATIVE_TO_SELF, 0.5f,
                  Animation.RELATIVE_TO_SELF, 0.5f);
          animation.setDuration(1000);
          //animRotate = AnimationUtils.loadAnimation(this, R.anim.rotation);
      
          animation.setRepeatCount(Animation.INFINITE);
      
          ImageView imageView = new ImageView(this);
          imageView.setImageDrawable(UIHelper.getIcon(this, MMEXIconFont.Icon.mmx_refresh));
      
          imageView.startAnimation(animation);
          item.setActionView(imageView);
      

      【讨论】:

      • 使用这个并且点击 onOptionsItemSelected 不会被调用。
      • 如果自 2016 年以来 Android 发生了大量变化,我不会感到惊讶,当这个问题得到解答时。请注意,这是来自实际开源应用程序的代码,并且当时一直在工作。
      【解决方案5】:

      借助支持库,我们可以在没有自定义 actionView 的情况下为图标设置动画。

      private AnimationDrawableWrapper drawableWrapper;    
      
      @Override
      public boolean onCreateOptionsMenu(Menu menu) {
          //inflate menu...
      
          MenuItem menuItem = menu.findItem(R.id.your_icon);
          Drawable icon = menuItem.getIcon();
          drawableWrapper = new AnimationDrawableWrapper(getResources(), icon);
          menuItem.setIcon(drawableWrapper);
          return true;
      }
      
      public void startRotateIconAnimation() {
          ValueAnimator animator = ObjectAnimator.ofInt(0, 360);
          animator.addUpdateListener(animation -> {
              int rotation = (int) animation.getAnimatedValue();
              drawableWrapper.setRotation(rotation);
          });
          animator.start();
      }
      

      我们不能直接为drawable设置动画,所以使用DrawableWrapper(来自android.support.v7 for API

      public class AnimationDrawableWrapper extends DrawableWrapper {
      
          private float rotation;
          private Rect bounds;
      
          public AnimationDrawableWrapper(Resources resources, Drawable drawable) {
              super(vectorToBitmapDrawableIfNeeded(resources, drawable));
              bounds = new Rect();
          }
      
          @Override
          public void draw(Canvas canvas) {
              copyBounds(bounds);
              canvas.save();
              canvas.rotate(rotation, bounds.centerX(), bounds.centerY());
              super.draw(canvas);
              canvas.restore();
          }
      
          public void setRotation(float degrees) {
              this.rotation = degrees % 360;
              invalidateSelf();
          }
      
          /**
           * Workaround for issues related to vector drawables rotation and scaling:
           * https://code.google.com/p/android/issues/detail?id=192413
           * https://code.google.com/p/android/issues/detail?id=208453
           */
          private static Drawable vectorToBitmapDrawableIfNeeded(Resources resources, Drawable drawable) {
              if (drawable instanceof VectorDrawable) {
                  Bitmap b = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
                  Canvas c = new Canvas(b);
                  drawable.setBounds(0, 0, c.getWidth(), c.getHeight());
                  drawable.draw(c);
                  drawable = new BitmapDrawable(resources, b);
              }
              return drawable;
          }
      }
      

      我从这里获得了 DrawableWrapper 的想法:https://stackoverflow.com/a/39108111/5541688

      【讨论】:

        【解决方案6】:

        这是我非常简单的解决方案(例如,需要一些重构) 与标准 MenuItem 一起使用, 您可以将它与任意数量的状态、图标、动画、逻辑等一起使用。

        在Activity类中:

        private enum RefreshMode {update, actual, outdated} 
        

        标准监听器:

        public boolean onOptionsItemSelected(MenuItem item) {
            switch (item.getItemId()) {
                case R.id.menu_refresh: {
                    refreshData(null);
                    break;
                }
            }
        }
        

        进入 refreshData() 做这样的事情:

        setRefreshIcon(RefreshMode.update);
        // update your data
        setRefreshIcon(RefreshMode.actual);
        

        为图标定义颜色或动画的方法:

         void setRefreshIcon(RefreshMode refreshMode) {
        
            LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            Animation rotation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.rotation);
            FrameLayout iconView;
        
            switch (refreshMode) {
                case update: {
                    iconView = (FrameLayout) inflater.inflate(R.layout.refresh_action_view,null);
                    iconView.startAnimation(rotation);
                    toolbar.getMenu().findItem(R.id.menu_refresh).setActionView(iconView);
                    break;
                }
                case actual: {
                    toolbar.getMenu().findItem(R.id.menu_refresh).getActionView().clearAnimation();
                    iconView = (FrameLayout) inflater.inflate(R.layout.refresh_action_view_actual,null);
                    toolbar.getMenu().findItem(R.id.menu_refresh).setActionView(null);
                    toolbar.getMenu().findItem(R.id.menu_refresh).setIcon(R.drawable.ic_refresh_24dp_actual);
                    break;
                }
                case outdated:{
                    toolbar.getMenu().findItem(R.id.menu_refresh).setIcon(R.drawable.ic_refresh_24dp);
                    break;
                }
                default: {
                }
            }
        }
        

        有 2 个带有图标的布局 (R.layout.refresh_action_view (+ "_actual") ):

        <FrameLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:gravity="center">
        <ImageView
            android:src="@drawable/ic_refresh_24dp_actual" // or ="@drawable/ic_refresh_24dp"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:layout_margin="12dp"/>
        </FrameLayout>
        

        本例中的标准旋转动画(R.anim.rotation):

        <rotate xmlns:android="http://schemas.android.com/apk/res/android"
        android:fromDegrees="0"
        android:toDegrees="360"
        android:pivotX="50%"
        android:pivotY="50%"
        android:duration="1000"
        android:repeatCount="infinite"
        />
        

        【讨论】:

          【解决方案7】:

          最好的方法在这里:

          public class HomeActivity extends AppCompatActivity {
              public static ActionMenuItemView btsync;
              public static RotateAnimation rotateAnimation;
          
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              rotateAnimation = new RotateAnimation(360, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
              rotateAnimation.setDuration((long) 2*500);
              rotateAnimation.setRepeatCount(Animation.INFINITE);
          

          然后:

          private void sync() {
              btsync = this.findViewById(R.id.action_sync); //remember that u cant access this view at onCreate() or onStart() or onResume() or onPostResume() or onPostCreate() or onCreateOptionsMenu() or onPrepareOptionsMenu()
              if (isSyncServiceRunning(HomeActivity.this)) {
                  showConfirmStopDialog();
              } else {
                  if (btsync != null) {
                      btsync.startAnimation(rotateAnimation);
                  }
                  Context context = getApplicationContext();
                  context.startService(new Intent(context, SyncService.class));
              }
          }
          

          记住你不能访问“btsync = this.findViewById(R.id.action_sync);”在 onCreate() 或 onStart() 或 onResume() 或 onPostResume() 或 onPostCreate() 或 onCreateOptionsMenu() 或 onPrepareOptionsMenu() 如果您想在活动开始后立即获取它,请将其放入延迟后:

          public static void refreshSync(Activity context) {
              Handler handler = new Handler(Looper.getMainLooper());
              handler.postDelayed(new Runnable() {
                  public void run() {
                      btsync = context.findViewById(R.id.action_sync);
                      if (btsync != null && isSyncServiceRunning(context)) {
                          btsync.startAnimation(rotateAnimation);
                      } else if (btsync != null) {
                          btsync.clearAnimation();
                      }
                  }
              }, 1000);
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2017-03-28
            • 2011-04-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-10-08
            • 1970-01-01
            相关资源
            最近更新 更多