Анімована піктограма для ActionItem


91

Я всюди шукав правильного вирішення своєї проблеми, і, здається, поки що не можу її знайти. У мене є 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 має підтримку використання власних подань ( Додавання подання дій ), однак цей спеціальний вигляд розширений, щоб охопити всю область ActionBar і фактично блокує все, крім піктограми програми, що в моєму випадку не те, що я шукав .

Отже, моєю наступною спробою було спробувати використати AnimationDrawable та визначити мою анімацію кадр за кадром, встановити малюнок як піктограму для пункту меню, а потім onOptionsItemSelected(MenuItem item)отримати файл і розпочати анімацію за допомогою ((AnimationDrawable)item.getIcon()).start(). Однак це не вдалося. Хтось знає про будь-який спосіб досягнення цього ефекту?

Відповіді:


173

Ви на правильному шляху. Ось як додаток 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


2
Чудова відповідь, однак getActivity () більше не доступний, використовуйте замість цього getApplication ().
theAlse

8
@Alborz Це цілком конкретно для вашого додатка, а не загальне правило. Все залежить від того, де ви розміщуєте метод оновлення.
Джейк Уортон,

1
Чи працює це також із звичайним ActionBar (без ActionBar Sherlock)? Для мене піктограма стрибає вліво, коли починається анімація, і після неї більше не можна натискати. РЕДАГУВАТИ: Щойно з’ясувалося, що встановлення ActionView спричинює це, а не саму анімацію.
Відображувати ім'я

2
Не повинно бути стрибків, якщо ваше зображення квадратне і має правильний розмір для дії.
Джейк Уортон,

13
У мене також були проблеми зі стрибком кнопки вбік при реалізації цього. Це тому, що я не використовував стиль Widget.Sherlock.ActionButton. Я виправив це, додавши android:paddingLeft="12dp"і android:paddingRight="12dp"до власної теми.
Вільям Картер

16

Я трохи попрацював над рішенням за допомогою 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 / rotacija_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
drawable-xhdpi / ic_menu_navigation_refresh.png

Це можна знайти за адресою http://developer.android.com/design/downloads/index.html#action-bar-icon-pack


FYI Мені довелося також додати layout-tvdpi-v11 / indeterminate_progress_action.xml з android: layout_marginRight = "16dp", щоб правильно відображати. Я не знаю, чи є ця невідповідність помилкою в моєму коді, ABS або SDK.
Іракліс,

Я протестував це рішення у кількох своїх додатках, і всі вони використовують один і той же код. Отже, я думаю, це є деякою невідповідністю у вашому коді, оскільки ABS (4.2.0) та SDK (API 14 і вище) є спільними ;-)
Marek Sebera

Ви пробували це в Nexus 7? (не ему, реальний пристрій) Єдиний пристрій, який не відображається належним чином, отже, налаштування tvdpi.
Іракліс,

@Iraklis ні, у мене немає такого пристрою. Так так, тепер я бачу, що ви налагодили. Чудово, сміливо додайте його для відповіді.
Марек Себера

6

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

Спочатку створіть нове логічне значення (для всього класу):

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

Таким чином, анімацію можна зупинити лише в тому випадку, якщо вона вже обернулась до кінця і буде повторена незабаром, і вона більше не завантажується.

Це принаймні те, що я зробив, коли хотів реалізувати ідею Джейка.


2

Також є можливість створити обертання в коді. Повний фрагмент:

    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 не викликається.
псевдозач

1

За допомогою бібліотеки підтримки ми можемо анімувати піктограму без спеціального 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 для API <21):

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


0

моє дуже просте рішення (наприклад, потрібен якийсь рефактор) працює зі стандартним MenuItem, ви можете використовувати його з будь-якою кількістю станів, піктограм, анімації, логіки тощо.

в класі активності:

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

0

найкращий спосіб тут:

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);" at onCreate () or onStart () or onResume () or onPostResume () or onPostCreate () or onCreateOptionsMenu () or 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);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.