Animowana ikona dla ActionItem

Szukałem wszędzie odpowiedniego rozwiązania mojego problemu i nie mogę go jeszcze znaleźć. Mam ActionBar (ActionBarSherlock) z menu, które jest napompowane z pliku XML i że menu zawiera jeden element i że jeden element jest wyświetlany jako ActionItem.

Menu:

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

Aktywność:

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

ActionItem jest wyświetlany z ikoną i bez tekstu, jednak gdy użytkownik kliknie na ActionItem, chcę, aby ikona zaczęła animować, więcej w szczególności obracanie w miejscu. Ikona, o której mowa, jest ikoną odświeżania.

Zdaję sobie sprawę, że ActionBar ma wsparcie dla używania niestandardowych widoków (dodawanie widoku akcji ), jednak ten niestandardowy widok jest rozszerzony, aby objąć cały obszar ActionBar i faktycznie blokuje wszystko z wyjątkiem ikony aplikacji, która w moim przypadku nie jest tym, czego szukałem.

Więc moją kolejną próbą było użycie AnimationDrawable i zdefiniowanie mojej animacji klatka po klatce, ustawienie drawable jako ikony dla element menu, a następnie w onOptionsItemSelected(MenuItem item) Pobierz ikonę i rozpocznij animację za pomocą ((AnimationDrawable)item.getIcon()).start(). Nie powiodło się to jednak. Czy ktoś zna jakiś sposób na osiągnięcie tego efektu?

Author: Alex Fu, 2012-03-16

5 answers

Jesteś na dobrej drodze. Oto jak GitHub Gaug.es aplikacja będzie go wdrażać.

Najpierw definiują animację 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" />

Zdefiniuj teraz układ widoku akcji:

<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" />

Wystarczy włączyć ten widok za każdym razem, gdy element zostanie kliknięty:

 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
 }

Po zakończeniu ładowania, po prostu zatrzymaj animację i wyczyść Widok:

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

Kilka dodatkowych rzeczy do zrobienia:

  • buforuj widok akcji inflacja układu i inflacja animacji. Są powolne, więc chcesz je zrobić tylko raz.
  • dodaj null sprawdzanie w completeRefresh()

Oto żądanie pull w aplikacji: https://github.com/github/gauges-android/pull/13/files

 169
Author: Jake Wharton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-26 13:32:39

Pracowałem trochę nad rozwiązaniem używając ActionBarSherlock, wpadłem na to:

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>

Kod w activity (mam go w klasie rodzica 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);
    }
}

W aktywności tworzenie pozycji menu

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

A ikona, To jest drawable-xhdpi/ic_menu_navigation_refresh.png
drawable-xhdpi / ic_menu_navigation_refresh.png

To można znaleźć w http://developer.android.com/design/downloads/index.html#action-bar-icon-pack

 16
Author: Marek Sebera,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-11 07:48:46

Oprócz tego, co powiedział Jake Wharton, powinieneś prawdopodobnie wykonać następujące czynności, aby upewnić się, że animacja zatrzyma się płynnie i nie przeskakuje po zakończeniu ładowania.

Najpierw Utwórz nowy boolean (dla całej klasy):

private boolean isCurrentlyLoading;

Znajdź metodę, która rozpocznie ładowanie. Ustaw wartość logiczną na true, gdy aktywność zaczyna się ładować.

isCurrentlyLoading = true;

Znajdź metodę, która zostanie uruchomiona po zakończeniu ładowania. Zamiast wyczyścić animację, ustaw Twój boolean do false.

isCurrentlyLoading = false;

Ustaw AnimationListener na swojej animacji:

animationRotate.setAnimationListener(new AnimationListener() {

Następnie, za każdym razem, gdy animacja została wykonana jeden raz, to znaczy, gdy ikona wykonała jeden obrót, sprawdź stan ładowania, a jeśli nie ładuje się więcej, animacja zostanie zatrzymana.

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

W ten sposób animacja może zostać zatrzymana tylko wtedy, gdy jest już obrócona do końca i zostanie wkrótce powtórzona i nie jest już ładowana.

To jest przynajmniej to, co zrobiłem, gdy chciałem wdrożyć pomysł Jake ' a.

 6
Author: Lesik2008,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-19 16:08:54

Istnieje również opcja tworzenia rotacji w kodzie. Pełny snip:

    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);
 1
Author: Alen Siljak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-27 14:48:12

Z biblioteką wsparcia możemy animować ikonę bez custom 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();
}

Nie możemy bezpośrednio animować drawable, więc użyj DrawableWrapper (z Androida.wsparcie.v7 dla 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;
    }
}

Wziąłem Pomysł Na DrawableWrapper stąd: https://stackoverflow.com/a/39108111/5541688

 0
Author: Anrimian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-08 19:00:21