Android Icon Badge Example Using Layer-list Drawable

0

You want to add a badge to your cart icon to show the number of items.

In this article, we will see how to create a badge right above the cart icon using layer-list drawable.

Download shopping cart icon from https://www.google.com/design/icons/index.html

Layer List Drawable

We will use layer-list drawable to show the cart and the badge. A layer-list is a drawable object that manages an array of other drawables. Each drawable in the list is drawn in the order of the list. The cart icon will form the first layer and the badge will form the second. The second layer will be transparent initially which we will fill later with a circle and write some text, for example, a quantity showing the order items count. Since the last drawable in the list is drawn on top, we will have the badge as the last item.

badge_icon.xml:

<?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_shopping_cart_black_24dp"
        android:gravity="center"/>
   <item
        android:id="@+id/ic_badge"
        android:drawable="@android:color/transparent"/>
</layer-list>

Each drawable is represented by an element inside a single element. If you want to just view the layers, swap the items and add a drawable color instead of transparent.

badge_icon.xml:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item
        android:id="@+id/ic_badge"
        android:drawable="@android:color/darker_gray"/>
    <item
        android:id="@+id/ic_cart_items"
        android:drawable="@drawable/ic_shopping_cart_black_24dp"
        android:gravity="center"/>
</layer-list>
Icon and Badge Layers

Icon and Badge Layers

Badge Size

If we draw a circle around the transparent drawable at x,y=(width,0) and radius width/2, the circle will look pretty big.

cart and circle drawable

cart and circle drawable

When you modify the code and run, this is what we see.

radius=width/2, x,y=width,0

radius=width/2, x,y=width,0

If the radius is width/4, the circle seems to be a bit small than what we are expecting.

radius=width/4, x,y=width,0

radius=width/4, x,y=width,0

If the radius is width/3 or (width/4 + 2.5), 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;
radius=width/4+2.5

radius=width/4+2.5

Badge location

The first one is at width,0 and second one is at width – radius, height – radius.

Badge Location

Badge Location

When you run, the circle looks as if it is pushed down the cart.

Badge location = (width-radius, height-radius)

x=width-radius, y=radius

Badge location after adjustment.

float circleX = width - circleRadius + 6.2F;
x=width-radius+6.2f y=radius-7.0f

x=width-radius+6.2F, y=radius-7.0F

Badge Text

If we place the text at circle’s x and y coordinates, we loose some of the text in the top.

Badge Text

Badge Text

We will find the text’s rectangular boundary and then add to the y coordinate half of the text’s width.

float textY = circleY + (this.mTxtRect.bottom - this.mTxtRect.top) / 2.0F;	
x=circle's X, y=circle's Y + text's width/2

x=circle’s X, y=circle’s Y + text’s width/2

If quantity is 10, the circle looks a bit closed in so we will no have to adjust the circle’s size if the qty is >= 10.

Quantity is 10

Quantity is 10

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;
}
Adjust circle's size if qty >=10

Adjust circle’s size if qty >=10

Now we can’t see the bottom portion of the cart, let’s re-adjust the circle coordinates.

float circleX = width - circleRadius + 6.2F;
float circleY = circleRadius - 9.5f;
Cart Icon with Badge

Cart Icon with Badge

Add the Badge Icon Drawable to Menu

Bade Icon drawable badge_icon.xml contains the cart and the transparent badge.

main_menu.xml:

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

    <item
        android:id="@+id/action_cart"
        android:icon="@drawable/badge_icon"
        android:showAsAction="always"
        android:title="@string/title_cart"/>

</menu>

Our main page contains just a text item.

welcome.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:ulview="http://schemas.android.com/apk/res-auto"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@android:color/white"
    android:orientation="vertical">
    
    <TextView
        android:id="@+id/welcome"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="40dp"
        android:layout_marginTop="18dp"
        android:text="@string/welcome"
        android:textColor="@color/welcome_text_color"
        android:textSize="20sp" />


</LinearLayout>

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.

  1. First we need to find the menu item.
    MenuItem cartItem = this.mToolbarMenu.findItem(R.id.action_cart)
  2. Next, we will get the icon’s drawable object.
    LayerDrawable localLayerDrawable = (LayerDrawable) cartItem.getIcon();
  3. 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);
    
  4. The first time the badge is drawn, cartBadgeDrawable will be null, so we will instantiate the object
            if ((cartBadgeDrawable != null)
    				&& ((cartBadgeDrawable instanceof BadgeDrawable))
    				&& (paramInt < 10)) {
    	     badgeDrawable = (BadgeDrawable) cartBadgeDrawable;
             } else {
    	     badgeDrawable = new BadgeDrawable(this);
    	 }
    
  5. Set the order item quantity to our custom badge drawable. We will see the class in our next section.
    badgeDrawable.setCount();
  6. Call to BadgeDrawable.setCount() will force the drawable to redraw itself.
  7. Replace the transparent drawable layer with the badge drawable we created.
    localLayerDrawable.setDrawableByLayerId(R.id.ic_badge, badgeDrawable);
  8. Reset the cart item’s icon.
    cartItem.setIcon(localLayerDrawable)

MainActivity:

package com.javarticles.android;

import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

public class MainActivity extends Activity {
	private Menu mToolbarMenu;

	public void onCreate(final Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.welcome);
	}

	public boolean onCreateOptionsMenu(Menu paramMenu) {
		getMenuInflater().inflate(R.menu.main_menu, paramMenu);
		return super.onCreateOptionsMenu(paramMenu);
	}

	public boolean onOptionsItemSelected(MenuItem paramMenuItem) {
		switch (paramMenuItem.getItemId()) {
		case R.id.action_cart:
			Toast.makeText(this, "Show Cart", Toast.LENGTH_SHORT).show();
			return true;
		default:
			return super.onOptionsItemSelected(paramMenuItem);
		}
	}

	public boolean onPrepareOptionsMenu(Menu paramMenu) {
		mToolbarMenu = paramMenu;
		createCartBadge(10);
		return super.onPrepareOptionsMenu(paramMenu);
	}

	private void createCartBadge(int paramInt) {
		if (Build.VERSION.SDK_INT <= 15) {
			return;
		}
		MenuItem cartItem = this.mToolbarMenu.findItem(R.id.action_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);
	}
}

If the quantity is 0, you will not see the badge. BadgeDrawable is a custom Drawable, we create two Paint objects, one for filling the circle and the other to write the quantity.

BadgeDrawable:

package com.javarticles.android;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;

public class BadgeDrawable extends Drawable {

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

	public BadgeDrawable(Context paramContext) {
		this.mTextSize = paramContext.getResources().getDimension(
				R.dimen.textsize_14);
		this.mBadgePaint = new Paint();
		this.mBadgePaint.setColor(paramContext.getResources().getColor(
				R.color.welcome_text_color));
		this.mBadgePaint.setAntiAlias(true);
		this.mBadgePaint.setStyle(Paint.Style.FILL);
		this.mTextPaint = new Paint();
		this.mTextPaint.setColor(-1);
		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();
		}
	}

}

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.javarticles.android"
    android:versionCode="1"
    android:versionName="1.0">
	<uses-sdk
		android:minSdkVersion="14"
		android:maxSdkVersion="23"
		android:targetSdkVersion="23"/>
	<application
        android:icon="@drawable/ic_launcher_sandbox"
        android:label="@string/app_name"
        android:allowBackup="true"
        android:theme="@style/Theme.MyTheme">
        <activity android:name="com.javarticles.android.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Download the source code

This was an example about creating Android badge using layer-list drawable element.

You can download the source code here: badgeExample.zip
Share.

Comments are closed.