Adding Notification badges to the icons in Android

We all have been the notification badges somewhere in every application, whether it is the Food ordering application or your favourite E-commerce application, notification badges are a sleek way to convey the user that something new needs your attention.

There are a lot of 3rd party libraries you can use to implement the notification badges in Android. But in this article, we will learn how to build this functionality ourself in Android.

It will be a pizza ordering application and whenever the user adds a pizza to the cart. The cart icon will be showing the count of the items that are added as the order.

If we look at this task as a whole it is two icons being displayed at the same place, imagine one icon of the cart and another one for the badge displaying the number of the items added or any other data that you want.

Initially the badge drawable will be blank or invisible that depends on what and how you want to show the badge. For this, we will use a layer-list.

Layer List Drawable

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/ic_cart_items"
android:drawable="@drawable/ic_cart"
android:gravity="center"
android:title="TODO" />
<item
android:id="@+id/ic_badge"
android:drawable="@android:color/transparent"
android:title="TODO" />
</layer-list>

Let’s see how we will draw our badge on the cart icon.

cart icon

If we draw a circle around the transparent drawable at x,y=(width,0) and radius width/2, the circle will look pretty big. And if the radius is width/3 or (width/4 + 2.5), the size of our circle looks better. Let’s add an increment of 2.5F to width/4.

circleRadius = Math.min(width, height)/4.0f+ 2.5F;

cart icon

Badge location

With some adjustment, I was able to get the perfect location.

float circleX = width - circleRadius + 6.2F;

Badge Text

float textY = circleY + (this.mTxtRect.bottom - this.mTxtRect.top) / 2.0F;

Also, for double-digit numbers in the circle we need to adjusts it.

if (Integer.parseInt(this.mCount) < 10) { circleRadius = Math.min(width, height)/4.0f+ 2.5F; } else { circleRadius = Math.min(width, height)/4.0f+ 4.5F;}

Add the Badge Icon Drawable to Menu

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<item
android:id="@+id/cart"
android:icon="@drawable/ic_menu_cart"
app:showAsAction="ifRoom"
android:title="ShoppingCart">
</item>

</menu>

We want to show the menu option on the AppBar in the activity where we can

In onPrepareOptionsMenu(), we will create the badge drawable and force it to redraw itself.
Our cart menu item’s icon is composed of the cart icon and the badge drawable. Badge drawable is transparent, once we know the count of order items, we will force it to re-draw itself.
In the draw() of the BadgeDrawable, we will paint the circle and the order quantity.

We will have a method name createCartBadge() in our main activity that will be responsible for displaying the badge icon and showing the count for the items that are being added into the cart.

Let us walk through the code that is shown below.

First, we need to find the menu item

MenuItem cartItem = this.mToolbarMenu.findItem(R.id.action_cart)

Next, we will get the icon’s drawable object.

LayerDrawable localLayerDrawable = (LayerDrawable) cartItem.getIcon();

We know the drawable object is composed of the cart icon and the transparent badge. Our goal is to repaint the badge drawable so find the badge drawable layer.

Drawable cartBadgeDrawable = localLayerDrawable.findDrawableByLayerId(R.id.ic_badge);

The first time the badge is drawn cartBadgeDrawable will be null, so we will instantiate the object, that is what we are doing in the if block.

Set the order item quantity to our custom badge drawable. We will see the class in the next section.

Call to BadgeDrawable.setCount() will force the drawable to redraw itself.

Replace the transparent drawable layer with the badge drawable we created.

localLayerDrawable.setDrawableByLayerId(R.id.ic_badge, badgeDrawable);

Reset the cart item’s icon.cartItem.setIcon(localLayerDrawable)

private void createCartBadge(int paramInt) {
if (Build.VERSION.SDK_INT <= 15) {
return;
}
MenuItem cartItem = this.mToolbarMenu.findItem(R.id.cart);
LayerDrawable localLayerDrawable = (LayerDrawable) cartItem.getIcon(); Drawable cartBadgeDrawable = localLayerDrawable
.findDrawableByLayerId(R.id.ic_badge);
BadgeDrawable badgeDrawable; if ((cartBadgeDrawable != null)
&& ((cartBadgeDrawable instanceof BadgeDrawable))
&& (paramInt < 10)) {
badgeDrawable = (BadgeDrawable) cartBadgeDrawable;
} else {
badgeDrawable = new BadgeDrawable(this);
}
badgeDrawable.setCount(paramInt);
localLayerDrawable.mutate();
localLayerDrawable.setDrawableByLayerId(R.id.ic_badge, badgeDrawable);
cartItem.setIcon(localLayerDrawable);
}

Next, now look at our custom badge class.

We need to set up our Paint object for Badge, Text inside the badge, Textsize and the Rect Object

private Paint mBadgePaint;
private Paint mTextPaint;
private Rect mTxtRect = new Rect();
private float mTextSize;
private String mCount = "";

And in the constructor of our custom BadgeDrawable class, we will initialise on the required properties for the view.

And then in our onDraw() method, we will put our drawing logic

public class BadgeDrawable extends Drawable {

private Rect mTxtRect = new Rect();
private boolean mWillDraw = false;

public BadgeDrawable(Context paramContext) {
this.mTextSize = paramContext.getResources().getDimension(
R.dimen.badge_text_size);
this.mBadgePaint = new Paint();
this.mBadgePaint.setColor(paramContext.getResources().getColor(
R.color.colorBlack));
this.mBadgePaint.setAntiAlias(true);
this.mBadgePaint.setStyle(Paint.Style.FILL);
this.mTextPaint = new Paint();
this.mTextPaint.setColor(Color.CYAN);
this.mTextPaint.setTypeface(Typeface.DEFAULT);
this.mTextPaint.setTextSize(this.mTextSize);
this.mTextPaint.setAntiAlias(true);
this.mTextPaint.setTextAlign(Paint.Align.CENTER);
}

public void draw(Canvas paramCanvas) {
if (!this.mWillDraw) {
return;
}
Rect localRect = getBounds();
float width = localRect.right - localRect.left;
float height = localRect.bottom - localRect.top;
float circleRadius;
circleRadius = Math.min(width, height)/4.0f+ 2.5F;
if (Integer.parseInt(this.mCount) < 10) {
circleRadius = Math.min(width, height)/4.0f+ 2.5F;
}
else {
circleRadius = Math.min(width, height)/4.0f+ 4.5F;
}
float circleX = width - circleRadius + 6.2F;
float circleY = circleRadius - 9.5f;
paramCanvas.drawCircle(circleX, circleY, circleRadius, this.mBadgePaint);
this.mTextPaint.getTextBounds(this.mCount, 0, this.mCount.length(), this.mTxtRect);
float textY = circleY + (this.mTxtRect.bottom - this.mTxtRect.top) / 2.0F;
float textX = circleX;

if (Integer.parseInt(this.mCount) >= 10) {
textX = textX - 1.0F;
textY = textY - 1.0F;
}

paramCanvas.drawText(this.mCount, textX, textY , this.mTextPaint);
}

public int getOpacity() {
return 0;
}

public void setAlpha(int paramInt) {
}

public void setColorFilter(ColorFilter paramColorFilter) {
}

public void setCount(int paramInt) {
this.mCount = Integer.toString(paramInt);
if (paramInt > 0) {
this.mWillDraw = true;
invalidateSelf();
}
}

}

The code repo is here

Let me know if anyone has any queries.

A Software Engineer and gamer at heart.