在底部导航栏图标上方显示徽章

111

我已经在我的应用程序中实现了底部导航视图,并且我已经搜索了所有地方来显示像这样在图标上方显示徽章。 我想知道是否有可能实现。任何帮助都将不胜感激。 谢谢。


1
当然可以。您可以在底部导航视图的布局xml中自己创建徽章。创建一个帧布局,将菜单图标放置在徽章下方,并创建逻辑以显示/隐藏您的徽章视图。 - velval
1
你可以使用BadgeView;或在Github上搜索。 - zxbin
@velval,你有实现这个的代码示例或教程吗? - kalamar
1
https://dev59.com/ILXna4cB1Zd3GeqPFyVK#57073610 - Sumit Shukla
2
OP中的此链接是404。 - John
17个回答

169

这个答案在2020年2月被MDC v1.1.0取代了。

更新的示例,来自文档

final var badge = bottomNavigation.getOrCreateBadge(menuItemId);
badge.isVisible = true;
// An icon only badge will be displayed unless a number is set:
badge.number = 99;


如果你只想使用原始的BottomNavigationView而不使用第三方库,这是我所做的:

BottomNavigationMenuView bottomNavigationMenuView =
            (BottomNavigationMenuView) navigationView.getChildAt(0);
View v = bottomNavigationMenuView.getChildAt(3); 
BottomNavigationItemView itemView = (BottomNavigationItemView) v;
    
View badge = LayoutInflater.from(this)
            .inflate(R.layout.notification_badge, itemView, true);

那么这就是布局文件:

<merge 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">

    <TextView
        android:id="@+id/notifications.badge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="top|center_horizontal"
        android:layout_marginLeft="10dp"
        android:layout_marginStart="10dp"
        android:background="@drawable/notification_badge"
        android:gravity="center"
        android:padding="3dp"
        android:text="9+"
        android:textColor="@color/white"
        android:textSize="11sp" />
</merge>

然后通过id找到TextView并设置文本。 @drawable/notification_badge只是一个圆形形状的可绘制对象。


14
使用此方法删除徽章。public void removeBadge(BottomNavigationView navigationView, int index) { BottomNavigationMenuView bottomNavigationMenuView = (BottomNavigationMenuView) navigationView.getChildAt(0); View v = bottomNavigationMenuView.getChildAt(index); BottomNavigationItemView itemView = (BottomNavigationItemView) v; itemView.removeViewAt(itemView.getChildCount()-1); } - Nilesh Rathore
@Nilesh Rathore,移除代码无效,上面的代码会删除整个项目视图(包括徽章和图标),而我只需要删除徽章,请告诉我如何仅删除徽章。 - Ankit
你能否编辑答案并添加drawable的代码,以消除猜测。我是Android新手,这会有所帮助。 :) - Adi
3
我觉得我找到了一种略微更整洁的方法来移除@NileshRathore答案中的徽章。 相比于 itemView.removeViewAt(itemView.getChildCount()-1);,我使用了以下代码:View badge = itemView.findViewById(R.id.notifications_badge); ((ViewGroup)badge.getParent()).removeView(badge); - JamTheMan
9
我遇到了一个运行时异常:android.view.InflateException: 二进制 XML 文件第一行 #1:<merge /> 只能与有效的 ViewGroup 根元素和 attachToRoot=true 一起使用。 - Blazej SLEBODA
显示剩余9条评论

105

现在可以通过使用最新的Material依赖项来本地支持添加徽章。将以下内容添加到你的build.gradle文件中。

    implementation 'com.google.android.material:material:1.1.0-alpha09'

在你的布局中添加这个

<!-- The rest of your layout here ....-->

  <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottom_navigation"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:menu="@menu/bottom_nav_menu"
            />

那么你只需要

     val navBar  = findViewById<BottomNavigationView>(R.id.bottom_navigation)
     navBar.getOrCreateBadge(R.id.action_add).number = 2

R.id.action_add对于你来说将是你想在其上放置徽章的菜单项的ID。请从您提供给BottomNavigationView的菜单文件中检查它。

确保您的应用程序主题处于Theme.MaterialComponents中。

您可以在样式或清单中进行检查。 对于此示例,我的是这个

     <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="android:statusBarColor" tools:targetApi="lollipop">@color/colorPrimary</item>
    </style>

