[android] ActionItem의 애니메이션 아이콘

나는 내 문제에 대한 적절한 해결책을 모든 곳에서 찾고 있었지만 아직 찾지 못하는 것 같습니다. XML 파일에서 확장 된 메뉴가있는 ActionBar (ActionBarSherlock)가 있으며 해당 메뉴에는 하나의 항목이 포함되어 있으며 해당 항목은 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는 사용자 정의보기 ( Action View 추가) 사용을 지원한다는 것을 알고 있지만이 사용자 정의보기는 ActionBar의 전체 영역을 덮도록 확장되고 실제로 앱 아이콘을 제외한 모든 것을 차단합니다. .

그래서 다음 시도는 AnimationDrawable을 사용하고 내 애니메이션을 프레임별로 정의하고 드로어 블을 메뉴 항목의 아이콘으로 설정 한 다음 onOptionsItemSelected(MenuItem item)아이콘 을 가져 와서 ((AnimationDrawable)item.getIcon()).start(). 그러나 이것은 성공하지 못했습니다. 이 효과를 달성하는 방법을 아는 사람이 있습니까?



답변

당신은 올바른 길을 가고 있습니다. 다음은 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>

활동의 코드 (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 에서 찾을 수 있습니다.


답변

Jake Wharton이 말한 것 외에도 애니메이션이 부드럽게 멈추고 로딩이 끝나 자마자 점프하지 않도록 다음을 적절하게 수행해야합니다 .

먼저 새 부울을 만듭니다 (전체 클래스에 대해).

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);
    }
}

이렇게하면 애니메이션이 이미 끝까지 회전하고 곧 반복되고 더 이상로드되지 않는 경우에만 중지 할 수 있습니다.

이것은 적어도 Jake의 아이디어를 구현하고 싶을 때 한 일입니다.


답변

코드에서 회전을 만드는 옵션도 있습니다. 전체 캡처 :

    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();
}

드로어 블을 직접 애니메이션 할 수 없으므로 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


답변

내 매우 간단한 솔루션 (예 : 리팩터링이 필요함)은 standart 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"
/>


답변

가장 좋은 방법은 다음과 같습니다.

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);

그리고:

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));
    }
}

“btsync = this.findViewById (R.id.action_sync);”에 액세스 할 수 없음을 기억하십시오 . onCreate () 또는 onStart () 또는 onResume () 또는 onPostResume () 또는 onPostCreate () 또는 onCreateOptionsMenu () 또는 onPrepareOptionsMenu ()에서 활동 시작 직후에 가져 오려면 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);
}