Reputation: 5529
I have been searching everywhere for a proper solution to my problem and I can't seem to find one yet. I have an ActionBar (ActionBarSherlock) with a menu that is inflated from an XML file and that menu contains one item and that one item is shown as an 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>
activity:
[...]
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getSupportMenuInflater().inflate(R.menu.mymenu, menu);
return true;
}
[...]
The ActionItem is displayed with an icon and no text however when a user clicks on the ActionItem, I want the icon to begin animating, more specifically, rotating in place. The icon in question is a refresh icon.
I realize that ActionBar has support for using custom views (Adding an Action View) however this custom view is expanded to cover the entire area of the ActionBar and actually blocks everything except the app icon, which in my case is not what I was looking for.
So my next attempt was to try to use AnimationDrawable and define my animation frame-by-frame, set the drawable as the icon for the menu item, and then in onOptionsItemSelected(MenuItem item)
get the icon and begin animating using ((AnimationDrawable)item.getIcon()).start()
. This however was unsuccessful. Does anyone know of any way to accomplish this effect?
Upvotes: 92
Views: 68578
Reputation: 31
Following the steps in docs to create a Rotation animation: Use AnimatedVectorDrawable then make sure you set the animated-vector
drawable as the icon
<item
android:id="@+id/action_refresh"
android:title="@string/refresh"
android:icon="@drawable/animatorvectordrawable"
app:showAsAction="always"/>
Finally in the code to use the animation we need to cast to android.graphics.drawable.Animatable
(example using Kotlin):
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_refresh -> {
(item.icon as? Animatable)?.let { icon ->
if (icon.isRunning) {
icon.stop()
} else {
icon.start()
}
}
return true
} // other items
return super.onOptionsItemSelected(item)
}
Example of an infinite rotation from the center animation:
Upvotes: 0
Reputation: 49
the best way is here:
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);
and then:
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));
}
}
Remember that u cant access "btsync = this.findViewById(R.id.action_sync);" at onCreate() or onStart() or onResume() or onPostResume() or onPostCreate() or onCreateOptionsMenu() or onPrepareOptionsMenu() if u want get it just after activity start put it in a 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);
}
Upvotes: 0
Reputation: 177
its my very simple solution (for example, need some refactor) works with standart MenuItem, you can use it with any number of states, icons, animations, logic etc.
in Activity class:
private enum RefreshMode {update, actual, outdated}
standart listener:
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh: {
refreshData(null);
break;
}
}
}
into refreshData() do something like this:
setRefreshIcon(RefreshMode.update);
// update your data
setRefreshIcon(RefreshMode.actual);
method for define color or animation for icon:
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: {
}
}
}
there is 2 layouts with icon (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>
standart rotate animation in this case (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"
/>
Upvotes: 0
Reputation: 4477
With support library we can animate icon without 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();
}
We can't animate drawable directly, so use DrawableWrapper(from android.support.v7 for 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;
}
}
I took idea for DrawableWrapper from here: https://stackoverflow.com/a/39108111/5541688
Upvotes: 1
Reputation: 2595
There is also an option to create the rotation in code. Full snip:
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);
Upvotes: 1
Reputation: 487
In addition to what Jake Wharton said, you should propably do the following to ensure that the animation stops smoothly and does not jump around as soon as the loading finished.
First, create a new boolean (for the whole class):
private boolean isCurrentlyLoading;
Find the method that starts your loading. Set your boolean to true when the activity starts loading.
isCurrentlyLoading = true;
Find the method that is started when your loading is finished. Instead of clearing the animation, set your boolean to false.
isCurrentlyLoading = false;
Set an AnimationListener on your animation:
animationRotate.setAnimationListener(new AnimationListener() {
Then, each time the animation was executed one time, that means when your icon made one rotation, check the loading state, and if not loading anymore, the animation will stop.
@Override
public void onAnimationRepeat(Animation animation) {
if(!isCurrentlyLoading) {
refreshItem.getActionView().clearAnimation();
refreshItem.setActionView(null);
}
}
This way, the animation can only be stopped if it already rotated till the end and will be repeated shortly AND it is not loading anymore.
This is at least what I did when I wanted to implement Jake's idea.
Upvotes: 7
Reputation: 40651
I've worked a bit on solution using ActionBarSherlock, I've came up with this:
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>
Code in activity (I have it in ActivityWithRefresh parent class)
// 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);
}
}
in activity creating menu items
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();
}
And the icon, this is drawable-xhdpi/ic_menu_navigation_refresh.png
This could be found in http://developer.android.com/design/downloads/index.html#action-bar-icon-pack
Upvotes: 16
Reputation: 76085
You're on the right track. Here is how the GitHub Gaug.es app will be implementing it.
First they define an animation 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" />
Now define a layout for the action view:
<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" />
All we need to do is enable this view whenever the item is clicked:
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
}
When the loading is done, simply stop the animation and clear the view:
public void completeRefresh() {
refreshItem.getActionView().clearAnimation();
refreshItem.setActionView(null);
}
And you're done!
Some additional things to do:
null
checks in completeRefresh()
Here's the pull request on the app: https://github.com/github/gauges-android/pull/13/files
Upvotes: 176