Tạo Icon ActionBar hiển thị số đếm

Trong thị trường hiện nay, nếu bạn muốn thành công thì không chỉ cần một app tuyệt vời mà cần có một UX tuyệt vời. Vì vậy, trong ứng dụng android, mỗi khi bạn có chức năng chọn mục và gọi button trên actionbar thì việc hiển thị số mục đã chọn trên button actionbar sẽ tạo một UX tuyệt vời. Ví dụ Để tạo icon với số đếm như trên ta sẽ thực hiện như sau:

  • Nếu ta chuẩn bị trước mỗi ảnh tướng ứng với một state thì thật tồi tệ vì nếu ta có n state thì ta cần n ảnh, vì vậy ta sẽ custom 1 icon có khả năng hiển thị count khác nhau.

Bước 1. Ta sẽ tạo drawable với layer-list

  • Đầu tiên ta cần hiểu layer-list là gì và nó hoạt động như thế nào. Về cơ bản, layer-list được dùng để kết hợp các item khác nhau trong cùng 1 drawable, mỗi item sẽ nằm ở layer trong layer-list. Các layer được liệt kê trước sẽ được vẽ trước khi drawable được load, vì vậy item dưới cùng trong layer-list sẽ được hiển thị trên cùng khi drawable được load.
  • Để tạo được group icon như trên ta sẽ chia thành hai layer. Ở layer bên dưới ta sẽ tạo group icon, còn layer trên cùng ta sẽ tạo count drawable để hiển thị count.
actionbar_group_icon.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:drawable="@drawable/ic_group"
        android:gravity="center" /> 
  
    <item
        android:id="@+id/ic_group_count"
        android:drawable="@color/transparent" />

</layer-list>

Bước 2. Thêm icon tạo ở trên vào actionbar

tạo file menu sau đó add drawable tạo ở trên vào item như sau

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto" >
    
    <!-- add the following item to your menu.xml -->
    
    <item
        android:id="@+id/ic_group"
        android:icon="@drawable/actionbar_group_icon.xml"
        android:title="@string/group_title"
        app:showAsAction="always" />
</menu>

Bước 3. Tạo custom component để vẽ count trên layer top Tạo file java CountDrawable.java extend Drawable class

public class CountDrawable extends Drawable {

    private Paint mBadgePaint;
    private Paint mTextPaint;
    private Rect mTxtRect = new Rect();

    private String mCount = "";
    private boolean mWillDraw;

    public CountDrawable(Context context) {
        float mTextSize = context.getResources().getDimension(R.dimen.badge_count_textsize);

        mBadgePaint = new Paint();
        mBadgePaint.setColor(ContextCompat.getColor(context.getApplicationContext(), R.color.background_color));
        mBadgePaint.setAntiAlias(true);
        mBadgePaint.setStyle(Paint.Style.FILL);

        mTextPaint = new Paint();
        mTextPaint.setColor(Color.WHITE);
        mTextPaint.setTypeface(Typeface.DEFAULT);
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextAlign(Paint.Align.CENTER);
    }

    @Override
    public void draw(Canvas canvas) {

        if (!mWillDraw) {
            return;
        }
        Rect bounds = getBounds();
        float width = bounds.right - bounds.left;
        float height = bounds.bottom - bounds.top;

        // Position the badge in the top-right quadrant of the icon.

	        /*Using Math.max rather than Math.min */

        float radius = ((Math.max(width, height) / 2)) / 2;
        float centerX = (width - radius - 1) +5;
        float centerY = radius -5;
        if(mCount.length() <= 2){
            // Draw badge circle.
            canvas.drawCircle(centerX, centerY, (int)(radius+5.5), mBadgePaint);
        }
        else{
            canvas.drawCircle(centerX, centerY, (int)(radius+6.5), mBadgePaint);
        }
        // Draw badge count text inside the circle.
        mTextPaint.getTextBounds(mCount, 0, mCount.length(), mTxtRect);
        float textHeight = mTxtRect.bottom - mTxtRect.top;
        float textY = centerY + (textHeight / 2f);
        if(mCount.length() > 2)
            canvas.drawText("99+", centerX, textY, mTextPaint);
        else
            canvas.drawText(mCount, centerX, textY, mTextPaint);
    }

    /*
    Sets the count (i.e notifications) to display.
     */
    public void setCount(String count) {
        mCount = count;

        // Only draw a badge if there are notifications.
        mWillDraw = !count.equalsIgnoreCase("0");
        invalidateSelf();
    }

    @Override
    public void setAlpha(int alpha) {
        // do nothing
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        // do nothing
    }

    @Override
    public int getOpacity() {
        return PixelFormat.UNKNOWN;
    }
}

Bước 4. Sử dụng để set count trên icon ta thực hiện như sau:

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
  setCount(this, "9");
}
    
public void setCount(Context context, String count) {
    MenuItem menuItem = defaultMenu.findItem(R.id.ic_group);
    LayerDrawable icon = (LayerDrawable) menuItem.getIcon();

    CountDrawable badge;

    // Reuse drawable if possible
    Drawable reuse = icon.findDrawableByLayerId(R.id.ic_group_count);
    if (reuse != null && reuse instanceof CountDrawable) {
        badge = (CountDrawable) reuse;
    } else {
        badge = new CountDrawable(context);
    }

    badge.setCount(count);
    icon.mutate();
    icon.setDrawableByLayerId(R.id.ic_group_count, badge);
}

src: thanks for this great article