Badge notification over drawer toggle in toolbar - Android
Asked Answered
L

5

24

I want to notify user about new unread message in the app which is accessible through navigation drawer. I was thinking about notification badge something similar Apple have but over drawer toggle in toolbar.

This is what I have now: This is what I have now

This is what I want: This is what I want

How can I achieve that?

Lynettelynn answered 18/10, 2015 at 17:6 Comment(10)
What have you already tried?Synthetic
@Synthetic I was thinking of replacing the drawer icon with my own view but I'm not too happy about that. Is there any other option?Lynettelynn
Not that I'm aware of. You could maybe use the stock drawable as a base image in a solution based on e.g. #29558803Synthetic
Can u post the code of the Toolbar and how do you specify those icons ?Mimas
@VasilyKabunov Android - How to add badge counter to hamburger navigation menu icon?Walling
@MikeM. Nice solution. I'd even pull this out into an open-source library :)Surprising
Thanks @MikeM., I couldn't google this answer, seems it might be the solution for my issue. Let me some time to checkMarko
did you follow this #43881631Tumble
Hi @MikeM., could you rewrite your comment as an answer with some details, I'll accept itMarko
@Vasily Hmm, well, it would basically be a repeat of my other answer, or mainly just a link to it, both of which are frowned upon. If this question were posted today, I'd likely just close it as a duplicate, and possibly still will after the bounty ends (can't do it while a bounty is attached). Anyhoo, I'm not hurting for rep, and the bounty has worked as intended, inasmuch as the extra attention got an answer. If you like, you can award it to someone else, if you think that appropriate, or you can let it expire, as it's non-refundable anyway. Thank you, though. I appreciate the offer. Cheers!Walling
M
19

enter image description here

enter image description here

I found this really cool BadgeDrawable class on the internet and you can add badge count to any drawable using this class. Please follow below steps.

Step 1: add below class to your project first.

import android.content.Context;
import android.content.res.Resources;
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.util.TypedValue;


/**
 * Created by Admin on 2/25/2016.
 */
public class BadgeDrawable extends Drawable {

    private float mTextSize;
    private Paint mBadgePaint;
    private Paint mBadgePaint1;
    private Paint mTextPaint;
    private Rect mTxtRect = new Rect();

    private String mCount = "";
    private boolean mWillDraw = false;


    public BadgeDrawable(Context context) {
        mTextSize = dpToPx(context, 8); //text size
        mBadgePaint = new Paint();
        mBadgePaint.setColor(Color.RED);
        mBadgePaint.setAntiAlias(true);
        mBadgePaint.setStyle(Paint.Style.FILL);
        mBadgePaint1 = new Paint();
        mBadgePaint1.setColor(Color.parseColor("#EEEEEE"));
        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);
    }

    private float dpToPx(Context context, float value) {
        Resources r = context.getResources();
        float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, r.getDisplayMetrics());
        return px;
    }


    @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 radius = width * 0.15f;
        float centerX = (width - radius - 1) +10;
        float centerY = radius -5;
        if(mCount.length() <= 2){
            // Draw badge circle.
            canvas.drawCircle(centerX, centerY, radius+9, mBadgePaint1);
            canvas.drawCircle(centerX, centerY, radius+7, mBadgePaint);
        }
        else{
            canvas.drawCircle(centerX, centerY, radius+10, mBadgePaint1);
            canvas.drawCircle(centerX, centerY, radius+8, 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;
    }
}

Step 2: Now create a drawable (in my case it is ic_badge_drawable.xml). Then copy and paste below xml text.

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

    <item android:id="@+id/ic_main_icon"
        android:drawable="@drawable/ic_burger"
        android:gravity="center" />


    <!-- set a place holder Drawable so android:drawable isn't null -->
    <item android:id="@+id/ic_badge"
        android:drawable="@drawable/ic_burger" />
</layer-list>

here you can pass any drawable for now, later we can pass any drawable to those. These are just like place holders.

Step 3: We have already setup everything. Now you can use below method to set badge count for any drawable.

 private Drawable setBadgeCount(Context context, int res, int badgeCount){
        LayerDrawable icon = (LayerDrawable) ContextCompat.getDrawable(context, R.drawable.ic_badge_drawable);
        Drawable mainIcon = ContextCompat.getDrawable(context, res);
        BadgeDrawable badge = new BadgeDrawable(context);
        badge.setCount(String.valueOf(badgeCount));
        icon.mutate();
        icon.setDrawableByLayerId(R.id.ic_badge, badge);
        icon.setDrawableByLayerId(R.id.ic_main_icon, mainIcon);

        return icon;
    }

