Анимированная иконка для 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, я хочу, чтобы значок начал анимацию, а точнее, вращался на месте. Значок, о котором идет речь, является значком обновления.
Я понимаю, что ActionBar поддерживает использование пользовательских представлений (Добавление вида действия), однако это настраиваемое представление расширяется для охвата всей области ActionBar и фактически блокирует все, кроме значка приложения, которое в моем случае не то, что я искал.
Итак, моя следующая попытка состояла в том, чтобы попытаться использовать AnimationDrawable и определить мою анимацию по кадру, установить drawable как значок для элемента меню, а затем в onOptionsItemSelected(MenuItem item)
получить значок и начать анимацию с помощью ((AnimationDrawable)item.getIcon()).start()
, Однако это не увенчалось успехом. Кто-нибудь знает какой-либо способ достичь этого эффекта?
Ответы
Ответ 1
Ты на правильном пути. Вот как это будет делать приложение 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
Я немного поработал над решением, используя ActionBarSherlock, я придумал это:
Рез/макет/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>
Рез/макет-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>
Рез/вытяжка/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>
Код в действии (у меня есть это в родительском классе 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
Ответ 3
В дополнение к тому, что сказал Джейк Уортон, вы должны сделать следующее, чтобы убедиться, что анимация перестает плавно, а не прыгает, как только закончится загрузка.
Сначала создайте новое логическое значение (для всего класса):
private boolean isCurrentlyLoading;
Найдите метод, который начнет загрузку. Установите значение boolean в true, когда действие начнет загружаться.
isCurrentlyLoading = true;
Найдите метод, который запускается после завершения загрузки. Вместо очистки анимации установите значение boolean равным false.
isCurrentlyLoading = false;
Установите анимационную анимацию в анимации:
animationRotate.setAnimationListener(new AnimationListener() {
Затем каждый раз, когда анимация выполнялась один раз, это означает, что когда ваш значок сделал один поворот, проверьте состояние загрузки, а если больше не загрузите, анимация остановится.
@Override
public void onAnimationRepeat(Animation animation) {
if(!isCurrentlyLoading) {
refreshItem.getActionView().clearAnimation();
refreshItem.setActionView(null);
}
}
Таким образом, анимация может быть остановлена только в том случае, если она уже повернута до конца и будет скоро повторена. И она больше не загружается.
Это, по крайней мере, то, что я сделал, когда хотел реализовать идею Джейка.
Ответ 4
Существует также возможность создать поворот в коде. Полный снимок:
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);
Ответ 5
В библиотеке поддержки мы можем анимировать значок без пользовательского 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 отсюда: fooobar.com/questions/64039/...
Ответ 6
Это мое очень простое решение (например, нужно немного рефакторинга) работает со стандартным 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"
/>