ActionItem için Hareketli Simge


91

Sorunuma uygun bir çözüm bulmak için her yerde aradım ve henüz bir çözüm bulamıyorum. XML dosyasından şişirilmiş bir menüye sahip bir ActionBar'ım (ActionBarSherlock) var ve bu menü bir öğe içeriyor ve bu öğe bir ActionItem olarak gösteriliyor.

Menü:

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

aktivite:

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

ActionItem bir simgeyle ve metin olmadan görüntüleniyor, ancak bir kullanıcı ActionItem'i tıkladığında, simgenin animasyona başlamasını, daha spesifik olarak yerinde dönmesini istiyorum. Söz konusu simge, bir yenileme simgesidir.

ActionBar'ın özel görünümleri ( Eylem Görünümü Ekleme) kullanma desteğine sahip olduğunu fark ettim, ancak bu özel görünüm, ActionBar'ın tüm alanını kapsayacak şekilde genişletildi ve aslında uygulama simgesi dışındaki her şeyi engelliyor, bu benim durumumda aradığım şey değildi .

Bu yüzden bir sonraki girişimim AnimationDrawable'ı kullanmaya ve animasyonumu kare kare tanımlamaya, çizilebilir olanı menü öğesi için simge olarak ayarlamaya ve ardından onOptionsItemSelected(MenuItem item)simgeyi alıp animasyon kullanmaya başlamaya çalışmaktı ((AnimationDrawable)item.getIcon()).start(). Ancak bu başarısız oldu. Bu etkiyi gerçekleştirmenin herhangi bir yolunu bilen var mı?

Yanıtlar:


174

Doğru yoldasın. GitHub Gaug.es uygulamasının onu nasıl uygulayacağı aşağıda açıklanmıştır.

Önce bir animasyon XML'i tanımlarlar:

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

Şimdi eylem görünümü için bir düzen tanımlayın:

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

Tek yapmamız gereken, öğe her tıklandığında bu görünümü etkinleştirmektir:

 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
 }

Yükleme tamamlandığında, animasyonu durdurun ve görünümü temizleyin:

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

Ve bitirdiniz!

Yapılacak bazı ek şeyler:

  • Eylem görünümü düzeni şişirme ve animasyon şişirme işlemlerini önbelleğe alın. Yavaşlar, bu yüzden sadece bir kez yapmak istersiniz.
  • nullÇek eklecompleteRefresh()

Uygulamadaki çekme talebi: https://github.com/github/gauges-android/pull/13/files


1
Mükemmel! Android dokümanlar sayfasında kullanılan yöntemi takip ediyordum : Bir ActionView eklemek ve bu yöntemle AB'nin tamamı görünüm tarafından engelleniyor. Tekrar teşekkürler!
Alex Fu

2
Harika yanıt, ancak getActivity () artık erişilebilir değil, bunun yerine getApplication () kullanın.
TheAlse

8
@Alborz Bu, genel bir kural değil, tamamen uygulamanıza özeldir. Hepsi yenileme yöntemini nereye yerleştirdiğinize bağlıdır.
Jake Wharton

2
Resminiz kare ise ve bir eylem öğesi için doğru boyuttaysa atlama olmamalıdır.
Jake Wharton

14
Bunu uygularken düğmenin yana atlamasıyla da sorunlar yaşadım. Bunun nedeni, Widget.Sherlock.ActionButton stilini kullanmamamdı. Bunu kendi temama android:paddingLeft="12dp"ve ekleyerek düzelttim android:paddingRight="12dp".
William Carter

16

ActionBarSherlock kullanarak çözüm üzerinde biraz çalıştım, şunu buldum:

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>

Aktivitedeki kod (ActivityWithRefresh ebeveyn sınıfında var)

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

menü öğeleri oluşturma etkinliğinde

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

Ve simge, bu drawable-xhdpi/ic_menu_navigation_refresh.png
drawable-xhdpi / ic_menu_navigation_refresh.png

