ActionItem의 애니메이션 아이콘


91

나는 내 문제에 대한 적절한 해결책을 모든 곳에서 찾고 있었지만 아직 찾지 못하는 것 같습니다. 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(). 그러나 이것은 성공하지 못했습니다. 이 효과를 달성하는 방법을 아는 사람이 있습니까?

답변:


173

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


2
좋은 대답이지만 getActivity ()는 더 이상 액세스 할 수 없으므로 대신 getApplication ()을 사용하십시오.
theAlse

8
@Alborz 이것은 일반적인 규칙이 아니라 앱에만 국한된 것입니다. 모두 새로 고침 방법을 배치하는 위치에 따라 다릅니다.
Jake Wharton

1
이것은 일반 ActionBar에서도 작동합니까 (ActionBar Sherlock없이)? 나에게 아이콘은 애니메이션이 시작될 때 왼쪽으로 점프하고 나중에 더 이상 클릭 할 수 없습니다. 편집 : ActionView를 설정하면 애니메이션 자체가 아니라 이것이 발생한다는 것을 알았습니다.
표시 이름

2
이미지가 정사각형이고 작업 항목에 맞는 크기이면 점프가 없어야합니다.
Jake Wharton

13
또한 이것을 구현할 때 버튼이 옆으로 점프하는 데 문제가있었습니다. 이것은 Widget.Sherlock.ActionButton 스타일을 사용하지 않았기 때문입니다. 나는 추가하여 수정 android:paddingLeft="12dp"하고 android:paddingRight="12dp"내 자신의 주제에.
William Carter

16

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 에서 찾을 수 있습니다.


참고로 제대로 표시하려면 layout-tvdpi-v11 / indeterminate_progress_action.xml을 android : layout_marginRight = "16dp"와 함께 추가해야했습니다. 이 불일치가 내 코드, ABS 또는 SDK의 버그인지 모르겠습니다.
Iraklis 2013

내 응용 프로그램 중 일부로이 솔루션을 테스트했으며 모두 동일한 코드를 사용합니다. 따라서 ABS (4.2.0)와 SDK (API 14 이상)가 공유되기 때문에 이것이 코드에서 약간의 불일치라고 생각합니다 ;-)
Marek Sebera 2013

Nexus 7에서 사용해 보셨습니까? (에뮤, 실제 장치가 아님) 제대로 표시되지 않는 유일한 장치이므로 tvdpi 설정입니다.
Iraklis 2013

@Iraklis 아니, 그런 장치가 없습니다. 네, 이제 당신이 디버깅 한 것을 알 수 있습니다. 좋습니다. 자유롭게 추가하여 답변 해주세요.
Marek Sebera 2013

6

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의 아이디어를 구현하고 싶을 때 한 일입니다.


2

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

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

이것을 사용하면 onOptionsItemSelected 탭이 호출되지 않습니다.
pseudozach

1

지원 라이브러리를 사용하면 사용자 정의 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


0

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

0

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

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);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.