我想在bottomNavigationView上设置指示器。 如何设置指示器?
我没有找到指标的任何例子。
<android.support.design.widget.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@drawable/bottom_navigation_bg"
app:labelVisibilityMode="unlabeled"
app:menu="@menu/bottom_menu_main"/>
答案 0 :(得分:3)
我以前的答案提供了很大的灵活性,并且可以在选定状态之间实现良好的动画效果,但是涉及的代码量却相对较大。一个没有动画的简单解决方案是使用itemBackground
的{{1}}属性:
BottomNavigationView
bottom_nav_tab_background
<?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_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemBackground="@drawable/bottom_nav_tab_background"
android:layout_gravity="bottom"
app:menu="@menu/menu_bottom_nav" />
</LinearLayout>
答案 1 :(得分:0)
这是一种不好的做法,但是如果您希望这样做,可以采用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_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/tabs"
/>
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
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>
答案 2 :(得分:0)
要使此工作正常进行,您需要扩展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:width="24dp"
android:height="24dp"
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_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0dp"
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_width="match_parent"
android:layout_height="wrap_content"
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_width="0dp"
android:layout_height="4dp"
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>
答案 3 :(得分:0)
我在这里鞭打了一个指标,以使其移动。
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)
}
}
}