Bu, http://developer.android.com/design/downloads/index.html#action-bar-icon-pack içinde bulunabilir


Bilginize Ayrıca düzgün bir şekilde görüntülemek için layout-tvdpi-v11 / indeterminate_progress_action.xml dosyasını android: layout_marginRight = "16dp" ile eklemem gerekiyordu. Bu tutarsızlığın kodumda, ABS'de veya SDK'da bir hata olup olmadığını bilmiyorum.
Iraklis

Bu çözümü birkaç uygulamayla test ettim ve hepsi aynı kodu kullanıyor. Bu yüzden, ABS (4.2.0) ve SDK (API 14 ve üstü) paylaşıldığı için bu kodunuzda bir tutarsızlık olduğunu tahmin ediyorum ;-)
Marek Sebera

Bunu bir Nexus 7'de denediniz mi? (emu değil, gerçek bir cihaz) Düzgün görüntülenmeyen tek cihaz, dolayısıyla tvdpi ayarları.
Iraklis

@Iraklis hayır, bende böyle bir cihaz yok. Yani evet, şimdi ne hata ayıkladığını görüyorum. Harika, yanıtlamak için eklemekten çekinmeyin.
Marek Sebera

6

Jake Wharton'ın söylediklerine ek olarak, animasyonun sorunsuz bir şekilde durmasını ve yükleme biter bitmez atlamamasını sağlamak için muhtemelen aşağıdakileri yapmanız gerekir .

İlk olarak, yeni bir boole oluşturun (tüm sınıf için):

private boolean isCurrentlyLoading;

Yüklemenizi başlatan yöntemi bulun. Etkinlik yüklenmeye başladığında booleanınızı true olarak ayarlayın.

isCurrentlyLoading = true;

Yüklemeniz bittiğinde başlayan yöntemi bulun. Animasyonu temizlemek yerine, boolean değerini false olarak ayarlayın.

isCurrentlyLoading = false;

Animasyonunuzda bir Animasyon Dinleyici ayarlayın:

animationRotate.setAnimationListener(new AnimationListener() {

Daha sonra, animasyon her seferinde çalıştırıldığında, bu, simgeniz bir dönüş yaptığında, yükleme durumunu kontrol edin ve artık yüklenmiyorsa, animasyonun duracağı anlamına gelir.

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

Bu şekilde, animasyon yalnızca sonuna kadar döndürülmüşse durdurulabilir ve kısa bir süre sonra tekrarlanacak ve artık yüklenmiyor.

En azından Jake'in fikrini uygulamak istediğimde yaptığım buydu.


2

Kodda rotasyonu oluşturma seçeneği de vardır. Tam ekran alıntısı:

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

Bunu ve onOptionsItemSelected tapını kullanmak çağrılmaz.
pseudozach

1

Destek kitaplığı ile simgeyi özel actionView olmadan canlandırabiliriz.

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

DrawableWrapper'ı (API <21 için android.support.v7'den) doğrudan canlandıramayız:

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

Buradan DrawableWrapper için fikir edindim: https://stackoverflow.com/a/39108111/5541688


0

benim çok basit çözümüm (örneğin, biraz refactor gerekiyor) standart MenuItem ile çalışıyor, istediğiniz sayıda durum, simge, animasyon, mantık vb. ile kullanabilirsiniz.

Activity sınıfında:

private enum RefreshMode {update, actual, outdated} 

standart dinleyici:

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

yenileme Verisine () şöyle bir şey yapın:

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

simge için renk veya animasyon tanımlama yöntemi:

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

simgeli 2 düzen vardır (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>

bu durumda standart döndürme animasyonu (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

en iyi yol burası:

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

ve sonra:

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

Unutmayın ki "btsync = this.findViewById (R.id.action_sync);" onCreate () veya onStart () veya onResume () veya onPostResume () veya onPostCreate () veya onCreateOptionsMenu () veya onPrepareOptionsMenu () 'de, etkinlik başladıktan hemen sonra almak istiyorsanız, sonradan gecikmeli olarak koyun:

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);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.