Biểu tượng hoạt hình cho ActionItem


91

Tôi đã tìm kiếm khắp nơi để tìm ra giải pháp thích hợp cho vấn đề của mình và dường như tôi vẫn chưa thể tìm ra giải pháp nào. Tôi có một ActionBar (ActionBarSherlock) với một menu được thổi phồng từ tệp XML và menu đó chứa một mục và một mục được hiển thị dưới dạng ActionItem.

thực đơn:

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

Hoạt động:

[...]
  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getSupportMenuInflater().inflate(R.menu.mymenu, menu);
    return true;
  }
[...]

ActionItem được hiển thị với một biểu tượng và không có văn bản, tuy nhiên khi người dùng nhấp vào ActionItem, tôi muốn biểu tượng bắt đầu hoạt ảnh, cụ thể hơn là xoay tại chỗ. Biểu tượng được đề cập là biểu tượng làm mới.

Tôi nhận thấy rằng ActionBar có hỗ trợ sử dụng các chế độ xem tùy chỉnh ( Thêm một Chế độ xem Hành động ) tuy nhiên, chế độ xem tùy chỉnh này được mở rộng để bao phủ toàn bộ khu vực của ActionBar và thực sự chặn mọi thứ ngoại trừ biểu tượng ứng dụng, trong trường hợp của tôi không phải là thứ tôi đang tìm kiếm .

Vì vậy, nỗ lực tiếp theo của tôi là cố gắng sử dụng AnimationDrawable và xác định từng khung hình hoạt hình của tôi, đặt có thể vẽ làm biểu tượng cho mục menu, sau đó onOptionsItemSelected(MenuItem item)lấy biểu tượng và bắt đầu tạo hoạt ảnh bằng cách sử dụng ((AnimationDrawable)item.getIcon()).start(). Tuy nhiên, điều này đã không thành công. Có ai biết bất kỳ cách nào để thực hiện hiệu ứng này?

Câu trả lời:


173

Bạn đang đi đúng hướng. Đây là cách ứng dụng GitHub Gaug.es sẽ triển khai nó.

Đầu tiên họ xác định một XML hoạt ảnh:

<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" />

Bây giờ xác định một bố cục cho dạng xem hành động:

<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" />

Tất cả những gì chúng ta cần làm là bật chế độ xem này bất cứ khi nào mục được nhấp vào:

 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
 }

Khi quá trình tải hoàn tất, chỉ cần dừng hoạt ảnh và xóa chế độ xem:

public void completeRefresh() {
    refreshItem.getActionView().clearAnimation();
    refreshItem.setActionView(null);
}

Và bạn đã hoàn thành!

Một số việc bổ sung cần làm:

  • Lưu trữ lạm phát bố cục chế độ xem hành động và lạm phát hoạt ảnh. Chúng chậm nên bạn chỉ muốn làm chúng một lần.
  • Thêm nullkiểm tra trongcompleteRefresh()

Đây là yêu cầu kéo trên ứng dụng: https://github.com/github/gauges-android/pull/13/files


2
Câu trả lời tuyệt vời, tuy nhiên getActivity () không thể truy cập được nữa, hãy sử dụng getApplication () để thay thế.
theAlse

8
@Alborz Điều đó hoàn toàn dành riêng cho ứng dụng của bạn và không phải là một quy tắc chung. Tất cả phụ thuộc vào nơi bạn đang đặt phương thức làm mới.
Jake Wharton

1
Điều này có hoạt động với ActionBar bình thường (không có ActionBar Sherlock) không? Đối với tôi, biểu tượng nhảy sang trái khi hoạt ảnh bắt đầu và không thể nhấp được nữa sau đó. CHỈNH SỬA: Chỉ cần phát hiện ra rằng thiết lập ActionView gây ra điều này, không phải chính hoạt ảnh.
Tên hiển thị

2
Sẽ không có bước nhảy nếu hình ảnh của bạn là hình vuông và có kích thước chính xác cho một mục hành động.
Jake Wharton

13
Tôi cũng gặp sự cố với nút nhảy sang một bên khi thực hiện điều này. Điều này là do tôi không sử dụng kiểu Widget.Sherlock.ActionButton. Tôi đã sửa lỗi này bằng cách thêm android:paddingLeft="12dp"android:paddingRight="12dp"vào chủ đề của riêng tôi.
William Carter

16

Tôi đã làm việc một chút về giải pháp sử dụng ActionBarSherlock, tôi đã nghĩ ra điều này:

res / layout / indeterfinity_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 / indetermina_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 / rot_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>

Mã trong hoạt động (tôi có nó trong lớp cha 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);
    }
}

trong hoạt động tạo các món trong thực đơn

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

Và biểu tượng, đây là drawable-xhdpi/ic_menu_navigation_refresh.png
drawable-xhdpi / ic_menu_navigation_refresh.png

