上一片文章用的是BottomNavigationView+Menu+Fragment,但是可能有时候需求不一样,menu的样式不太够,所以需要自定义View来实现。
Android 底部导航栏(一、BottomNavigationView+Menu+Fragment)_&岁月不待人&的博客-CSDN博客_android 底部导航栏
自定义View:实现思路是自定义一个XMl布局,放五个切换的Item,根据需求的样式去绘制。最后加上Fragment的切换,点击监听等等,坏处的话就是自己写的,可能没有封装好的那么完善,很多方法,状态需要自己去写。
直接上代码吧!首先是XML布局代码:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/act_main_bottom_nav_ll"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#fff"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="5dp"
android:paddingBottom="5dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent">
<LinearLayout
android:id="@+id/bottom_bar_home"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/bottom_bar_tools"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="@+id/bottom_bar_home_icon"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_marginBottom="4dp"
android:contentDescription="@null"
android:src="@drawable/gongwenbao" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:text="@string/home"
android:textColor="@color/black"
android:textSize="8sp"
tools:ignore="SmallSp" />
</LinearLayout>
<LinearLayout
android:id="@+id/bottom_bar_tools"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintEnd_toStartOf="@id/bottom_bar_play"
app:layout_constraintStart_toEndOf="@id/bottom_bar_home"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="@+id/bottom_bar_tools_icon"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_marginBottom="4dp"
android:contentDescription="@null"
android:src="@drawable/zixun" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:text="@string/tools"
android:textColor="@color/black"
android:textSize="8sp"
tools:ignore="SmallSp" />
</LinearLayout>
<LinearLayout
android:id="@+id/bottom_bar_play"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@id/bottom_bar_tools"
app:layout_constraintEnd_toStartOf="@id/bottom_bar_relax"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="@+id/bottom_bar_play_icon"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_marginBottom="4dp"
android:contentDescription="@null"
android:src="@drawable/youxi" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:text="@string/play"
android:textColor="@color/black"
android:textSize="8sp"
tools:ignore="SmallSp" />
</LinearLayout>
<LinearLayout
android:id="@+id/bottom_bar_relax"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@id/bottom_bar_play"
app:layout_constraintEnd_toStartOf="@id/bottom_bar_mine"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_marginBottom="4dp"
android:contentDescription="@null"
android:src="@drawable/yundong" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:text="@string/video"
android:textColor="@color/black"
android:textSize="8sp"
tools:ignore="SmallSp" />
</LinearLayout>
<LinearLayout
android:id="@+id/bottom_bar_mine"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@id/bottom_bar_relax"
app:layout_constraintEnd_toEndOf="parent"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="@+id/bottom_bar_mine_icon"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_marginBottom="4dp"
android:contentDescription="@null"
android:src="@drawable/yonghu" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:text="@string/mine"
android:textColor="@color/black"
android:textSize="8sp"
tools:ignore="SmallSp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
展示出来的效果是:
就是写自定义View:
主要就是绑定布局,为每一个Item添加点击事件,这儿我只做了简单的点击回调
package com.example.lxview.base.widget
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import androidx.constraintlayout.widget.ConstraintLayout
import com.example.lxview.R
class MainBottomNavBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) {
private lateinit var videoContainer: ConstraintLayout
private lateinit var navHome: LinearLayout
private lateinit var navTools: LinearLayout
private lateinit var navPlay: LinearLayout
private lateinit var navMe: LinearLayout
private lateinit var navRelax: LinearLayout
var listener:ResultClick?=null
init {
initView()
}
private fun initView() {
View.inflate(context, R.layout.main_act_bottom_nav_bar, this)
videoContainer = findViewById(R.id.act_main_bottom_nav_ll)
navHome = findViewById(R.id.bottom_bar_home)
navTools = findViewById(R.id.bottom_bar_tools)
navPlay = findViewById(R.id.bottom_bar_play)
navMe = findViewById(R.id.bottom_bar_mine)
navRelax = findViewById(R.id.bottom_bar_relax)
navHome.setOnClickListener {
listener?.click(0)
}
navTools.setOnClickListener {
listener?.click(1)
}
navPlay.setOnClickListener {
listener?.click(2)
}
navRelax.setOnClickListener {
listener?.click(3)
}
navMe.setOnClickListener {
listener?.click(4)
}
}
}
interface ResultClick{
fun click(int: Int)
}
第三步,在展示的Activity 的 xml文件里引用此布局,上面的FrameLayout用来放置fragment,底部放导航栏。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main_cl"
android:background="#f7f7f7"
xmlns:app="http://schemas.android.com/apk/res-auto">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/navigation"
/>
<com.example.lxview.base.widget.MainBottomNavBar
android:id="@+id/navigation"
android:layout_width="match_parent"
android:layout_height="50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/fragment_container"
app:layout_constraintStart_toStartOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
第4步:在Activity代码里加上逻辑,继承ResultClick接口,复写click方法,用replaceFragment实现每次点击item时切换fragment
package com.example.lxview
import android.view.MotionEvent
import android.widget.FrameLayout
import com.example.lxview.base.activity.BaseActivity
import com.example.lxview.base.fragment.BaseFragment
import com.example.lxview.base.widget.MainBottomNavBar
import com.example.lxview.home.fragment.*
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import com.example.lxview.base.widget.ResultClick
/**
* author: 李 祥
* date: 2022/3/31 1:57 下午
* description:
*/
class MainActivity : BaseActivity(),ResultClick{
override val contentId: Int
get() = R.layout.activity_main
private var indicatorView: MainBottomNavBar? = null
private lateinit var fragments: Array<BaseFragment>
private lateinit var curSelectId: String
private lateinit var homeFragment: HomeFragment
private lateinit var toolsFragment: ToolsFragment
private lateinit var relaxFragment: RelaxFragment
private lateinit var meFragment: MineFragment
private lateinit var playFragment: PlayFragment
private lateinit var container: FrameLayout
override fun initView() {
indicatorView = findViewById(R.id.navigation)
indicatorView?.listener = this
container = findViewById(R.id.fragment_container)
homeFragment = HomeFragment()
toolsFragment = ToolsFragment()
relaxFragment = RelaxFragment()
meFragment = MineFragment()
playFragment = PlayFragment()
curSelectId = homeFragment.id.toString()
fragments = arrayOf(homeFragment, toolsFragment, playFragment, relaxFragment, meFragment)
fragments.forEach {
addFragment(it,it.tag.toString())
hideFragment(it)
}
showFragment(fragments[0])
}
//添加Fragment到FragmentList中
private fun addFragment(fragment: Fragment, tag: String) {
val fragmentManager = supportFragmentManager
val transaction: FragmentTransaction = fragmentManager.beginTransaction()
transaction.add(R.id.fragment_container, fragment, tag)
transaction.commit()
}
// 清空fragmentList的所有Fragment,替换成新的Fragment,注意Fragment里面的坑
private fun replaceFragment(fragment: Fragment, tag: String) {
val fragmentManager = supportFragmentManager
val transaction: FragmentTransaction = fragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, fragment, tag)
transaction.commit()
}
//移除指定的Fragment
private fun removeFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
val transaction: FragmentTransaction = fragmentManager.beginTransaction()
transaction.remove(fragment)
transaction.commit()
}
//把Fragment设置成显示状态,但是并没有添加到FragmentList中
private fun showFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
val transaction: FragmentTransaction = fragmentManager.beginTransaction()
transaction.show(fragment)
transaction.commit()
}
//把Fragment设置成显示状态,但是并没有添加到FragmentList中
private fun hideFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
val transaction: FragmentTransaction = fragmentManager.beginTransaction()
transaction.hide(fragment)
transaction.commit()
}
// 效果和show相近,创建视图,添加到containerid指定的Added列表,FragmentList依然保留,但是会引起生命周期的变化
private fun attachFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
val transaction: FragmentTransaction = fragmentManager.beginTransaction()
transaction.attach(fragment)
transaction.commit()
}
// 效果和hide相近,清除视图,从containerid指定的Added列表移除,FragmentList依然保留,但是会引起生命周期的变化
private fun detachFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
val transaction: FragmentTransaction = fragmentManager.beginTransaction()
transaction.detach(fragment)
transaction.commit()
}
override fun click(int: Int) {//实现fragment切换
replaceFragment(fragments[int],fragments[int].tag.toString())
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
if (ev?.action ==MotionEvent.BUTTON_BACK){
application.onTerminate()
}
return super.dispatchTouchEvent(ev)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event?.action ==MotionEvent.BUTTON_BACK){
application.onTerminate()
}
return super.onTouchEvent(event)
}
}
总结:我这上面只实现了简单的点击切换,用自定义View来实现底部导航栏,很自由,可以实现一些稀奇古怪的需求,但是很多方法的回调,切换时的动画,就需要自己去实现,可能会相对复杂一些。所以若是普通的导航栏,还是用Android自带的组件去实现比较好!
eg:使用replaceFragment,会清空掉当前的fragmnet列表,每次都是重新创建fragment,所以在一些界面的状态保存上就会有问题,所以也可以使用hide和show方法来进行点击切换,且不会销毁掉fragment