6
依我之见,答案应该更新为这个。我刚在我的应用程序中实现了它,效果非常好。 - Oliver Mahoney
5
如果你的应用主题没有继承自 Theme.MaterialComponents(例如我使用的是Theme.AppCompat),那么准备对应用程序的其余部分进行一些重新样式化。我应该先想到这点。顺便说一句,主题更改是必需的,否则徽章会崩溃。https://dev59.com/rek5XIcBkEYKwwoY59_h - ChrisPrime
8
在我之前的评论中,进一步阐述如何在迁移时保留你的样式。只需使用相应的 Theme.MaterialComponents.*.Bridge 变体来进行直接转换,无需重新设置任何样式就可以保留原有的样式,详见链接:https://medium.com/over-engineering/setting-up-a-material-components-theme-for-android-fbf7774da739 - ChrisPrime
1
本应该是被接受的答案,使用这个材料主题变体Theme.MaterialCompoonents.*.Bridge来避免崩溃。 - Sadda Hussain

66

2020年修订版:

请改用材料组件中的BottomNavigation, 它支持添加徽章和其他许多功能:

https://github.com/material-components/material-components-android/blob/master/docs/components/BottomNavigation.md

旧答案:

使用支持库底部导航栏, 在菜单项上显示徽标/通知相当复杂. 然而有简单的解决方案. 比如https://github.com/aurelhubert/ahbottomnavigation

这个库是底部导航栏的更高级版本,您可以使用此代码片段简单地设置菜单项上的徽标。

bottomNavigation.setNotification(notification, bottomNavigation.getItemsCount() - 1);

然后你将会得到以下的结果

enter image description here


运行良好,这个库比原生的BottomNav更好。 - Syed Usama Ahmad
1
请查看@Abel在下面提供的答案,以了解2020年的方法,该方法使用了谷歌材料库的标准视图。更多信息请访问:https://material.io/develop/android/components/bottom-navigation-view/ - HBB20
谷歌现在已经将这些徽章的实现大大简化到其材料库中。 - Abhilash Maurya
我也确认了在材料库的BottomNavigationView中可以使用徽章。 - bigant02

56

EDIT 2:
根据文档这里所述,BottomNavigationView现在原生支持显示徽章。

bottomNavigation.getOrCreateBadge(menuItemId)


我遇到了同样的问题,但我不想使用库。

所以我创建了一个名为layout_news_badge的自定义布局:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/badge_frame_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <TextView
        android:id="@+id/badge_text_view"
        android:layout_width="19dp"
        android:layout_height="19dp"
        android:textSize="11sp"
        android:textColor="@android:color/white"
        android:background="@drawable/news_bottom_nav_bg"
        android:layout_gravity="top"
        android:layout_marginTop="4dp"
        android:layout_marginStart="16dp"
        android:gravity="center"
        android:padding="2dp"
        tools:text="9+" />
</FrameLayout>

TextView 背景(news_bottom_nav_bg):

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
    <solid android:color="?attr/colorPrimary" />
</shape>

接着我创建了一个名为BottomMenuHelper的类,并添加了以下两个方法:

public static void showBadge(Context context, BottomNavigationView 
    bottomNavigationView, @IdRes int itemId, String value) {
    removeBadge(bottomNavigationView, itemId);
    BottomNavigationItemView itemView = bottomNavigationView.findViewById(itemId);
    View badge = LayoutInflater.from(context).inflate(R.layout.layout_news_badge, bottomNavigationView, false);
    
    TextView text = badge.findViewById(R.id.badge_text_view);
    text.setText(value);
    itemView.addView(badge);
}

public static void removeBadge(BottomNavigationView bottomNavigationView, @IdRes int itemId) {
    BottomNavigationItemView itemView = bottomNavigationView.findViewById(itemId);
    if (itemView.getChildCount() == 3) {
        itemView.removeViewAt(2);
    }
}

然后当我在我的Activity中调用它时:

BottomMenuHelper.showBadge(this, mBottomNavigationView, R.id.action_news, "1");

编辑 1: 根据jatin rana的建议,进行了改进。


1
marginTop和marginStart的值是什么? - letsCode
2
在调用 showBadge 之前调用 removeBadge,以避免添加重复视图。 - jatin rana
1
它可以与android.support.design.widget.BottomNavigationView一起使用。 - Roger
视图被添加到NavigationBar中项的左侧。你们如何控制徽章的位置,相对于项视图? - Otieno Rowland
1
棒极了,太棒了! - raphaelbgr
显示剩余3条评论

26

更新:现在有材料支持徽章,请参见下文

材料设计徽章

底部导航栏

val badge = bottomNavigation.getOrCreateBadge(menuItemId)
badge.isVisible = true
// An icon only badge will be displayed unless a number is set:
badge.number = 99

旧回答

基于 @Tinashe 的回答,我想要徽章展示如下,不带数字: 在此输入图像描述

