Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add badge counter to hamburger navigation menu icon in Android

Tags:

My question is the same as this question (which is not a duplicate of this question).

The only answer to that question does not work for me as, rather than changing the default hamburger icon to the left of the activity's title, it just adds an additional hamburger icon to the right of my activity's title.

So how do I actually get this:

Android hamburger icon with badge counter

I've been poking around at it all day, but have got nowhere.

I see that Toolbar has a setNavigationIcon(Drawable drawable) method. Ideally, I would like to use a layout (that contains the hamburger icon and the badge view) instead of a Drawable, but I'm not sure if/how this is achievable - or if there is a better way?

NB - This isn't a question about how to create the badge view. I have already created that and have implemented it on the nav menu items themselves. So I am now just needing to add a similar badge view to the default hamburger icon.

like image 545
ban-geoengineering Avatar asked May 09 '17 23:05

ban-geoengineering


People also ask

What is the hamburger icon on Samsung Galaxy?

A navigation bar allows you to see more core features and functionality on the home screen. You can see multiple icons on the bar at a glance. Plus, these bars are usually at the bottom of the screen, which is much easier to reach than the top-right or top-left where hamburger icons typically live.

Where is the hamburger menu on Android?

Like iOS's legacy back buttons, these hamburger menu buttons appear in the top left corners of their apps, but considering you don't need to use them very often, I'm not so worried about how easy they are to reach.


1 Answers

Since version 24.2.0 of the support library, the v7 version of ActionBarDrawerToggle has offered the setDrawerArrowDrawable() method as a means to customize the toggle icon. DrawerArrowDrawable is the class that provides that default icon, and it can be subclassed to alter it as needed.

As an example, the BadgeDrawerArrowDrawable class overrides the draw() method to add a basic red and white badge after the superclass draws itself. This allows the hamburger-arrow animation to be preserved underneath.

