How to implement badges in the Navigation Drawer menu ?

Sample badges: 10 and 99+ (picture)

In the badge I want to display quantitative data from the database. Created the markup:

 <LinearLayout android:orientation="horizontal" android:layout_width="0dp" android:layout_height="match_parent" android:gravity="start" android:layout_weight="85" > <TextView android:id="@+id/item_name" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_marginLeft="16dp" android:layout_marginStart="16dp" android:layout_marginRight="16dp" android:drawablePadding="16dp" android:layout_marginEnd="16dp" android:textSize="14sp" android:gravity="center_vertical" android:textColor="@color/myTextPrimaryColor" /> </LinearLayout> <LinearLayout android:orientation="horizontal" android:layout_width="0dp" android:layout_height="match_parent" android:layout_margin="16dp" android:gravity="end" android:layout_weight="15" > <TextView android:id="@+id/badge" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="end|center_vertical" android:textSize="14sp" /> </LinearLayout> 

So add new items to the list. File: NavigationDrawerFregment

 public List<NavigationItem> getMenu() { List<NavigationItem> items = new ArrayList<NavigationItem>(); String[] nav_items = getResources().getStringArray(R.array.views_array); items.add(new NavigationItem(nav_items[0], getResources().getDrawable(R.drawable.ic_menu_check))); items.add(new NavigationItem(nav_items[1], getResources().getDrawable(R.drawable.ic_menu_check))); items.add(new NavigationItem(nav_items[2], getResources().getDrawable(R.drawable.ic_menu_check))); items.add(new NavigationItem(nav_items[3], getResources().getDrawable(R.drawable.ic_add_tooth))); items.add(new NavigationItem(nav_items[4], getResources().getDrawable(R.drawable.ic_add_tooth_doc))); items.add(new NavigationItem(nav_items[5], getResources().getDrawable(R.drawable.ic_menu_check))); return items; } 

As a result, I have updated markup, but I do not know how to display the desired menu item.

NavigationItem code:

 public class NavigationItem { private String mText; private Drawable mDrawable; public NavigationItem(String text, Drawable drawable) { mText = text; mDrawable = drawable; } public String getText() { return mText; } public void setText(String text) { mText = text; } public Drawable getDrawable() { return mDrawable; } public void setDrawable(Drawable drawable) { mDrawable = drawable; } 

}