代码:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
        // position = 2
        addBadge(POSITION_HISTORY)
    }

    /**
     * add badge(notification dot) to bottom bar
     * @param position to get badge container
     */
    @SuppressLint("PrivateResource")
    private fun addBadge(position: Int) {
        // get badge container (parent)
        val bottomMenu = navigation.getChildAt(0) as? BottomNavigationMenuView
        val v = bottomMenu?.getChildAt(position) as? BottomNavigationItemView

        // inflate badge from layout
        badge = LayoutInflater.from(this)
                .inflate(R.layout.badge_layout, bottomMenu, false)

        // create badge layout parameter
        val badgeLayout: FrameLayout.LayoutParams = FrameLayout.LayoutParams(badge?.layoutParams).apply {
            gravity = Gravity.CENTER_HORIZONTAL
            topMargin = resources.getDimension(R.dimen.design_bottom_navigation_margin).toInt()

            // <dimen name="bagde_left_margin">8dp</dimen>
            leftMargin = resources.getDimension(R.dimen.bagde_left_margin).toInt()
        }

        // add view to bottom bar with layout parameter
        v?.addView(badge, badgeLayout)
    }

徽章布局文件(badge_size=12dp)

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="@dimen/badge_size"       
    android:layout_height="@dimen/badge_size"
    android:background="@drawable/new_notification" />

和可绘制的背景新通知new_notification.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <corners android:radius="100dp"/>
    <solid android:color="#F44336"/>
</shape>

1
我该如何移除徽章? - Sudhir Singh Khanger
1
@SudhirKhanger 为了移除,创建一个全局变量来存储 badge 视图。然后只需在 v.removeView(badge) 中传递该变量,就像添加时一样。 - Narendra Pal
badge.isVisible = true 不起作用。badge.setVisible(true); 是可行的。 - Handelika
嗨@Handelika,isVisible = true是Kotlin版本,与之相同,感谢您的反馈。 - vuhung3990

18

现在Badge已经作为AndroidX BottomNavigationView的一部分,由BadgeDrawable实现。

查看文档
fun setBadge(count: Int) {
    if (count == 0) {
        bottomNavigationView.removeBadge(R.id.ticketsNavigation)
    } else {
        val badge = bottomNavigationView.getOrCreateBadge(R.id.ticketsNavigation) // previously showBadge
        badge.number = count
        badge.backgroundColor = getColor(R.color.badge)
        badge.badgeTextColor = getColor(R.color.blackTextColor)
    }
}

// Menu:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/ticketsNavigation"
        android:icon="@drawable/vector_drawable_navbar_tickets"
        android:title="@string/tickets_title"/>
    ...
</menu>

编辑:

如评论中所述,应该可以只更新徽章计数,而不需要像这样一直添加或删除徽章:

fun setBadge(count: Int) {
    bottomNavigationView.getBadge(menuItemId)?.isVisible = count > 0
}

它在文档中有提到,但截至2019年10月,它不是com.google.android.material:material:1.2.0_beta1及更早版本的一部分。 - Flummox - don't be evil SE
@Flummox-don'tbeevilSE,我们正在使用1.1.0-alpha07版本的生产环境,所以我认为你是错的。 - Morten Holmgaard
@MortenHomgaard,这很有趣,可能是Java和Kotlin之间的差异?我一直在尝试操作它,但无法按照您在此处发布的方式完成。 - Flummox - don't be evil SE
showBadge已更改为getOrCreateBadge - Morten Holmgaard
1
提醒未来的读者,如果您只想显示/隐藏徽章,可以使用bottom_navigation_view.getBadge(menuItemId)?.isVisible = NEW_VISIBILITY_STATE来访问它。无需不断删除/创建徽章 :)。 - Joaquim Ley

10

根据 @zxbin 的回答,您可以查看 BadgeView 并尝试以下代码

BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(this);
navigation.setSelectedItemId(R.id.navigation_store);
BottomNavigationMenuView bottomNavigationMenuView =
        (BottomNavigationMenuView) navigation.getChildAt(0);
View v = bottomNavigationMenuView.getChildAt(4); // number of menu from left
new QBadgeView(this).bindTarget(v).setBadgeNumber(5);

源自我的代码片段


你好,如何设置徽章的右边距,使其仅显示在视图的正确位置? - famfamfam
文档质量较差,但看起来确实很理想。 - TheRealChx101

8

一个简单的方法:

随着Material Design更新了他们的库,根据https://medium.com/over-engineering/hands-on-with-material-components-for-android-bottom-navigation-aae2aa9066be所述,我能够在我的RecyclerView适配器(在Fragment中)内部更新(或创建我的BottomNavigationView Badge)而不需要任何外部库。

初始状态: Initial State

因此,在适配器中,我从我的Activity中获取上下文并访问底部导航栏的实例:

navBottomView = ((AppCompatActivity)this.context).findViewById(R.id.nav_view);

