web-dev-qa-db-ja.com

ActionItemのアニメーションアイコン

私は自分の問題の適切な解決策をどこでも探していましたが、まだ見つけられないようです。 XMLファイルから拡張されたメニューを持つActionBar(ActionBarSherlock)があり、そのメニューには1つのアイテムが含まれ、その1つのアイテムは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;
  }
[...]
_

ActionItemにはアイコンが表示され、テキストは表示されませんが、ユーザーがActionItemをクリックすると、アイコンのアニメーション、具体的にはその場での回転が開始されます。問題のアイコンは更新アイコンです。

ActionBarはカスタムビューの使用をサポートしていることを認識しています( アクションビューの追加 )が、このカスタムビューはActionBarの領域全体をカバーするように拡張され、実際にはアプリアイコン以外のすべてをブロックします私が探していたものではありません。

だから私の次の試みは、AnimationDrawableを使用して、フレームごとにアニメーションを定義し、メニュー項目のアイコンとしてドロアブルを設定し、onOptionsItemSelected(MenuItem item)でアイコンを取得し、_((AnimationDrawable)item.getIcon()).start()_。しかし、これは失敗しました。誰もがこの効果を達成する方法を知っていますか?

91
Alex Fu

あなたは正しい軌道に乗っています。 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);
}
_

これで完了です!

追加の作業:

  • アクションビューのレイアウトインフレーションとアニメーションインフレーションをキャッシュします。それらは遅いので、一度だけやりたいだけです。
  • completeRefresh()nullチェックを追加します

アプリのプルリクエストは次のとおりです。 https://github.com/github/gauges-Android/pull/13/files

172
Jake Wharton

私は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>

アクティビティのコード(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 にあります。

16
Marek Sebera

Jake Whartonが言ったことに加えて、アニメーションがスムーズに停止し、ロードが終了したらすぐにジャンプしないになるように、以下を適切に行う必要があります。

最初に、新しいブール値を作成します(クラス全体に対して):

private boolean isCurrentlyLoading;

ロードを開始するメソッドを見つけます。アクティビティの読み込みが開始されたら、ブール値をtrueに設定します。

isCurrentlyLoading = true;

ロードが完了したときに開始されるメソッドを見つけます。アニメーションをクリアする代わりに、ブール値をfalseに設定します。

isCurrentlyLoading = false;

アニメーションにAnimationListenerを設定します。

animationRotate.setAnimationListener(new AnimationListener() {

その後、アニメーションが1回実行されるたびに、つまり、アイコンが1回転すると、ロード状態を確認し、ロードしなくなった場合はアニメーションが停止します。

@Override
public void onAnimationRepeat(Animation animation) {
    if(!isCurrentlyLoading) {
        refreshItem.getActionView().clearAnimation();
        refreshItem.setActionView(null);
    }
}

この方法では、アニメーションは最後まですでに回転していて、すぐに繰り返され、ロードされなくなった場合にのみ停止できます。

これは、少なくともJakeのアイデアを実装したいときにしたことです。

6
Lesik2008

サポートライブラリを使用すると、カスタム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を使用します(API <21のAndroid.support.v7から):

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

1
Anrimian

コードで回転を作成するオプションもあります。完全な切り取り:

    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);
1
Alen Siljak

私の非常にシンプルなソリューション(たとえば、リファクタリングが必要)は標準の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"
/>
0
Андрей К