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 google design icons.
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>
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.
When you modify the code and run, this is what we see.
If the radius is width/4, the circle seems to be a bit small than what we are expecting.
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;
Badge location
The first one is at width,0 and second one is at width – radius, height – radius.
When you run, the circle looks as if it is pushed down the cart.
Badge location after adjustment.
float circleX = width - circleRadius + 6.2F;
Badge Text
If we place the text at circle’s x and y coordinates, we loose some of the text in the top.
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;
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.
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; }
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;
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.
- 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 objectif ((cartBadgeDrawable != null) && ((cartBadgeDrawable instanceof BadgeDrawable)) && (paramInt < 10)) { badgeDrawable = (BadgeDrawable) cartBadgeDrawable; } else { badgeDrawable = new BadgeDrawable(this); }
- Set the order item quantity to our custom badge drawable. We will see the class in our next section.
badgeDrawable.setCount();
- 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)
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.