Step 4: I used it as below to change my default burger icon.

setSupportActionBar(toolbar);
getSupportActionBar().setHomeAsUpIndicator(setBadgeCount(this,R.drawable.ic_burger, 3));
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowCustomEnabled(true);   // enable overriding the default toolbar layout
getSupportActionBar().setDisplayShowTitleEnabled(false);// disable the default title element here (for centered title)
Multilateral answered 19/6, 2017 at 5:31 Comment(0)
M
15

enter image description here

I have used simple TextView inside android.support.design.widget.AppBarLayout please check below complete code

MainActivity.java

import android.os.Bundle;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

     private DrawerLayout mDrawerLayout;

     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         initViews();
     }

     private void initViews() {
         mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
         setUpToolbar();
     }

     private void setUpToolbar() {
         Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
         setSupportActionBar(toolbar);
         final ActionBar ab = getSupportActionBar();
         ab.setHomeAsUpIndicator(R.drawable.navigation_drawericon);
         ab.setDisplayHomeAsUpEnabled(true);
     }

     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         getMenuInflater().inflate(R.menu.home_menu, menu);
         return true;
     }

     @Override
     public boolean onOptionsItemSelected(android.view.MenuItem item) {
         switch (item.getItemId()) {
             case android.R.id.home:
                 mDrawerLayout.openDrawer(GravityCompat.START);
                 return true;
         }
         return super.onOptionsItemSelected(item);
     }

     private void updateCounter(int count) {
         ((TextView) findViewById(R.id.tv_nav_drawer_count)).setText(count + "");
     }

     public void closeDrawer() {
         mDrawerLayout.closeDrawers();
     }
  }

activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.v4.widget.DrawerLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:fitsSystemWindows="true">

        <include layout="@layout/container_layout"/>

        <android.support.design.widget.NavigationView
            android:id="@+id/navigation_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            android:fitsSystemWindows="true">

        </android.support.design.widget.NavigationView>


    </android.support.v4.widget.DrawerLayout>

container_layout.xml

<android.support.design.widget.CoordinatorLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@+id/main_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <android.support.design.widget.AppBarLayout
                android:id="@+id/appbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    android:background="?attr/colorPrimary"
                    app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>


                <TextView
                    android:id="@+id/tv_nav_drawer_count"
                    android:layout_width="15dp"
                    android:layout_height="15dp"
                    android:layout_marginLeft="30dp"
                    android:layout_marginTop="-45dp"
                    android:background="@drawable/menu_text_bg"
                    android:gravity="center"
                    android:text="10"
                    android:textColor="@android:color/white"
                    android:textSize="8dp"/>


            </android.support.design.widget.AppBarLayout>

            <FrameLayout
                android:id="@+id/home_frame_container"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginTop="?actionBarSize"/>


        </android.support.design.widget.CoordinatorLayout>

home_menu.xml

     <?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/menu_search"
                android:icon="@android:drawable/ic_menu_search"
                android:orderInCategory="101"
                android:title="Search"
                app:showAsAction="always"/>
        </menu>

menu_text_bg.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportHeight="24.0"
        android:viewportWidth="24.0">
    <path
        android:fillColor="@android:color/holo_red_dark"
        android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"/>