import android.content.Context; import android.graphics.Color; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; import android.support.v7.graphics.drawable.DrawerArrowDrawable; import java.util.Objects;  public class BadgeDrawerArrowDrawable extends DrawerArrowDrawable {      // Fraction of the drawable's intrinsic size we want the badge to be.     private static final float SIZE_FACTOR = .3f;     private static final float HALF_SIZE_FACTOR = SIZE_FACTOR / 2;      private Paint backgroundPaint;     private Paint textPaint;     private String text;     private boolean enabled = true;      public BadgeDrawerArrowDrawable(Context context) {         super(context);          backgroundPaint = new Paint();         backgroundPaint.setColor(Color.RED);         backgroundPaint.setAntiAlias(true);          textPaint = new Paint();         textPaint.setColor(Color.WHITE);         textPaint.setAntiAlias(true);         textPaint.setTypeface(Typeface.DEFAULT_BOLD);         textPaint.setTextAlign(Paint.Align.CENTER);         textPaint.setTextSize(SIZE_FACTOR * getIntrinsicHeight());     }      @Override     public void draw(Canvas canvas) {         super.draw(canvas);          if (!enabled) {             return;         }          final Rect bounds = getBounds();         final float x = (1 - HALF_SIZE_FACTOR) * bounds.width();         final float y = HALF_SIZE_FACTOR * bounds.height();         canvas.drawCircle(x, y, SIZE_FACTOR * bounds.width(), backgroundPaint);          if (text == null || text.length() == 0) {             return;         }          final Rect textBounds = new Rect();         textPaint.getTextBounds(text, 0, text.length(), textBounds);         canvas.drawText(text, x, y + textBounds.height() / 2, textPaint);     }      public void setEnabled(boolean enabled) {         if (this.enabled != enabled) {             this.enabled = enabled;             invalidateSelf();         }     }      public boolean isEnabled() {         return enabled;     }      public void setText(String text) {         if (!Objects.equals(this.text, text)) {             this.text = text;             invalidateSelf();         }     }      public String getText() {         return text;     }      public void setBackgroundColor(int color) {         if (backgroundPaint.getColor() != color) {             backgroundPaint.setColor(color);             invalidateSelf();         }     }      public int getBackgroundColor() {         return backgroundPaint.getColor();     }      public void setTextColor(int color) {         if (textPaint.getColor() != color) {             textPaint.setColor(color);             invalidateSelf();         }     }      public int getTextColor() {         return textPaint.getColor();     } } 

An instance of this can be set on the toggle any time after it's instantiated, and the badge's properties set directly on the drawable as needed.

As the OP noted below, the Context used for the custom DrawerArrowDrawable should be obtained with ActionBar#getThemedContext() or Toolbar#getContext() to ensure the correct style values are used. For example:

private ActionBarDrawerToggle toggle; private BadgeDrawerArrowDrawable badgeDrawable; ...  toggle = new ActionBarDrawerToggle(this, ...); badgeDrawable = new BadgeDrawerArrowDrawable(getSupportActionBar().getThemedContext());  toggle.setDrawerArrowDrawable(badgeDrawable); badgeDrawable.setText("1"); ... 

screenshots


To simplify things a bit, it might be preferable to subclass ActionBarDrawerToggle as well, and handle everything through the toggle instance.

import android.app.Activity; import android.content.Context; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.widget.Toolbar; import java.lang.reflect.Field; import java.lang.reflect.Method;  public class BadgeDrawerToggle extends ActionBarDrawerToggle {      private BadgeDrawerArrowDrawable badgeDrawable;      public BadgeDrawerToggle(Activity activity, DrawerLayout drawerLayout,                              int openDrawerContentDescRes,                              int closeDrawerContentDescRes) {         super(activity, drawerLayout, openDrawerContentDescRes,               closeDrawerContentDescRes);         init(activity);     }      public BadgeDrawerToggle(Activity activity, DrawerLayout drawerLayout,                              Toolbar toolbar, int openDrawerContentDescRes,                              int closeDrawerContentDescRes) {         super(activity, drawerLayout, toolbar, openDrawerContentDescRes,               closeDrawerContentDescRes);         init(activity);     }      private void init(Activity activity) {         Context c = getThemedContext();         if (c == null) {             c = activity;         }         badgeDrawable = new BadgeDrawerArrowDrawable(c);         setDrawerArrowDrawable(badgeDrawable);     }      public void setBadgeEnabled(boolean enabled) {         badgeDrawable.setEnabled(enabled);     }      public boolean isBadgeEnabled() {         return badgeDrawable.isEnabled();     }      public void setBadgeText(String text) {         badgeDrawable.setText(text);     }      public String getBadgeText() {         return badgeDrawable.getText();     }      public void setBadgeColor(int color) {         badgeDrawable.setBackgroundColor(color);     }      public int getBadgeColor() {         return badgeDrawable.getBackgroundColor();     }      public void setBadgeTextColor(int color) {         badgeDrawable.setTextColor(color);     }      public int getBadgeTextColor() {         return badgeDrawable.getTextColor();     }      private Context getThemedContext() {         // Don't freak about the reflection. ActionBarDrawerToggle         // itself is already using reflection internally.         try {             Field mActivityImplField = ActionBarDrawerToggle.class                 .getDeclaredField("mActivityImpl");             mActivityImplField.setAccessible(true);             Object mActivityImpl = mActivityImplField.get(this);             Method getActionBarThemedContextMethod = mActivityImpl.getClass()                 .getDeclaredMethod("getActionBarThemedContext");             return (Context) getActionBarThemedContextMethod.invoke(mActivityImpl);         }         catch (Exception e) {             return null;         }     } } 

With this, the custom badge drawable will be set automatically, and everything toggle-related can be managed through a single object.

BadgeDrawerToggle is a drop-in replacement for ActionBarDrawerToggle, and its constructors are exactly the same.

private BadgeDrawerToggle badgeToggle; ...  badgeToggle = new BadgeDrawerToggle(this, ...); badgeToggle.setBadgeText("1"); ... 
like image 186
Mike M. Avatar answered Sep 20 '22 05:09

Mike M.