Wszędzie szukałem właściwego rozwiązania swojego problemu i nie mogę jeszcze go znaleźć. Mam ActionBar (ActionBarSherlock) z menu, które jest zawyżone z pliku XML i to menu zawiera jeden element, a ten 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>
czynność:
[...]
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getSupportMenuInflater().inflate(R.menu.mymenu, menu);
return true;
}
[...]
Element ActionItem jest wyświetlany z ikoną i bez tekstu, jednak gdy użytkownik kliknie element ActionItem, chcę, aby ikona zaczęła się animować, a dokładniej obracać w miejscu. Ikona, o której mowa, to ikona odświeżania.
Zdaję sobie sprawę, że ActionBar obsługuje korzystanie z niestandardowych widoków ( dodawanie widoku akcji ), jednak ten niestandardowy widok jest rozszerzany, aby objąć cały obszar ActionBar i faktycznie blokuje wszystko oprócz 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 rysowalnego jako ikony elementu menu, a następnie onOptionsItemSelected(MenuItem item)
pobranie ikony i rozpoczęcie animacji za pomocą ((AnimationDrawable)item.getIcon()).start()
. Nie udało się to jednak. Czy ktoś zna jakiś sposób na osiągnięcie tego efektu?
android:paddingLeft="12dp"
iandroid:paddingRight="12dp"
do mojego własnego motywu.Trochę pracowałem nad rozwiązaniem przy użyciu ActionBarSherlock, wymyśliłem 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 działaniu (mam go w klasie nadrzędnej 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 działalności tworzenia 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 to ikona
drawable-xhdpi/ic_menu_navigation_refresh.png
Można to znaleźć w http://developer.android.com/design/downloads/index.html#action-bar-icon-pack
źródło
Oprócz tego, co powiedział Jake Wharton, powinieneś prawdopodobnie wykonać następujące czynności, aby upewnić się, że animacja zatrzymuje się płynnie i nie przeskakuje po zakończeniu ładowania.
Najpierw utwórz nową wartość logiczną (dla całej klasy):
private boolean isCurrentlyLoading;
Znajdź metodę, która rozpoczyna ładowanie. Ustaw wartość logiczną na true, gdy aktywność zacznie się ładować.
isCurrentlyLoading = true;
Znajdź metodę, która jest uruchamiana po zakończeniu ładowania. Zamiast czyścić animację, ustaw wartość logiczną na fałsz.
isCurrentlyLoading = false;
Ustaw AnimationListener dla swojej animacji:
animationRotate.setAnimationListener(new AnimationListener() {
Następnie za każdym razem, gdy animacja była wykonywana jeden raz, czyli gdy ikona wykonała jeden obrót, sprawdź stan ładowania, a jeśli już się nie ładuje, animacja się zatrzyma.
@Override public void onAnimationRepeat(Animation animation) { if(!isCurrentlyLoading) { refreshItem.getActionView().clearAnimation(); refreshItem.setActionView(null); } }
W ten sposób animację można zatrzymać tylko wtedy, gdy jest już obrócona do końca i wkrótce zostanie powtórzona ORAZ już się nie ładuje.
Tak przynajmniej zrobiłem, gdy chciałem wdrożyć pomysł Jake'a.
źródło
Istnieje również opcja tworzenia rotacji w kodzie. Pełny wycinek:
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);
źródło
Dzięki bibliotece wsparcia możemy animować ikonę bez niestandardowego 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 android.support.v7 dla 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; } }
Pomysł na DrawableWrapper wziąłem stąd: https://stackoverflow.com/a/39108111/5541688
źródło
to moje bardzo proste rozwiązanie (na przykład potrzeba jakiegoś refaktora) działa ze standardowym MenuItem, możesz go używać z dowolną liczbą stanów, ikon, animacji, logiki itp.
w klasie Activity:
private enum RefreshMode {update, actual, outdated}
słuchacz standardowy:
public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_refresh: { refreshData(null); break; } } }
w refreshData () zrób coś takiego:
setRefreshIcon(RefreshMode.update); // update your data setRefreshIcon(RefreshMode.actual);
metoda definiowania koloru lub animacji ikony:
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: { } } }
są 2 układy z 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>
standardowa animacja obracania w tym przypadku (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" />
źródło
najlepszy sposób jest tutaj:
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);
i wtedy:
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)); } }
Pamiętaj, że nie możesz uzyskać dostępu "btsync = this.findViewById (R.id.action_sync);" at onCreate () lub onStart () lub onResume () lub onPostResume () lub onPostCreate () lub onCreateOptionsMenu () lub onPrepareOptionsMenu (), jeśli chcesz go pobrać zaraz po rozpoczęciu działania, umieść go w opóźnionym opóźnieniu:
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); }
źródło