Thursday, June 5, 2014

A compatible animated ActionBar refresh icon

Many apps put a refresh icon on the ActionBar, and it's nice to have that icon animate while a refresh is in progress. The internet is full of suggestions on how to do this, but most either require third party libraries (e.g. ActionBarSherlock) or are not compatible with older versions of the API. Here is some simple code that will rotate the refresh icon clockwise for API 4+ (disclaimer: I've only tested it down to API 10).

Note: The code assumes you already have ic_action_refresh in res/drawable. You can get the stock Android refresh icon from the Action Bar Icon Pack here.

res/anim/spin_clockwise.xml:
<?xml version="1.0" encoding="utf-8"?>

<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromDegrees="0"
    android:interpolator="@android:anim/linear_interpolator"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toDegrees="360" />

res/layout/refresh_action_view.xml:
<?xml version="1.0" encoding="utf-8"?>

<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:contentDescription="Refresh"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/ic_action_refresh"
    android:paddingLeft="12dp"
    android:paddingRight="12dp"
    />

AnimatingRefreshButtonManager.java:
import android.content.Context;
import android.support.v4.view.MenuItemCompat;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;

public class AnimatingRefreshButtonManager
{
    private final MenuItem mRefreshItem;
    private final Animation mRotationAnimation;

    private boolean mIsRefreshInProgress = false;

    public AnimatingRefreshButtonManager(Context context,
       MenuItem refreshItem)
    {
        // null checks omitted for brevity

        mRefreshItem = refreshItem;
        mRotationAnimation = AnimationUtils.loadAnimation(
            context, R.anim.spin_clockwise);

        mRotationAnimation.setAnimationListener(
            new Animation.AnimationListener()
            {
                @Override public void onAnimationStart(Animation animation) {}

                @Override public void onAnimationEnd(Animation animation) {}

                @Override public void onAnimationRepeat(Animation animation)
                {
                    // If a refresh is not in progress, stop the animation
                    // once it reaches the end of a full cycle
                    if (!mIsRefreshInProgress)
                        stopAnimation();
                }
            }
        );

        mRotationAnimation.setRepeatCount(Animation.INFINITE);
    }

    public void onRefreshBeginning()
    {
        if (mIsRefreshInProgress)
            return;
        mIsRefreshInProgress = true;

        stopAnimation();
        MenuItemCompat.setActionView(mRefreshItem,
            R.layout.refresh_action_view);
        View actionView = MenuItemCompat.getActionView(mRefreshItem);
        if (actionView != null)
            actionView.startAnimation(mRotationAnimation);
    }

    public void onRefreshComplete()
    {
        mIsRefreshInProgress = false;
    }

    private void stopAnimation()
    {
        View actionView = MenuItemCompat.getActionView(mRefreshItem);
        if (actionView == null)
            return;
        actionView.clearAnimation();
        MenuItemCompat.setActionView(mRefreshItem, null);
    }
}

Skeleton Activity demonstrating usage:
import android.app.Activity;
import android.view.Menu;
import android.view.MenuItem;

public class SomeActivity extends Activity
{
    private AnimatingRefreshButtonManager mRefreshButtonManager;
    
    @Override public boolean onCreateOptionsMenu(Menu menu)
    {
        getMenuInflater().inflate(R.menu.main, menu);

        MenuItem refreshItem = menu.findItem(R.id.action_refresh);

        mRefreshButtonManager =
           new AnimatingRefreshButtonManager(this, refreshItem);

        return super.onCreateOptionsMenu(menu);
    }
    
    @Override public boolean onOptionsItemSelected(MenuItem item)
    {
        switch (item.getItemId())
        {
            case R.id.action_refresh:
                mRefreshButtonManager.onRefreshBeginning();
                // start the refresh
                return true;
        }

        return super.onOptionsItemSelected(item);
    }
    
    // how you detect this is application-specific
    void onRefreshComplete()
    {
        mRefreshButtonManager.onRefreshComplete();
    }
}

No comments:

Post a Comment