Ikon Animasi untuk ActionItem

91

Saya telah mencari di mana-mana untuk solusi yang tepat untuk masalah saya dan sepertinya saya belum dapat menemukannya. Saya memiliki ActionBar (ActionBarSherlock) dengan menu yang dinaikkan dari file XML dan menu itu berisi satu item dan satu item itu ditampilkan sebagai ActionItem.

Tidak bisa:

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

aktivitas:

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

ActionItem ditampilkan dengan ikon dan tidak ada teks, namun ketika pengguna mengklik ActionItem, saya ingin ikon mulai beranimasi, lebih khusus lagi, berputar di tempatnya. Ikon yang dimaksud adalah ikon segarkan.

Saya menyadari bahwa ActionBar memiliki dukungan untuk menggunakan tampilan kustom ( Menambahkan Tampilan Tindakan ) namun tampilan kustom ini diperluas untuk mencakup seluruh area ActionBar dan sebenarnya memblokir semuanya kecuali ikon aplikasi, yang dalam kasus saya bukan yang saya cari. .

Jadi upaya saya selanjutnya adalah mencoba menggunakan AnimationDrawable dan menentukan animasi frame-by-frame saya, mengatur drawable sebagai ikon untuk item menu, dan kemudian onOptionsItemSelected(MenuItem item)mendapatkan ikon dan mulai menganimasikan menggunakan ((AnimationDrawable)item.getIcon()).start(). Namun ini tidak berhasil. Adakah yang tahu cara untuk mencapai efek ini?

Alex Fu
sumber

Jawaban:

174

Anda berada di jalur yang benar. Berikut adalah bagaimana aplikasi GitHub Gaug.es akan mengimplementasikannya.

Pertama, mereka mendefinisikan XML animasi:

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

Sekarang tentukan tata letak untuk tampilan tindakan:

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

Yang perlu kita lakukan adalah mengaktifkan tampilan ini setiap kali item tersebut diklik:

 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
 }

Saat pemuatan selesai, cukup hentikan animasi dan hapus tampilan:

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

Dan Anda Selesai!

Beberapa hal tambahan yang harus dilakukan:

  • Cache inflasi tata letak tampilan tindakan dan inflasi animasi. Mereka lambat jadi Anda hanya ingin melakukannya sekali.
  • Tambahkan nullcheck incompleteRefresh()

Berikut permintaan tarik di aplikasi: https://github.com/github/gauges-android/pull/13/files

Jake Wharton
sumber
1
Sempurna! Saya telah mengikuti metode yang digunakan pada halaman dokumen Android: Menambahkan ActionView dan dengan metode itu seluruh AB diblokir oleh tampilan. Terima kasih lagi!
Alex Fu
2
Jawaban yang bagus, namun getActivity () tidak dapat diakses lagi, gunakan getApplication () sebagai gantinya.
theAlse
8
@Alborz Itu sepenuhnya khusus untuk aplikasi Anda dan bukan aturan umum. Itu semua tergantung di mana Anda menempatkan metode penyegaran.
Jake Wharton
2
Tidak boleh ada lompatan jika gambar Anda persegi dan ukurannya benar untuk item tindakan.
Jake Wharton
14
Saya juga mengalami masalah dengan tombol yang melompat ke samping saat menerapkan ini. Ini karena saya tidak menggunakan gaya Widget.Sherlock.ActionButton. Saya mengoreksi ini dengan menambahkan android:paddingLeft="12dp"dan android:paddingRight="12dp"ke tema saya sendiri.
William Carter
16

Saya telah mengerjakan sedikit solusi menggunakan ActionBarSherlock, saya telah menemukan ini:

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>

Kode dalam aktivitas (saya memilikinya di kelas induk 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);
    }
}

dalam aktivitas membuat item 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();
}

Dan ikonnya, ini drawable-xhdpi/ic_menu_navigation_refresh.png
drawable-xhdpi / ic_menu_navigation_refresh.png

Ini dapat ditemukan di http://developer.android.com/design/downloads/index.html#action-bar-icon-pack