检查徽章是否为空(尚未创建),如果是,则将其设置为所选择的数量:

  BadgeDrawable badgeDrawable = navBottomView.getBadge(R.id.navigation_carrinho);
  if (badgeDrawable == null)
      navBottomView.getOrCreateBadge(R.id.navigation_carrinho).setNumber(item.getQuantidade());

否则获取前一个数量并增加徽章值:
int previousValue = badgeDrawable.getNumber();
badgeDrawable.setNumber(previousValue + item.getQuantidade());

不要忘记导入:

import com.google.android.material.badge.BadgeDrawable;
import com.google.android.material.bottomnavigation.BottomNavigationView;

最终状态:

Final State

一体化,带有加入购物车按钮监听器:

btnAddCarrinho.setOnClickListener(v -> {
            navBottomView = ((AppCompatActivity) this.context).findViewById(R.id.nav_view);
            BadgeDrawable badgeDrawable = navBottomView.getBadge(R.id.navigation_carrinho);
            if (badgeDrawable == null) {
                navBottomView.getOrCreateBadge(R.id.navigation_carrinho).setNumber(item.getQuantidade());
            } else {
                int previousValue = badgeDrawable.getNumber();
                badgeDrawable.setNumber(previousValue + item.getQuantidade());
            }
        });

这是最佳答案。但请注意,为了使用它,必须从Theme.MaterialComponents继承您的父主题。 - porya74

5
请尝试以下步骤:

1) 创建徽章的XML文件(例如notification_badge_view.xml)

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/badge"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_gravity="top|center_horizontal"
        android:layout_marginStart="10dp"
        android:gravity="center"
        android:padding="3dp"
        app:srcCompat="@drawable/notification_badge" />
</FrameLayout>

2) 创建通知点形状的可绘制文件(例如badge_circle.xml)

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@color/colorAccent" />
    <stroke
        android:width="2dp"
        android:color="@android:color/white" />
</shape>

3) 在你的Activity的onCreate方法中,将徽章视图添加到BottomNavigationView中。

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

         addBadgeView();

    }

4) addBadgeView方法如下:

private void addBadgeView() {
        try {
            BottomNavigationMenuView menuView = (BottomNavigationMenuView) bottomNavigationBar.getChildAt(0);
            BottomNavigationItemView itemView = (BottomNavigationItemView) menuView.getChildAt(0); //set this to 0, 1, 2, or 3.. accordingly which menu item of the bottom bar you want to show badge
            notificationBadge = LayoutInflater.from(LandingActivity.this).inflate(R.layout.view_notification_badge, menuView, false);
            itemView.addView(notificationBadge);
            notificationBadge.setVisibility(GONE);// initially badge will be invisible 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

注意:bottomNavigationBar 是您的底部栏视图。
5)通过以下方法刷新徽章以显示和隐藏
private void refreshBadgeView() {
        try {
            boolean badgeIsVisible = notificationBadge.getVisibility() != GONE;
            notificationBadge.setVisibility(badgeIsVisible ? GONE : VISIBLE);//makes badge visible and invisible 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

6) 当我们点击特定的底部页标签时,最后将其隐藏,使用以下代码行。
bottomNavigationBar.setOnNavigationItemSelectedListener(new 
BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) 
              {
                switch (menuItem.getItemId()) {
                    case R.id.bottom_bar_one:
                        //while moving to first fragment
                        notificationBadge.setVisibility(GONE);
                        break;
                    case R.id.bottom_bar_two:
                        //moving to second fragment
                        break;
                    case R.id.bottom_bar_three:
                        //moving to third fragment
                        break;
                }
                return true;
            }
        });

4
这里有一个简单的方法,可以使用材料底部导航创建和删除徽章计数。
public class BadgeIconHelper {
    
    public static void showNotificationBadge(BottomNavigationView
                                                     bottomNavigationView, @IdRes int itemId, String value) {
        BadgeDrawable badge = bottomNavigationView.getOrCreateBadge(itemId);
        badge.setBackgroundColor(ContextCompat.getColor(bottomNavigationView.getContext(), R.color.color_primary));
        badge.setBadgeTextColor(ContextCompat.getColor(bottomNavigationView.getContext(), R.color.color_white));
        badge.setMaxCharacterCount(3);
        badge.setVerticalOffset(2);
        badge.setVisible(true);
        badge.setNumber(Integer.parseInt(value));
    }
    
    public static void removeNotificationBadge(BottomNavigationView bottomNavigationView, @IdRes int itemId) {
        BadgeDrawable badgeDrawable = bottomNavigationView.getBadge(itemId);
        if (badgeDrawable != null) {
            badgeDrawable.setVisible(false);
            badgeDrawable.clearNumber();
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接