</vector>
Morton answered 16/6, 2017 at 12:47 Comment(4)
Thanks for the answer. Your solution works nice for one screen (honestly it was my first thought to go this way, and I even wrote the code similar to yours with the textview inside toolbar. But when I realized that I will need to rewrite the layout on each screen of the app, I started to look for more universal solution.Marko
If your TextView slides underneath the toolbar like it happened for me, set an android:elevation value higher than the Toolbar's value to fix the drawing order.Petaliferous
Thanks, it worked but I had to use a shape instead of a vector in the menu_text_bg.xml file <shape xmlns:android="schemas.android.com/apk/res/android" android:shape="oval"> <solid android:color="@color/red"/> <size android:width="24dp" android:height="24dp" android:viewportHeight="24.0" android:viewportWidth="24.0"/> </shape>Sitton
Very simple and efficient techniqueMudslinging
D
4

I know its late but still. if you are using android Side Navigation Menu as a starter then there would be a file name app_bar_main.xml in this file you'll see some code like below except for the TextView this TextView is responsible for showing badge just initialize it in MainActivity where you are initializing your Toolbar and change its visibility according to your need (visible when count is positive else gone) right now as you can see its visibility is gone

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.thumbsol.beakns.activities.MainActivity">

<android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:theme="@style/AppTheme.AppBarOverlay">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:popupTheme="@style/AppTheme.PopupOverlay"></android.support.v7.widget.Toolbar>

    <TextView
        android:id="@+id/hamburger_count"
        android:layout_width="25dp"
        android:layout_height="25dp"
        android:layout_marginLeft="30dp"
        android:layout_marginTop="-45dp"
        android:background="@drawable/red_circle_bacground"
        android:gravity="center"
        android:text="10"
        android:textColor="@android:color/white"
        android:visibility="gone" />

    </android.support.design.widget.AppBarLayout>

  <include layout="@layout/content_main" />

and here is the code of red_circle_bacground.xml put it in drawable

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#f00" />
<corners
    android:bottomLeftRadius="30dp"
    android:bottomRightRadius="30dp"
    android:topLeftRadius="30dp"
    android:topRightRadius="30dp" />

  <size
    android:height="25dp"
    android:width="25dp"/>
</shape>
Diuresis answered 9/8, 2017 at 9:37 Comment(1)
So easy and great solution!! ThanksEntry
Y
2

Based on the answer from TDSoft, you can shorten the procedure even further by extending DrawerArrowDrawable.

Step 1: (Except for DrawerArrowDrable, basically Same as TDSoft's Badge class).

public class BadgeNavigationDrawable extends DrawerArrowDrawable{

private float mTextSize;
private Paint mBadgePaint;
private Paint mBadgePaint1;
private Paint mTextPaint;
private Rect mTxtRect = new Rect();

private String mCount = "";
private boolean mWillDraw = false;



/**
 * @param context used to get the configuration for the drawable from
 */
public BadgeNavigationDrawable(Context context) {
    super(context);

    setColor(context.getResources().getColor(R.color.accent));

    mTextSize = dpToPx(context, 8); //text size
    mBadgePaint = new Paint();
    mBadgePaint.setColor(Color.RED);
    mBadgePaint.setAntiAlias(true);
    mBadgePaint.setStyle(Paint.Style.FILL);
    mBadgePaint1 = new Paint();
    mBadgePaint1.setColor(Color.parseColor("#EEEEEE"));
    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) {
    super.draw(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 radius = width * 0.15f;
    float centerX = (width - radius - 1) +10;
    float centerY = radius -5;
    if(mCount.length() <= 2){
        // Draw badge circle.
        canvas.drawCircle(centerX, centerY, radius+9, mBadgePaint1);
        canvas.drawCircle(centerX, centerY, radius+7, mBadgePaint);
    }
    else{
        canvas.drawCircle(centerX, centerY, radius+10, mBadgePaint1);
        canvas.drawCircle(centerX, centerY, radius+8, 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;
}
private float dpToPx(Context context, float value) {
    Resources r = context.getResources();
    float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, r.getDisplayMetrics());
    return px;
}

}

Step 2: That is all you need, if you need to update your navigation drawer icon, call - Your events are all all preserved.

BadgeNavigationDrawable drawerIcon = new BadgeNavigationDrawable(MainActivity.this);
drawerIcon.setCount(String.valueOf(count));

mDrawerToggle.setDrawerArrowDrawable(drawerIcon);
                    mDrawerToggle.syncState();
Yodel answered 18/4, 2018 at 14:4 Comment(0)
C
-1

I achive this by adding a TextView to my main layout with targeting the toggle. in my mainactivity.xml I added this

 <RelativeLayout 
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/badge_ham"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="26dp"
        android:layout_marginTop="14dp"
        android:textSize="9sp" />
</RelativeLayout>

and for adding badge:

private  void addBadgeToHamburger(int badge){
    badge_ham.setVisibility(View.VISIBLE);
    String counter = Integer.toString(badge);
    String s =  "   " + counter + " ";
    SpannableString sColored = new SpannableString(s);

    sColored.setSpan(new RoundedBackgroundSpan(Color.RED, Color.WHITE), s.length() - 3, s.length(), 0);
    badge_ham.setText(sColored);
}
Carpi answered 30/10, 2015 at 14:44 Comment(3)
Was able to show the badge in Kitkat... but for Lollipop, strangely it gave NullPointerException when referencing the badge view, in your case 'badge_ham'. Did you face such?Baker
-1 Incomplete answer. The RoundedBackgroundSpan is something we have to come up with? Found some similar implementation but nothing with the same constructor.Concave
Rounded bg span is avilable in : raw.githubusercontent.com/jaychang0917/SimpleText/master/…Ministrant

© 2022 - 2024 — McMap. All rights reserved.