android BottomNavigationView 下划线项
android BottomNavigationView underline item
我在我的应用程序中使用 BottomNavigationView
。现在我的导航视图看起来像这样:
但我希望它带有带下划线的所选项目,如下所示:
有什么方法可以用一些标准属性来做到这一点吗?
当用户选择此项目时,您可以使用 SpannableString
with UnderlineSpan
到项目标题,方法是将 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>
并在您的文本中多次使用  
,因为您需要空格,这将反映每个项目下的行的长度
strings.xml
<resources>
...
<string name="text_spaces">          </string>
这是预览
希望这能解决您的问题,欢迎您提出任何疑问。
我知道我迟到了,但对于下一代来说 - 这是一个具有更多控制和动画的解决方案:) 使用约束布局。
示例为 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
}
)
最终结果:
This could be a simpler & better solution than my other answer; it also could have a variety of capabilities like the thickness of the width/height, corners, shapes, padding..etc stuff of drawable capabilities.
您可以创建一个重力设置为 bottom
:
的选择器(仅选中状态)
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"
我在我的应用程序中使用 BottomNavigationView
。现在我的导航视图看起来像这样:
但我希望它带有带下划线的所选项目,如下所示:
有什么方法可以用一些标准属性来做到这一点吗?
当用户选择此项目时,您可以使用 SpannableString
with UnderlineSpan
到项目标题,方法是将 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>
并在您的文本中多次使用  
,因为您需要空格,这将反映每个项目下的行的长度
strings.xml
<resources>
...
<string name="text_spaces">          </string>
这是预览
希望这能解决您的问题,欢迎您提出任何疑问。
我知道我迟到了,但对于下一代来说 - 这是一个具有更多控制和动画的解决方案:) 使用约束布局。
示例为 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
}
)
最终结果:
This could be a simpler & better solution than my other answer; it also could have a variety of capabilities like the thickness of the width/height, corners, shapes, padding..etc stuff of drawable capabilities.
您可以创建一个重力设置为 bottom
:
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"