Анимированная иконка для ActionItem


Я искал везде правильное решение моей проблемы, и я не могу, кажется, найти его еще. У меня есть 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, I хотите, чтобы значок начал анимацию, более конкретно, вращаясь на месте. Рассматриваемый значок является значком обновления.

Я понимаю, что ActionBar поддерживает использование пользовательских представлений ( добавление вида действия) однако этот пользовательский вид расширяется, чтобы охватить всю область панели действий и фактически блокирует все, кроме значка приложения, который в моем случае не то, что я искал.

так что мой следующий шаг состоял в том, чтобы попытаться использовать AnimationDrawable и определить свой анимация кадр за кадром, установите drawable в качестве значка для пункта меню, а затем в onOptionsItemSelected(MenuItem item) получить значок и начать анимацию с помощью ((AnimationDrawable)item.getIcon()).start(). Однако это была неудачной. Кто-нибудь знает, как достичь этого эффекта?

5 90

5 ответов:

вы на правильном пути. Вот как 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()

вот запрос pull на приложение:https://github.com/github/gauges-android/pull/13/files

Я немного поработал над решением с помощью 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>

код в activity (у меня он есть в 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
drawable-xhdpi/ic_menu_navigation_refresh.png

это можно найти в http://developer.android.com/design/downloads/index.html#action-bar-icon-pack

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

сначала создайте новое логическое значение (для всего класса):

private boolean isCurrentlyLoading;

найти метод, который запускает загрузку. Установите логическое значение 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);
    }
}

таким образом, анимация может быть остановлена только в том случае, если она уже повернута до конца и будет повторена в ближайшее время, и она не загружается больше.

это по крайней мере то, что я сделал, когда я хотел реализовать идею Джейка.

существует также возможность создать вращение в коде. Полный СНиП:

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

с библиотекой поддержки мы можем анимировать значок без пользовательского 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.поддержка.v7 для 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