In every application, we decorate our MenuItem placed on ActionBar. Displaying of badge count not only decorate the ActionBar beautifully but also displays useful information like showing the number of items currently available in your cart or update you with unread notification.
We can create badge count using either a custom view or by adding a distinct drawable to display the each state of MenuItem.
Both the options have its pros and cons.
There is another approach using LayerDrawable which is more flexible and efficient than the former approaches.
Here’s what we’re going to do:
- Create a LayerDrawable to display MenuItem with a badge.
- Create a custom drawable let us called BadgeDrawable.
- Set badge on the menu item from our Activity.
LayerDrawable
A Drawable that manages an array of other Drawables. These are drawn in array order, so the element with the largest index will be drawn on top.It can be defined in an XML file with the
It can be defined in an XML file with the <layer-list>
element. Each Drawable in the layer is defined in a nested<item>
i. Creating a layer drawable to display menu item on layer one and badge on top of it.
ic_menu_cart.xml
1 2 3 4 5 6 7 8 9 10 11 |
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/ic_cart" android:gravity="center" /> <!-- set a place holder Drawable so android:drawable isn't null --> <item android:id="@+id/ic_badge" android:drawable="@drawable/ic_cart" /> </layer-list> |
ii. Creating menu for our activity to display the count on the menu items.
main.xml
1 2 3 4 5 6 7 8 9 10 11 |
<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" tools:context="com.example.mobikul.MainActivity" > <item android:id="@+id/action_cart" android:icon="@drawable/ic_menu_cart" android:title="@string/action_cart" app:showAsAction="always" /> </menu> |
iii. Creating a flexible and efficient custom BagdeDrawable class to draw a view that looks like a count
BagdeDrawable.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
package com.webkul.mobikul.helper; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.support.v4.content.ContextCompat; import com.webkul.mobikul.R; public class BadgeDrawable extends Drawable { private Paint mBadgePaint; private Paint mBadgePaint1; private Paint mTextPaint; private Rect mTxtRect = new Rect(); private String mCount = ""; private boolean mWillDraw; public BadgeDrawable(Context context) { float mTextSize = context.getResources().getDimension(R.dimen.badge_text_size); mBadgePaint = new Paint(); mBadgePaint.setColor(Color.RED); mBadgePaint.setAntiAlias(true); mBadgePaint.setStyle(Paint.Style.FILL); mBadgePaint1 = new Paint(); mBadgePaint1.setColor(ContextCompat.getColor(context.getApplicationContext(), R.color.grey_ivory5)); mBadgePaint1.setAntiAlias(true); mBadgePaint1.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+7.5), mBadgePaint1); canvas.drawCircle(centerX, centerY, (int)(radius+5.5), mBadgePaint); } else{ canvas.drawCircle(centerX, centerY, (int)(radius+8.5), mBadgePaint1); canvas.drawCircle(centerX, centerY, (int)(radius+6.5), mBadgePaint); // canvas.drawRoundRect(radius, radius, radius, radius, 10, 10, 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; } } |
Setting up altogether
We can get the icon of the menu using getIcon() method.
1 2 3 |
MenuItem itemCart = menu.findItem(R.id.action_cart); LayerDrawable icon = (LayerDrawable) itemCart.getIcon(); setBadgeCount(this, icon, "9"); |
Here is our method setBadgeCount(). We can pass LayerDrawable which we get from meniItem and setBadge from anywhere from the application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public static void setBadgeCount(Context context, LayerDrawable icon, String count) { BadgeDrawable badge; // Reuse drawable if possible Drawable reuse = icon.findDrawableByLayerId(R.id.ic_badge); if (reuse != null && reuse instanceof BadgeDrawable) { badge = (BadgeDrawable) reuse; } else { badge = new BadgeDrawable(context); } badge.setCount(count); icon.mutate(); icon.setDrawableByLayerId(R.id.ic_badge, badge); } |