Badge on BottomNavigationView
I am trying to add a badge to the BottomNavigationView
Item without using any library, however somehow the BottomNavigationView
is not showing the badge (custom_view)
main_view.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.hrskrs.test.MainActivity">
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
app:itemBackground="@color/colorPrimary"
app:itemIconTint="@color/colorAccent"
app:itemTextColor="@color/colorPrimaryDark"
app:menu="@menu/bottom_navigation_main" />
</RelativeLayout>
bottom_navigation_menu.xml:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/item_test"
android:icon="@mipmap/ic_launcher"
android:title="action1"
app:showAsAction="always" />
<item
android:enabled="true"
android:icon="@mipmap/ic_launcher"
android:title="action2"
app:showAsAction="ifRoom" />
<item
android:enabled="true"
android:icon="@mipmap/ic_launcher"
android:title="action3"
app:showAsAction="ifRoom" />
</menu>
Activity extended from AppCompatActivity
:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu = bottomNavigationView.getMenu();
menu.clear();
getMenuInflater().inflate(R.menu.bottom_navigation_main, menu);
MenuItem item = menu.findItem(R.id.item_test);
item = MenuItemCompat.setActionView(item, R.layout.custom_view);
RelativeLayout badgeWrapper = (RelativeLayout) MenuItemCompat.getActionView(item);
TextView textView = (TextView) badgeWrapper.findViewById(R.id.txtCount);
textView.setText("99+");
return super.onCreateOptionsMenu(menu);
}
custom_view.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="@android:style/Widget.ActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:clickable="true"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="Notification Icon"
android:gravity="center"
android:src="@mipmap/ic_launcher" />
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/txtCount"
android:gravity="right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_badge"
android:text="55"
android:textColor="#ffffffff"
android:textSize="12sp" />
</RelativeLayout>
Istead of showing (badge) custom_view
it shows the item itself only:
Below you can see from the debug mode that the view
accessed is the right one and it is being set correctly. However somehow the BottomNavigationView
is not being invalidated:
Solution 1:
You can use the BottomNavigationView
provided by the Material Components Library.
Just add the BottomNavigationView
to your layout:
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_gravity="bottom"
app:menu="@menu/navigation_main"
../>
Then use in your code:
int menuItemId = bottomNavigationView.getMenu().getItem(0).getItemId();
BadgeDrawable badge = bottomNavigationView.getOrCreateBadge(menuItemId);
badge.setNumber(2);
To change the badge gravity use the setBadgeGravity
method.
badge.setBadgeGravity(BadgeDrawable.BOTTOM_END);
Solution 2:
I managed to make BottomNavigationView with the badge. Here is my code (Kotlin).
This is the panel (inherited from BottomNavigationView)
/** Bottom menu with badge */
class AdvancedBottomNavigationView(context: Context, attrs: AttributeSet) : BottomNavigationView(context, attrs) {
private companion object {
const val BADGE_MIN_WIDTH = 16 // [dp]
const val BADGE_MARGIN_TOP = 5 // [dp]
const val BADGE_MARGIN_LEFT = 15 // [dp]
}
@Inject internal lateinit var uiCalculator: UICalculatorInterface
private val bottomMenuView: BottomNavigationMenuView
init {
// Get access to internal menu
val field = BottomNavigationView::class.java.getDeclaredField("mMenuView")
field.isAccessible = true
bottomMenuView = field.get(this) as BottomNavigationMenuView
App.injections.presentationLayerComponent!!.inject(this)
@SuppressLint("CustomViewStyleable")
val a = context.obtainStyledAttributes(attrs, R.styleable.advanced_bottom_navigation_bar)
val badgeLayoutId = a.getResourceId(R.styleable.advanced_bottom_navigation_bar_badge_layout, -1)
a.recycle()
initBadges(badgeLayoutId)
}
/**
* [position] index of menu item */
fun setBadgeValue(position: Int, count: Int) {
val menuView = bottomMenuView
val menuItem = menuView.getChildAt(position) as BottomNavigationItemView
val badge = menuItem.findViewById(R.id.bottom_bar_badge)
val badgeText = menuItem.findViewById(R.id.bottom_bar_badge_text) as TextView
if (count > 0) {
badgeText.text = count.toString()
badge.visibility = View.VISIBLE
} else {
badge.visibility = View.GONE
}
}
/**
* Select menu item
* [position] index of menu item to select
*/
fun setSelected(position: Int) = bottomMenuView.getChildAt(position).performClick()
private fun initBadges(badgeLayoutId: Int) {
// Adding badges to each Item
val menuView = bottomMenuView
val totalItems = menuView.childCount
val oneItemAreaWidth = uiCalculator.getScreenSize(context).first / totalItems
val marginTop = uiCalculator.dpToPixels(context, BADGE_MARGIN_TOP)
val marginLeft = uiCalculator.dpToPixels(context, BADGE_MARGIN_LEFT)
for (i in 0 until totalItems) {
val menuItem = menuView.getChildAt(i) as BottomNavigationItemView
// Add badge to every item
val badge = View.inflate(context, badgeLayoutId, null) as FrameLayout
badge.visibility = View.GONE
badge.minimumWidth = uiCalculator.dpToPixels(context, BADGE_MIN_WIDTH)
val layoutParam = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT)
layoutParam.gravity = Gravity.START
layoutParam.setMargins(oneItemAreaWidth / 2 + marginLeft, marginTop, 0, 0)
menuItem.addView(badge, layoutParam)
}
}
}
It's attr.xml file with options for this component:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="advanced_bottom_navigation_bar">
<attr name="badge_layout" format="reference|integer" />
</declare-styleable>
</resources>
Background for badge from drawable folder:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="#ff0000" />
<corners android:radius="10dp" />
</shape>
</item>
</selector>
Badge itself:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="@+id/bottom_bar_badge"
android:layout_height="20dp"
android:layout_width="20dp"
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@drawable/bcg_badge"
>
<TextView
android:id="@+id/bottom_bar_badge_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1"
android:textSize="10sp"
android:textColor="@android:color/white"
xmlns:android="http://schemas.android.com/apk/res/android"
android:textAlignment="center"
android:layout_gravity="center"/>
</FrameLayout>
And this is an example how to use it in your code:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
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="su.ivcs.ucim.presentationLayer.userStories.mainScreen.view.MainActivity">
<su.ivcs.ucim.presentationLayer.common.advancedBottomNavigationView.AdvancedBottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
app:itemBackground="@android:color/white"
app:itemIconTint="@color/main_screen_tabs_menu_items"
app:itemTextColor="@color/main_screen_tabs_menu_items"
app:menu="@menu/main_screen_tabs_menu"
app:badge_layout = "@layout/common_badge"
app:layout_constraintTop_toBottomOf="@+id/fragmentsContainer"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
</android.support.constraint.ConstraintLayout>
I hope this helps you.
Solution 3:
@hrskrs Try adding a higher elevation on your txtCount or badgeWrapper itself.
BottomNavigationView
seems to have higher elevation than the views on the screen.
I struggled with showing badges on BottomNavigationView
items. My badge (without any text value) being part of the drawable itself turned grey when user clicked other item or became the same color defined in the tint (if not defined is colorPrimary).
I think you will run into the same problem I faced with colouring of the badge/counter on top of menu item of BottomNavigationView
as tint color will be applied to the item itself and your badgeWrapper being part of MenuItem
will take the tint (turns grey when you tap any other item which you will not want I guess).
Check out my answer here: Is there a way to display notification badge on Google's official BottomNavigationView menu items introduced in API 25?
I used an ImageView
for a badge but you can have your badgeWrapper RelativeView
instead of the ImageView.