Marek Sebera
sumber
FYI saya juga harus menambahkan layout-tvdpi-v11 / indeterminate_progress_action.xml dengan android: layout_marginRight = "16dp", agar dapat ditampilkan dengan benar. Saya tidak tahu apakah ketidakkonsistenan ini merupakan bug dalam kode saya, ABS, atau SDK.
Iraklis
Saya telah menguji solusi ini dengan beberapa aplikasi saya, dan semuanya menggunakan kode yang sama. Jadi saya kira ini adalah beberapa ketidakkonsistenan dalam kode Anda, karena ABS (4.2.0) dan SDK (API 14 dan lebih tinggi) digunakan bersama ;-)
Marek Sebera
Sudahkah Anda mencobanya di Nexus 7? (bukan emu, perangkat sungguhan) Ini satu-satunya perangkat yang tidak ditampilkan dengan benar, oleh karena itu pengaturan tvdpi.
Iraklis
@Iraklis tidak, saya tidak punya perangkat seperti itu. Jadi ya, sekarang saya mengerti, apa yang telah Anda debug. Bagus, jangan ragu untuk menambahkannya ke jawaban.
Marek Sebera
6

Selain apa yang dikatakan Jake Wharton, sebaiknya lakukan hal berikut untuk memastikan animasi berhenti dengan lancar dan tidak melompat-lompat segera setelah pemuatan selesai.

Pertama, buat boolean baru (untuk seluruh kelas):

private boolean isCurrentlyLoading;

Temukan metode yang memulai pemuatan Anda. Setel boolean Anda ke true saat aktivitas mulai memuat.

isCurrentlyLoading = true;

Temukan metode yang dimulai saat pemuatan Anda selesai. Alih-alih menghapus animasi, setel boolean Anda ke false.

isCurrentlyLoading = false;

Setel AnimationListener pada animasi Anda:

animationRotate.setAnimationListener(new AnimationListener() {

Kemudian, setiap animasi dieksekusi satu kali, artinya ketika ikon Anda berputar satu kali, periksa status loading, dan jika tidak dimuat lagi, animasi akan berhenti.

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

Dengan cara ini, animasi hanya dapat dihentikan jika sudah diputar hingga akhir dan akan diulangi segera DAN tidak dimuat lagi.

Setidaknya inilah yang saya lakukan ketika saya ingin menerapkan ide Jake.

Lesik2008
sumber
2

Ada juga opsi untuk membuat rotasi dalam kode. Snip penuh:

    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);
Alen Siljak
sumber
Menggunakan ini dan tap onOptionsItemSelected tidak dipanggil.
pseudozach
1

Dengan pustaka dukungan kita bisa menganimasikan ikon tanpa 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();
}

Kita tidak bisa menganimasikan drawable secara langsung, jadi gunakan DrawableWrapper (dari android.support.v7 untuk 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;
    }
}

Saya mengambil ide untuk DrawableWrapper dari sini: https://stackoverflow.com/a/39108111/5541688

Anrimian
sumber
0

ini solusi saya yang sangat sederhana (misalnya, perlu beberapa refactor) bekerja dengan MenuItem standar, Anda dapat menggunakannya dengan sejumlah status, ikon, animasi, logika, dll.

di kelas Aktivitas:

private enum RefreshMode {update, actual, outdated} 

pendengar standar:

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

ke refreshData () lakukan sesuatu seperti ini:

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

metode untuk menentukan warna atau animasi untuk ikon:

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

ada 2 tata letak dengan ikon (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>

animasi putar standar dalam hal ini (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"
/>
Андрей К
sumber
0

cara terbaik ada di sini:

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

lalu:

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

Ingatlah bahwa Anda tidak dapat mengakses "btsync = this.findViewById (R.id.action_sync);" di onCreate () atau onStart () atau onResume () atau onPostResume () atau onPostCreate () atau onCreateOptionsMenu () atau onPrepareOptionsMenu () jika Anda ingin mendapatkannya tepat setelah aktivitas mulai, letakkan di postdelayed:

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);
}
M Kasesang
sumber