Анимированная иконка для ActionItem
Я искал везде правильное решение моей проблемы, и я не могу, кажется, найти его еще. У меня есть 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, I хотите, чтобы значок начал анимацию, более конкретно, вращаясь на месте. Рассматриваемый значок является значком обновления.
Я понимаю, что ActionBar поддерживает использование пользовательских представлений ( добавление вида действия) однако этот пользовательский вид расширяется, чтобы охватить всю область панели действий и фактически блокирует все, кроме значка приложения, который в моем случае не то, что я искал.
так что мой следующий шаг состоял в том, чтобы попытаться использовать AnimationDrawable и определить свой анимация кадр за кадром, установите drawable в качестве значка для пункта меню, а затем в onOptionsItemSelected(MenuItem item)
получить значок и начать анимацию с помощью ((AnimationDrawable)item.getIcon()).start()
. Однако это была неудачной. Кто-нибудь знает, как достичь этого эффекта?
5 ответов:
вы на правильном пути. Вот как 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()
вот запрос pull на приложение:https://github.com/github/gauges-android/pull/13/files
Я немного поработал над решением с помощью 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 / 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>
код в activity (у меня он есть в 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
это можно найти в http://developer.android.com/design/downloads/index.html#action-bar-icon-pack
в дополнение к тому, что сказал Джейк Уортон, вы должны сделать следующее, чтобы убедиться, что анимация останавливается плавно и не прыгает вокруг как только загрузка завершена.
сначала создайте новое логическое значение (для всего класса):
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); } }
таким образом, анимация может быть остановлена только в том случае, если она уже повернута до конца и будет повторена в ближайшее время, и она не загружается больше.
это по крайней мере то, что я сделал, когда я хотел реализовать идею Джейка.
существует также возможность создать вращение в коде. Полный СНиП:
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);
с библиотекой поддержки мы можем анимировать значок без пользовательского 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.поддержка.v7 для 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; } }
Я взял идею для DrawableWrapper отсюда:https://stackoverflow.com/a/39108111/5541688