安卓底部导航栏下划线选项

4
我在我的应用程序中使用了一个BottomNavigationView。现在我的导航视图看起来像这样:

current state

但我希望它能够有下划线选定的项目,就像这样:

desired state

有没有使用一些标准属性来完成这个操作的方法?
3个回答

4
你可以使用 SpannableStringUnderlineSpan 来为项目标题添加下划线,当用户通过将 OnNavigationItemSelectedListener 监听器设置为 BottomNavigationView 并选择该项目时。
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ...

    BottomNavigationView bottomNavigationView = (BottomNavigationView)
            findViewById(R.id.bottom_navigation);


    underlineMenuItem(bottomNavigationView.getMenu().getItem(0)); // underline the default selected item when the activity is launched

    bottomNavigationView.setOnNavigationItemSelectedListener(
            new BottomNavigationView.OnNavigationItemSelectedListener() {
                @Override
                public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                    removeItemsUnderline(bottomNavigationView); // remove underline from all items
                    underlineMenuItem(item); // underline selected item
                    switch (item.getItemId()) {
                        // handle item clicks
                    }
                    return false;
                }
            });
}

private void removeItemsUnderline(BottomNavigationView bottomNavigationView) {
    for (int i = 0; i <  bottomNavigationView.getMenu().size(); i++) {
        MenuItem item = bottomNavigationView.getMenu().getItem(i);
        item.setTitle(item.getTitle().toString());
    }
}

private void underlineMenuItem(MenuItem item) {
    SpannableString content = new SpannableString(item.getTitle());
    content.setSpan(new UnderlineSpan(), 0, content.length(), 0);
    item.setTitle(content);

}

这仅适用于使用文本的菜单项,但在您的情况下,您只是在菜单中使用图标。为解决此问题,您需要在menu.xml中利用菜单项的android:title,并添加空格,如下所示:
bottom_nav_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/action_favorites"
        android:enabled="true"
        android:icon="@drawable/ic_favorite_white_24dp"
        android:title="@string/text_spaces"
        app:showAsAction="ifRoom" />
    <item
        android:id="@+id/action_schedules"
        android:enabled="true"
        android:icon="@drawable/ic_access_time_white_24dp"
        android:title="@string/text_spaces"
        app:showAsAction="ifRoom" />
    <item
        android:id="@+id/action_music"
        android:enabled="true"
        android:icon="@drawable/ic_audiotrack_white_24dp"
        android:title="@string/text_spaces"
        app:showAsAction="ifRoom" />
</menu>

在文本中使用&#160;来表示需要的空格数量,这将反映在每个项目下面的线条长度。

strings.xml

<resources>
    ...
    <string name="text_spaces">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</string>

这是预览

希望这能解决你的问题,并且欢迎任何疑问。


你好 @Zain,我稍后会检查一下,谢谢你的回答! - Денис Чорный

3

虽然我来晚了,但为了下一代 - 这是一个具有更多控制和动画的解决方案:) 使用约束布局。

此示例适用于4个项目,请根据需要调整数字。

首先,在包含BottomNavigationView的(约束)布局中创建具有所需特征的视图。将app:layout_constraintWidth_percent设置为1/项目数量。

    <com.google.android.material.bottomnavigation.BottomNavigationView
    android:id="@+id/mainTabBottomNavigation"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:background="@android:color/white"
    android:nestedScrollingEnabled="true"
    app:elevation="16dp"
    app:itemIconTint="@drawable/nav_account_item"
    app:labelVisibilityMode="unlabeled"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:menu="@menu/main_bottom_navigation"

    />

<View
    android:id="@+id/underline"
    android:layout_width="0dp"
    android:layout_height="3dp"
    android:background="@color/underlineColor"
    android:elevation="16dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintWidth_percent="0.25" />
</androidx.constraintlayout.widget.ConstraintLayout>

然后,在 OnNavigationItemSelectedListener 中使用此函数:

private fun underlineSelectedItem(view: View, itemId: Int) {
    val constraintLayout: ConstraintLayout = view as ConstraintLayout
    TransitionManager.beginDelayedTransition(constraintLayout)
    val constraintSet = ConstraintSet()
    constraintSet.clone(constraintLayout)
    constraintSet.setHorizontalBias(
        R.id.underline,
        getItemPosition(itemId) * 0.33f
    )
    constraintSet.applyTo(constraintLayout)
}