UPD2 - my NavigationDrawerAdapter

 public class NavigationDrawerAdapter extends RecyclerView.Adapter<NavigationDrawerAdapter.ViewHolder> { private List<NavigationItem> mData; private NavigationDrawerCallbacks mNavigationDrawerCallbacks; private View mSelectedView; private int mSelectedPosition; private TextView badge_tv; public NavigationDrawerAdapter(List<NavigationItem> data) { mData = data; } public NavigationDrawerCallbacks getNavigationDrawerCallbacks() { return mNavigationDrawerCallbacks; } public void setNavigationDrawerCallbacks(NavigationDrawerCallbacks navigationDrawerCallbacks) { mNavigationDrawerCallbacks = navigationDrawerCallbacks; } @Override public NavigationDrawerAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.drawer_row, viewGroup, false); final ViewHolder viewHolder = new ViewHolder(v); viewHolder.itemView.setClickable(true); viewHolder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mSelectedView != null) { mSelectedView.setSelected(false); } mSelectedPosition = viewHolder.getAdapterPosition(); v.setSelected(true); mSelectedView = v; if (mNavigationDrawerCallbacks != null) mNavigationDrawerCallbacks.onNavigationDrawerItemSelected(viewHolder.getAdapterPosition()); } } ); viewHolder.itemView.setBackgroundResource(R.drawable.row_selector); badge_tv = (TextView) viewHolder.itemView.findViewById(R.id.badge); return viewHolder; } @Override public void onBindViewHolder(NavigationDrawerAdapter.ViewHolder viewHolder, int i) { viewHolder.textView.setText(mData.get(i).getText()); viewHolder.textView.setCompoundDrawablesWithIntrinsicBounds(mData.get(i).getDrawable(), null, null, null); if (mSelectedPosition == i) { if (mSelectedView != null) { mSelectedView.setSelected(false); } mSelectedPosition = i; mSelectedView = viewHolder.itemView; mSelectedView.setSelected(true); } if (mData.get(i).getBadge().equals("")) badge_tv.setVisibility(View.INVISIBLE); else { badge_tv.setVisibility(View.VISIBLE); badge_tv.setText(mData.get(i).getBadge()); } } public void setBadge(int count , int pos){ mData.get(pos).setBadge(String.valueOf(count)) ; notifyDataSetChanged(); } public void dropBage(int pos){ // убрать бэйдж из пункта pos mData.get(pos).setBadge(""); notifyDataSetChanged(); } public void selectPosition(int position) { mSelectedPosition = position; notifyItemChanged(position); } @Override public int getItemCount() { return mData != null ? mData.size() : 0; } public static class ViewHolder extends RecyclerView.ViewHolder { public TextView textView; public ViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.item_name); } } 

UPD3 - NavigationDrawerFragment

 public class NavigationDrawerFragment extends Fragment implements NavigationDrawerCallbacks { /** * Remember the position of the selected item. */ private static final String STATE_SELECTED_POSITION = "selected_navigation_drawer_position"; /** * Per the design guidelines, you should show the drawer on launch until the user manually * expands it. This shared preference tracks this. */ private static final String PREF_USER_LEARNED_DRAWER = "navigation_drawer_learned"; /** * A pointer to the current callbacks instance (the Activity). */ private NavigationDrawerCallbacks mCallbacks; /** * Helper component that ties the action bar to the navigation drawer. */ private ActionBarDrawerToggle mActionBarDrawerToggle; private DrawerLayout mDrawerLayout; private RecyclerView mDrawerList; private View mFragmentContainerView; private int mCurrentSelectedPosition = 0; private boolean mFromSavedInstanceState; private boolean mUserLearnedDrawer; String LOG_TAG = "myLogs"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Read in the flag indicating whether or not the user has demonstrated awareness of the // drawer. See PREF_USER_LEARNED_DRAWER for details. SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity()); mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false); if (savedInstanceState != null) { mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION); mFromSavedInstanceState = true; } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_navigation_drawer, container, false); mDrawerList = (RecyclerView) view.findViewById(R.id.drawerList); LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); layoutManager.setOrientation(LinearLayoutManager.VERTICAL); mDrawerList.setLayoutManager(layoutManager); mDrawerList.setHasFixedSize(true); final List<NavigationItem> navigationItems = getMenu(); NavigationDrawerAdapter adapter = new NavigationDrawerAdapter(navigationItems); adapter.setNavigationDrawerCallbacks(this); mDrawerList.setAdapter(adapter); selectItem(mCurrentSelectedPosition); return view; } public boolean isDrawerOpen() { return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mFragmentContainerView); } public ActionBarDrawerToggle getActionBarDrawerToggle() { return mActionBarDrawerToggle; } public DrawerLayout getDrawerLayout() { return mDrawerLayout; } @Override public void onNavigationDrawerItemSelected(int position) { selectItem(position); } public List<NavigationItem> getMenu() { List<NavigationItem> items = new ArrayList<NavigationItem>(); String[] nav_items = getResources().getStringArray(R.array.views_array); items.add(new NavigationItem(nav_items[0], getResources().getDrawable(R.drawable.ic_menu_check))); items.add(new NavigationItem(nav_items[1], getResources().getDrawable(R.drawable.ic_menu_check))); items.add(new NavigationItem(nav_items[2], getResources().getDrawable(R.drawable.ic_menu_check))); items.add(new NavigationItem(nav_items[3], getResources().getDrawable(R.drawable.ic_menu_check))); items.add(new NavigationItem(nav_items[4], getResources().getDrawable(R.drawable.ic_menu_check))); items.add(new NavigationItem(nav_items[5], getResources().getDrawable(R.drawable.ic_menu_check))); return items; } /** * Users of this fragment must call this method to set up the navigation drawer interactions. * * @param fragmentId The android:id of this fragment in its activity's layout. * @param drawerLayout The DrawerLayout containing this fragment's UI. * @param toolbar The Toolbar of the activity. */ public void setup(int fragmentId, DrawerLayout drawerLayout, Toolbar toolbar) { mFragmentContainerView = getActivity().findViewById(fragmentId); mDrawerLayout = drawerLayout; mDrawerLayout.setStatusBarBackgroundColor(getResources().getColor(R.color.myPrimaryDarkColor)); mActionBarDrawerToggle = new ActionBarDrawerToggle(getActivity(), mDrawerLayout, toolbar, R.string.drawer_open, R.string.drawer_close) { @Override public void onDrawerClosed(View drawerView) { super.onDrawerClosed(drawerView); if (!isAdded()) return; getActivity().invalidateOptionsMenu(); // calls onPrepareOptionsMenu() } @Override public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); if (!isAdded()) return; if (!mUserLearnedDrawer) { mUserLearnedDrawer = true; SharedPreferences sp = PreferenceManager .getDefaultSharedPreferences(getActivity()); sp.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).apply(); } getActivity().invalidateOptionsMenu(); // calls onPrepareOptionsMenu() Log.v(LOG_TAG, "dr opened"); InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(getActivity().getCurrentFocus().getWindowToken(), 0); } }; // If the user hasn't 'learned' about the drawer, open it to introduce them to the drawer, // per the navigation drawer design guidelines. if (!mUserLearnedDrawer && !mFromSavedInstanceState) { mDrawerLayout.openDrawer(mFragmentContainerView); } // Defer code dependent on restoration of previous instance state. mDrawerLayout.post(new Runnable() { @Override public void run() { mActionBarDrawerToggle.syncState(); } }); mDrawerLayout.setDrawerListener(mActionBarDrawerToggle); } private void selectItem(int position) { mCurrentSelectedPosition = position; if (mDrawerLayout != null) { mDrawerLayout.closeDrawer(mFragmentContainerView); } if (mCallbacks != null) { mCallbacks.onNavigationDrawerItemSelected(position); } ((NavigationDrawerAdapter) mDrawerList.getAdapter()).selectPosition(position); } public void openDrawer() { mDrawerLayout.openDrawer(mFragmentContainerView); } public void closeDrawer() { mDrawerLayout.closeDrawer(mFragmentContainerView); } @Override public void onAttach(Activity activity) { super.onAttach(activity); try { mCallbacks = (NavigationDrawerCallbacks) activity; } catch (ClassCastException e) { throw new ClassCastException("Activity must implement NavigationDrawerCallbacks."); } } @Override public void onDetach() { super.onDetach(); mCallbacks = null; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // Forward the new configuration the drawer toggle component. mActionBarDrawerToggle.onConfigurationChanged(newConfig); } public void setUserData(String user, String email, Bitmap avatar) { ImageView avatarContainer = (ImageView) mFragmentContainerView.findViewById(R.id.imgAvatar); ((TextView) mFragmentContainerView.findViewById(R.id.txtUserEmail)).setText(email); ((TextView) mFragmentContainerView.findViewById(R.id.txtUsername)).setText(user); avatarContainer.setImageDrawable(new RoundImage(avatar)); } public View getGoogleDrawer() { return mFragmentContainerView.findViewById(R.id.googleDrawer); } public static class RoundImage extends Drawable { private final Bitmap mBitmap; private final Paint mPaint; private final RectF mRectF; private final int mBitmapWidth; private final int mBitmapHeight; public RoundImage(Bitmap bitmap) { mBitmap = bitmap; mRectF = new RectF(); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setDither(true); final BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); mPaint.setShader(shader); mBitmapWidth = mBitmap.getWidth(); mBitmapHeight = mBitmap.getHeight(); } @Override public void draw(Canvas canvas) { canvas.drawOval(mRectF, mPaint); } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); mRectF.set(bounds); } @Override public void setAlpha(int alpha) { if (mPaint.getAlpha() != alpha) { mPaint.setAlpha(alpha); invalidateSelf(); } } @Override public void setColorFilter(ColorFilter cf) { mPaint.setColorFilter(cf); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } @Override public int getIntrinsicWidth() { return mBitmapWidth; } @Override public int getIntrinsicHeight() { return mBitmapHeight; } public void setAntiAlias(boolean aa) { mPaint.setAntiAlias(aa); invalidateSelf(); } @Override public void setFilterBitmap(boolean filter) { mPaint.setFilterBitmap(filter); invalidateSelf(); } @Override public void setDither(boolean dither) { mPaint.setDither(dither); invalidateSelf(); } public Bitmap getBitmap() { return mBitmap; } } 

}

  • one
    Do you need to be in the NeoKree library or have you decided to implement such functionality yourself? If so, do not waste your time in coming up with something that already exists and works great. Mike Penz - MaterialDrawer or the library of the same NeoKree from the screenshot, but it loses a little first. - pavlofff
  • @pavlofff, can I independently trim the library of Mike Penza? I met her, she has a rich functionality, but I need only part of it. - web_alex
  • There are sources - it means you can, but it will not be very ethical - pavlofff
  • @pavlofff, I do not want to assign myself his code. I don't need all the functionality of Mike. Therefore, I asked about the trimmed version. At the moment I use the template Navigation Drawer, which is updated to Material Design and imported into Android Studio. But he does not have badges, but I need them. In fact, if, then I have almost implemented my badges (I changed the markup and designed it as necessary, there is information for output, but I don’t know where to put it in the code, what to display in that textView that I need. - web_alex
  • one
    In general, I do not see a problem in the redundant functionality of libraries - proguard can remove unused classes from the release assembly. - pavlofff

2 answers 2

The implementation of these badges can be in different ways - I offer the most compromise and full-featured, that is, not all items can have a badge and then the badge badge does not appear at all.

Since the generation of items during scrolling takes place according to complicated laws, we need to save the values ​​for the badges in some kind of separate storage so that when the item is recreated, it is not lost. We will keep these values ​​in the list of models with your items.

In the class holder, add a link to the new element badgeView :

 public static class ViewHolder extends RecyclerView.ViewHolder { public TextView textView; public TextView badgeView; public ViewHolder(View itemView) { super(itemView); textView = (TextView) itemView.findViewById(R.id.item_name); badgeView = (TextView) itemView.findViewById(R.id.badge); } } 

In the adapter's onBindViewHolder() method, onBindViewHolder() check if there is a value for the badge and set the visibility / invisibility of the widget that displays it:

 @Override public void onBindViewHolder(NavigationDrawerAdapter.ViewHolder viewHolder, int i) { if (mData.get(i).getBadge().equals("")) viewHolder.badgeView.setVisibility(View.INVISIBLE); else { viewHolder.badgeView.setVisibility(View.VISIBLE); viewHolder.badgeView.setText(mData.get(i).getBadge()); } 

In the adapter, we implement the setter, to set the values ​​in the badge (if you need a getter, you can also organize it naturally):

 public void setBadge(int count , int pos){ mData.get(pos).setBadge(String.valueOf(count)) ; notifyDataSetChanged(); } public void dropBadge(int pos){ // убрать бэйдж из пункта pos mData.get(pos).setBadge("")) ; notifyDataSetChanged(); } 

Add a field to the model to save the values ​​of the badge (if you wish, you can add initialization of the initial value of the badge to the constructor and set it when filling in the list items if necessary):

 public class NavigationItem { private String mText; private Drawable mDrawable; private String mBadge; public NavigationItem(String text, Drawable drawable) { mText = text; mDrawable = drawable; mBadge = ""; } public String getText() { return mText; } public void setText(String text) { mText = text; } public Drawable getDrawable() { return mDrawable; } public void setDrawable(Drawable drawable) { mDrawable = drawable; } public String getBadge() { return mBadge; } public void setBadge(String badge) { mBadge = badge; } } 

It is suggested to use this good way - in the activation \ fragment you set the value for the badge through the adapter instance that you connect to ReciclerView() with list items, you need to choose the pos value yourself, based on the position of the item where you want to show the badge (here’s the third point of the driver to display the value 15):

 navigationDrawerAdapter.setBadge( 15, 2); 

If you need to write all sorts of +99 and not display negative values, implement additional functionality in the setter for converting count to a string for the widget with the necessary formatting.

You need to create a method in your class menu, for example:

  public static class ViewHolder extends RecyclerView.ViewHolder { public TextView item_name_tv, badge_tv, notifications; private int notificationsNumber; public ViewHolder(View itemView) { super(itemView); item_name_tv = (TextView) itemView.findViewById(R.id.item_name); badge_tv = (TextView) itemView.findViewById(R.id.badge); notifications = (TextView) view.findViewById(R.id.notification_id); notificationsNumber = 0; } public ViewHolder setNotifications(int notifications) { String textNotification; textNotification = String.valueOf(notifications); if(notifications < 1) { textNotification = ""; } if(notifications > 99) { textNotification = "99+"; } this.notifications.setText(textNotification); numberNotifications = notifications; return this; } } 

In the layout add another TextView:

 <LinearLayout android:orientation="horizontal" android:layout_width="0dp" android:layout_height="match_parent" android:gravity="start" android:layout_weight="85" > <TextView android:id="@+id/item_name" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_marginLeft="16dp" android:layout_marginStart="16dp" android:layout_marginRight="16dp" android:drawablePadding="16dp" android:layout_marginEnd="16dp" android:textSize="14sp" android:gravity="center_vertical" android:textColor="@color/myTextPrimaryColor" /> <TextView android:layout_width="40dp" android:layout_height="wrap_content" android:id="@+id/notification_id" android:layout_alignParentRight="true" android:layout_marginRight="8dp" android:lines="1" android:maxLength="3" android:textSize="14sp" android:textStyle="bold" android:layout_centerVertical="true" android:gravity="center_horizontal" /> </LinearLayout> 
  • I will replace notifications with my count, because You need to display the number of records in the database table. And how can I output specifically to a certain element (badge)? I can not pass int to the setNotifications method. - web_alex
  • 2
    What is your NavigationItem code? - Alexey Shtanko
  • updated question - UPD1. - web_alex