Điều này có thể được tìm thấy trong http://developer.android.com/design/downloads/index.html#action-bar-icon-pack


FYI Tôi cũng đã phải thêm layout-tvdpi-v11 / indetermina_progress_action.xml với android: layout_marginRight = "16dp", để hiển thị đúng. Tôi không biết liệu sự không nhất quán này có phải là lỗi trong mã, ABS hay SDK của tôi hay không.
Iraklis

Tôi đã thử nghiệm giải pháp này với một số ứng dụng của mình và tất cả chúng đều sử dụng cùng một mã. Vì vậy, tôi đoán đây là một số mâu thuẫn trong mã của bạn, như ABS (4.2.0) và SDK (API 14 và cao hơn) được chia sẻ ;-)
Marek Sebera

Bạn đã thử nó trên Nexus 7 chưa? (không phải emu, một thiết bị thực) Đây là thiết bị duy nhất không hiển thị đúng cách, do đó cài đặt tvdpi.
Iraklis

@Iraklis nope, tôi không có thiết bị như vậy. Vì vậy, có, bây giờ tôi thấy, những gì bạn đã gỡ lỗi. Tuyệt vời, hãy thêm nó để trả lời.
Marek Sebera

6

Ngoài những gì Jake Wharton đã nói, bạn nên làm những điều sau đây để đảm bảo rằng hoạt ảnh dừng mượt mà và không bị nhảy xung quanh ngay sau khi tải xong.

Đầu tiên, tạo một boolean mới (cho cả lớp):

private boolean isCurrentlyLoading;

Tìm phương pháp bắt đầu tải của bạn. Đặt boolean của bạn thành true khi hoạt động bắt đầu tải.

isCurrentlyLoading = true;

Tìm phương thức được bắt đầu khi quá trình tải của bạn kết thúc. Thay vì xóa hoạt ảnh, hãy đặt boolean của bạn thành false.

isCurrentlyLoading = false;

Đặt AnimationListener trên hoạt ảnh của bạn:

animationRotate.setAnimationListener(new AnimationListener() {

Sau đó, mỗi lần hoạt ảnh được thực hiện một lần, có nghĩa là khi biểu tượng của bạn thực hiện một lần xoay, hãy kiểm tra trạng thái tải và nếu không tải nữa, hoạt ảnh sẽ dừng.

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

Bằng cách này, hoạt ảnh chỉ có thể bị dừng nếu nó đã được xoay cho đến cuối và sẽ được lặp lại trong thời gian ngắn VÀ nó không tải nữa.

Đây ít nhất là những gì tôi đã làm khi muốn thực hiện ý tưởng của Jake.


2

Ngoài ra còn có một tùy chọn để tạo vòng quay trong mã. Đoạn trích đầy đủ:

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

Sử dụng điều này và nhấn onOptionsItemSelected không được gọi.
pseudozach

1

Với thư viện hỗ trợ, chúng tôi có thể tạo hoạt ảnh cho biểu tượng mà không cần actionView tùy chỉnh.

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

Chúng tôi không thể tạo hoạt ảnh trực tiếp có thể vẽ được, vì vậy hãy sử dụng DrawableWrapper (từ android.support.v7 cho 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;
    }
}

Tôi lấy ý tưởng cho DrawableWrapper từ đây: https://stackoverflow.com/a/39108111/5541688


0

Giải pháp rất đơn giản của tôi (ví dụ: cần một số cấu trúc lại) hoạt động với MenuItem độc lập, bạn có thể sử dụng nó với bất kỳ số trạng thái, biểu tượng, hoạt ảnh, logic nào, v.v.

trong lớp Hoạt động:

private enum RefreshMode {update, actual, outdated} 

người nghe standart:

public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.menu_refresh: {
            refreshData(null);
            break;
        }
    }
}

vào refreshData () làm một cái gì đó như sau:

setRefreshIcon(RefreshMode.update);
// update your data
setRefreshIcon(RefreshMode.actual);

phương pháp xác định màu sắc hoặc hoạt ảnh cho biểu tượng:

 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: {
        }
    }
}

có 2 bố cục có biểu tượng (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>

hoạt ảnh xoay standart trong trường hợp này (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

cách tốt nhất là ở đây:

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

và sau đó:

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

Hãy nhớ rằng bạn không thể truy cập "btsync = this.findViewById (R.id.action_sync);" tại onCreate () hoặc onStart () hoặc onResume () hoặc onPostResume () hoặc onPostCreate () hoặc onCreateOptionsMenu () hoặc onPrepareOptionsMenu () nếu bạn muốn lấy nó ngay sau khi hoạt động bắt đầu, hãy đưa nó vào trạng thái trả sau:

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);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.