完整代码(片段):

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val navController = Navigation.findNavController(
            requireActivity(),
            R.id.mainNavigationFragment
        )
        mainTabBottomNavigation.setupWithNavController(navController)

        underlineSelectedItem(view, R.id.bottomNavFragmentHome) //select first item
        mainTabBottomNavigation.setOnNavigationItemSelectedListener { item ->
            underlineSelectedItem(view, item.itemId)
            true
        }
    }

    private fun underlineSelectedItem(view: View, itemId: Int) {
        val constraintLayout: ConstraintLayout = view as ConstraintLayout
        TransitionManager.beginDelayedTransition(constraintLayout)
        val constraintSet = ConstraintSet()
        constraintSet.clone(constraintLayout)
        constraintSet.setHorizontalBias(
            R.id.underline,
            getItemPosition(itemId) * 0.33f
        )
        constraintSet.applyTo(constraintLayout)
    }

    private fun getItemPosition(itemId: Int): Int {
        return when (itemId) {
            R.id.bottomNavFragmentHome -> 0
            R.id.bottomNavFragmentMyAccount -> 1
            R.id.bottomNavFragmentCoupon -> 2
            R.id.bottomNavFragmentSettings -> 3
            else -> 0
        }
    }

请注意,此实现覆盖了导航功能。为了保持此功能,您需要在过渡动画结束时使用NavigationUI.onNavDestinationSelected(item, navController)

完整代码:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val navController = Navigation.findNavController(
            requireActivity(),
            R.id.mainNavigationFragment
        )
        mainTabBottomNavigation.setupWithNavController(navController)

        underlineSelectedItem(view, R.id.bottomNavFragmentHome, null, null, null)
        mainTabBottomNavigation.setOnNavigationItemSelectedListener { item ->
            underlineSelectedItem(view, item.itemId, item, navController) { item1, navController1 ->
                safeLet(item1, navController1) { a, b->
                    NavigationUI.onNavDestinationSelected(a, b)
                }
            }
           true
        }
    }

    private fun underlineSelectedItem(
        view: View,
        itemId: Int,
        item: MenuItem?,
        navController: NavController?,
        onAnimationEnd: ((item: MenuItem?, navController: NavController?) -> Unit)?
    ) {
        val constraintLayout: ConstraintLayout = view as ConstraintLayout

        val transition: Transition = ChangeBounds()

        transition.addListener(object : Transition.TransitionListener {
            override fun onTransitionStart(transition: Transition?) {
            }

            override fun onTransitionEnd(transition: Transition?) {
                onAnimationEnd?.invoke(item, navController)
            }

            override fun onTransitionCancel(transition: Transition?) {
            }

            override fun onTransitionPause(transition: Transition?) {
            }

            override fun onTransitionResume(transition: Transition?) {
            }

        })

        TransitionManager.beginDelayedTransition(constraintLayout, transition)
        val constraintSet = ConstraintSet()
        constraintSet.clone(constraintLayout)
        constraintSet.setHorizontalBias(
            R.id.underline,
            getItemPosition(itemId) * 0.33f
        )
        constraintSet.applyTo(constraintLayout)
    }

    private fun getItemPosition(itemId: Int): Int {
        return when (itemId) {
            R.id.bottomNavFragmentHome -> 0
            R.id.bottomNavFragmentMyAccount -> 1
            R.id.bottomNavFragmentCoupon -> 2
            R.id.bottomNavFragmentSettings -> 3
            else -> 0
        }
    }

(safeLet 是 Kotlin 的一个助手函数,用于检查两个变量是否为空:

fun <T1 : Any, T2 : Any, R : Any> safeLet(p1: T1?, p2: T2?, block: (T1, T2) -> R?): R? {
    return if (p1 != null && p2 != null) block(p1, p2) else null
}

最终结果: 最终结果 gif

无法使用返回按钮 - Tomas

2
这可能是比我的其他答案更简单和更好的解决方案;它还可以具有各种功能,如宽度/高度的厚度、角落、形状、填充等可绘制功能。您可以创建一个选择器(仅具有选中状态),其重力设置为底部:

item_background.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true">
        <layer-list>
            <item android:gravity="bottom|center_horizontal">
                <shape android:shape="rectangle">
                    <size android:width="100dp" android:height="5dp" />
                    <solid android:color="#03DAC5" />
                    <corners android:bottomLeftRadius="3dp" android:bottomRightRadius="3dp" />
                </shape>
            </item>
        </layer-list>
    </item>
</selector>

将此设置为app:itemBackground:
<com.google.android.material.bottomnavigation.BottomNavigationView
    ....
    app:itemBackground="@drawable/item_background"

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