如何在 BottomNavigationView 中设置指标? [复制]
Posted
技术标签:
【中文标题】如何在 BottomNavigationView 中设置指标? [复制]【英文标题】:How to set indicator in BottomNavigationView? [duplicate] 【发布时间】:2019-07-11 12:12:14 【问题描述】:我想在底部导航视图上设置指示器。您可以在底部看到橙色线 如何设置指标?
我没有找到任何指标示例。
<android.support.design.widget.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_
android:layout_
android:layout_alignParentBottom="true"
android:background="@drawable/bottom_navigation_bg"
app:labelVisibilityMode="unlabeled"
app:menu="@menu/bottom_menu_main"/>
【问题讨论】:
@NileshRathod 他/她想要指示器(底部为 TabLayout),而不是徽章。 我没有问过徽章。我问了指标。 @PratikButani 对不起我的错 @Kvoid 我认为这是不好的做法,因为您会找到一些库或 Google 推荐的代码,它会让您更改图标颜色或文本颜色以识别当前选定的项目。即使您想做同样的事情,您也可以为此创建自定义视图。谢谢。 我不明白为什么这个问题已经结束,它指向了一个不相关的问题。有足够声誉的人必须重新提出这个问题。 【参考方案1】:我之前的答案提供了很大的灵活性,并且在所选状态之间很好地动画,但涉及的代码量相对较大。没有动画的更简单的解决方案是使用BottomNavigationView
的itemBackground
属性:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_
android:layout_
android:orientation="vertical">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_
android:layout_
android:layout_weight="1" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
android:layout_
android:layout_
app:itemBackground="@drawable/bottom_nav_tab_background"
android:layout_gravity="bottom"
app:menu="@menu/menu_bottom_nav" />
</LinearLayout>
bottom_nav_tab_background
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<layer-list>
<item android:gravity="bottom">
<shape android:shape="rectangle">
<size android: />
<solid android:color="?attr/colorPrimary" />
</shape>
</item>
</layer-list>
</item>
</selector>
【讨论】:
我的代码使用 app:itemIconTint="@drawable/bottom_nav_tab_background" 而不是 app:itemBackground="@drawable/bottom_nav_tab_background" 我不知道为什么【参考方案2】:这是我为添加指示器和移动它而设计的。
class IndicatorBottomNavigationView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.bottomNavigationStyle
) : BottomNavigationView(context, attrs, defStyleAttr)
private val indicator: View = View(context).apply
layoutParams = LayoutParams(0, 5)
setBackgroundColor(ContextCompat.getColor(context, R.color.carecredit_green))
init
addView(indicator)
var animateIndicator = true
override fun setOnNavigationItemSelectedListener(listener: OnNavigationItemSelectedListener?)
OnNavigationItemSelectedListener selectedItem ->
menu
.children
.first item ->
item.itemId == selectedItem.itemId
.itemId
.let
findViewById<View>(it)
.let view ->
this.post
indicator.layoutParams = LayoutParams(view.width, 5)
if (animateIndicator)
indicator
.animate()
.x(view.x)
.start()
else
indicator.x = view.x
indicator.y = view.y
listener?.onNavigationItemSelected(selectedItem) ?: false
.let
super.setOnNavigationItemSelectedListener(it)
【讨论】:
这太酷了,但是第一次没有显示选中的项目指示符 也不适用于背压【参考方案3】:要使其正常工作,您需要扩展 BottomNavigationView
以便能够拥有多个侦听器。最初的实现只允许一个,但您需要另一个能够告诉指标何时移动。
class CustomBottomNavigationView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : BottomNavigationView(context, attrs, defStyleAttr), OnNavigationItemSelectedListener
private val onNavigationItemSelectedListeners =
mutableListOf<OnNavigationItemSelectedListener>()
init
super.setOnNavigationItemSelectedListener(this)
itemIconTintList = null
override fun setOnNavigationItemSelectedListener(listener: OnNavigationItemSelectedListener?)
if (listener != null) addOnNavigationItemSelectedListener(listener)
fun addOnNavigationItemSelectedListener(listener: OnNavigationItemSelectedListener)
onNavigationItemSelectedListeners.add(listener)
fun addOnNavigationItemSelectedListener(listener: (Int) -> Unit)
addOnNavigationItemSelectedListener(OnNavigationItemSelectedListener
for (i in 0 until menu.size()) if (menu.getItem(i) == it) listener(i)
false
)
override fun onNavigationItemSelected(item: MenuItem) = onNavigationItemSelectedListeners
.map it.onNavigationItemSelected(item)
.fold(false) acc, it -> acc || it
然后创建一个指标项:
class BottomNavigationViewIndicator @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr)
val size = 8f
private val targetId: Int
private var target: BottomNavigationMenuView? = null
private var rect = Rect()
// val drawPaint = Paint()
private val backgroundDrawable: Drawable
private var index = 0
private var animator: AnimatorSet? = null
init
if (attrs == null)
targetId = NO_ID
backgroundDrawable = ColorDrawable(Color.TRANSPARENT)
else
with(context.obtainStyledAttributes(attrs, BottomNavigationViewIndicator))
targetId =
getResourceId(BottomNavigationViewIndicator_targetBottomNavigation, NO_ID)
val clippableId =
getResourceId(BottomNavigationViewIndicator_clippableBackground, NO_ID)
backgroundDrawable = if (clippableId != NO_ID)
androidx.appcompat.content.res.AppCompatResources.getDrawable(
context,
clippableId
) ?: ColorDrawable(Color.TRANSPARENT)
else
ColorDrawable(
getColor(
BottomNavigationViewIndicator_clippableBackground,
Color.TRANSPARENT
)
)
recycle()
override fun onAttachedToWindow()
super.onAttachedToWindow()
if (targetId == NO_ID) return attachedError("invalid target id $targetId, did you set the app:targetBottomNavigation attribute?")
val parentView =
parent as? View ?: return attachedError("Impossible to find the view using $parent")
val child = parentView.findViewById<View?>(targetId)
if (child !is CustomBottomNavigationView) return attachedError("Invalid view $child, the app:targetBottomNavigation has to be n ListenableBottomNavigationView")
for (i in 0 until child.childCount)
val subView = child.getChildAt(i)
if (subView is BottomNavigationMenuView) target = subView
elevation = child.elevation
child.addOnNavigationItemSelectedListener updateRectByIndex(it, true)
post updateRectByIndex(index, false)
private fun attachedError(message: String)
Log.e("BNVIndicator", message)
override fun onDetachedFromWindow()
super.onDetachedFromWindow()
target = null
override fun onDraw(canvas: Canvas)
super.onDraw(canvas)
canvas.clipRect(rect)
// canvas.drawCircle(size,size,size, drawPaint)
backgroundDrawable.draw(canvas)
private fun updateRectByIndex(index: Int, animated: Boolean)
this.index = index
target?.apply
if (childCount < 1 || index >= childCount) return
val reference = getChildAt(index)
val start = reference.left + left
val end = reference.right + left
backgroundDrawable.setBounds(left, top, right, bottom)
val newRect = Rect(start, 0, end, height)
if (animated) startUpdateRectAnimation(newRect) else updateRect(newRect)
private fun startUpdateRectAnimation(rect: Rect)
animator?.cancel()
animator = AnimatorSet().also
it.playTogether(
ofInt(this, "rectLeft", this.rect.left, rect.left),
ofInt(this, "rectRight", this.rect.right, rect.right),
ofInt(this, "rectTop", this.rect.top, rect.top),
ofInt(this, "rectBottom", this.rect.bottom, rect.bottom)
)
it.interpolator = FastOutSlowInInterpolator()
it.duration = resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
it.start()
private fun updateRect(rect: Rect)
this.rect = rect
postInvalidate()
@Keep
fun setRectLeft(left: Int) = updateRect(rect.apply this.left = left )
@Keep
fun setRectRight(right: Int) = updateRect(rect.apply this.right = right )
@Keep
fun setRectTop(top: Int) = updateRect(rect.apply this.top = top )
@Keep
fun setRectBottom(bottom: Int) = updateRect(rect.apply this.bottom = bottom )
将一些属性放入attrs.xml
:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BottomNavigationViewIndicator">
<attr name="targetBottomNavigation" format="reference"/>
<attr name="clippableBackground" format="reference|color"/>
</declare-styleable>
</resources>
创建一个在指示器移动时将被剪裁的背景可绘制对象: my_orange_background.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:
android:
android:viewportWidth="360"
android:viewportHeight="4">
<path
android:fillType="nonZero"
android:pathData="M0,0h359.899v3.933H0z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="360"
android:endY="2"
android:startX="0"
android:startY="2"
android:type="linear">
<item
android:color="@color/orange"
android:offset="0" />
<item
android:color="@color/orange"
android:offset="0.55" />
</gradient>
</aapt:attr>
</path>
</vector>
创建您的活动/片段布局:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_
android:layout_>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager"
android:layout_
android:layout_
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toTopOf="@+id/bottom_nav"
app:layout_constraintTop_toTopOf="parent" />
<com.example.app.view.CustomBottomNavigationView
android:id="@+id/bottom_nav"
android:layout_
android:layout_
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?attr/colorSurface"
app:labelVisibilityMode="unlabeled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/menu_bottom_nav" />
<com.example.app.view.BottomNavigationViewIndicator
android:layout_
android:layout_
app:clippableBackground="@drawable/my_orange_background"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/bottom_nav"
app:targetBottomNavigation="@+id/bottom_nav" />
</androidx.constraintlayout.widget.ConstraintLayout>
来源:Medium articleGitHub project
【讨论】:
你真的不需要多个,不过......你可以将监听器包装在另一个监听器中并调用它。【参考方案4】:这是一种不好的做法,但如果您希望这样做,您可以采用 Tab 布局。
这里是代码
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_
android:layout_>
<android.support.design.widget.AppBarLayout
android:layout_
android:layout_
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_
android:layout_
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>
<RelativeLayout
android:layout_
android:layout_>
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_
android:layout_
android:layout_above="@+id/tabs"
/>
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_
android:layout_
android:layout_alignParentBottom="true"
app:tabBackground="@color/colorPrimary"
app:tabIndicatorColor="@android:color/white"
app:tabMode="fixed"
app:tabGravity="fill"/>
</RelativeLayout>
</android.support.design.widget.CoordinatorLayout>
【讨论】:
究竟为什么它是“不好的做法”? 推荐不使用底部Tab布局的人数我们可以使用bottomNavigationView以上是关于如何在 BottomNavigationView 中设置指标? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
如何在 BottomNavigationView 中动态隐藏菜单项?
如何禁用 BottomNavigationView 移位模式?
如何在 BottomNavigationView 中设置指标? [复制]
如何在没有 ActionBar 的 AndroidX 中设置 BottomNavigationView