Android 自定义View之底部导航栏
文章目录
- Android 自定义View之底部导航栏
- 概述
- 代码
- 定义TabIndex
- 定义Tab
- 定义TabView
- 定义NavigationBar
- FragmentSwitchHelper管理类
- 使用
- 源码下载
Android 自定义View之底部导航栏
概述
封装一个通用的底部导航栏控件。
代码
定义TabIndex
@Retention(AnnotationRetention.SOURCE)
@IntDef(ONE_INDEX, TWO_INDEX, THREE_INDEX, FOUR_INDEX)
annotation class TabIndex {
companion object {
const val ONE_INDEX = 0
const val TWO_INDEX = 1
const val THREE_INDEX = 2
const val FOUR_INDEX = 3
}
}
定义Tab
data class Tab(
@TabIndex val index: Int,
val label: String,
@DrawableRes val tabIconDefault: Int,
@DrawableRes val tabIconSelected: Int,
@ColorRes val tabTextColorDefault: Int = R.color.tab_unselect_color,
@ColorRes val tabTextColorSelected: Int = R.color.tab_selected_color
)
定义TabView
class TabView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
private val paddingVertical = context.resources.getDimension(R.dimen.tab_padding_vertical).toInt()
private val iconSize = context.resources.getDimension(R.dimen.tab_icon_size).toInt()
private val fontSize = context.resources.getDimension(R.dimen.tab_text_size)
init {
layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT).apply {
weight = 1F
orientation = VERTICAL
}
gravity = Gravity.CENTER
setPadding(0, paddingVertical, 0, paddingVertical)
}
fun setData(tab: Tab) {
removeAllViews()
addViews(tab)
}
private fun addViews(tab: Tab) {
addView(createIcon(tab.tabIconDefault, tab.tabIconSelected))
addView(createText(tab.label, tab.tabTextColorDefault, tab.tabTextColorSelected))
}
private fun createIcon(
@DrawableRes tabIconDefault: Int,
@DrawableRes tabIconSelected: Int
): ImageView {
val drawable = StateListDrawable().apply {
addState(
intArrayOf(android.R.attr.state_selected),
ContextCompat.getDrawable(context, tabIconSelected)
)
addState(StateSet.NOTHING, ContextCompat.getDrawable(context, tabIconDefault))
}
return ImageView(context).apply {
layoutParams = LayoutParams(iconSize, iconSize)
setImageDrawable(drawable)
isSelected = false
}
}
private fun createText(
text: String,
@ColorRes textColorDefault: Int,
@ColorRes textColorSelected: Int
): TextView {
val states = arrayOf(
intArrayOf(android.R.attr.state_selected),
intArrayOf()
)
val colors = intArrayOf(
ContextCompat.getColor(context, textColorSelected),
ContextCompat.getColor(context, textColorDefault)
)
val colorStateList = ColorStateList(states, colors)
return TextView(context).apply {
layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
setText(text)
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize)
setTextColor(colorStateList)
isSelected = false
}
}
fun selected(isSelected: Boolean) {
children.forEach {
it.isSelected = isSelected
}
}
}
定义NavigationBar
class NavigationBar @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr), View.OnClickListener {
private val lineView by lazy { createLine() }
private val containerView by lazy { createContainer() }
private var onItemSelectedListener: ((Int) -> Unit)? = null
private var onItemReselectListener: ((Int) -> Unit)? = null
private var currentIndex: Int = ONE_INDEX
init {
addView(containerView, 0)
addView(lineView, 1)
}
fun setData(tabs: List<Tab>, defaultIndex: Int = ONE_INDEX) {
containerView.removeAllViews()
addViews(tabs)
currentIndex = defaultIndex
(containerView.getChildAt(currentIndex) as TabView).selected(true)
}
private fun addViews(tabs: List<Tab>) {
tabs.let {
for (tab in it) {
containerView.addView(createTabView(tab))
}
}
}
private fun createTabView(tab: Tab): TabView {
return TabView(context).apply {
tag = tab.index
setData(tab)
setOnClickListener(this@NavigationBar)
}
}
private fun createContainer(): LinearLayout {
return LinearLayout(context).apply {
layoutParams =
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
orientation = HORIZONTAL
}
gravity = Gravity.CENTER
}
}
private fun createLine(): View {
return View(context).apply {
setBackgroundColor(ContextCompat.getColor(context, R.color.tab_line))
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, 1.dp, Gravity.TOP
)
}
}
override fun onClick(view: View) {
if (view is TabView) {
clickTabView(view)
}
}
private var lock = false
private fun clickTabView(view: View) {
if (lock) {
return
}
lock = true
val selectIndex = view.tag as Int
if (selectIndex == currentIndex) {
onItemReselectListener?.invoke(selectIndex)
} else {
(containerView.getChildAt(currentIndex) as TabView).selected(false)
(containerView.getChildAt(selectIndex) as TabView).selected(true)
onItemSelectedListener?.invoke(selectIndex)
currentIndex = selectIndex
}
lock = false
}
fun setOnItemSelectedListener(onItemSelectedListener: ((Int) -> Unit)) {
this.onItemSelectedListener = onItemSelectedListener
}
fun setOnItemReselectListener(onItemReselectListener: ((Int) -> Unit)) {
this.onItemReselectListener = onItemReselectListener
}
}
FragmentSwitchHelper管理类
class FragmentSwitchHelper(
private val fragmentManager: FragmentManager,
@IdRes private val containerId: Int
) {
private var currentFragment: Fragment? = null
private var currentIndex: Int? = null
fun switchTo(@TabIndex index: Int) {
if (currentIndex == index) {
return
}
val transaction = fragmentManager.beginTransaction()
currentFragment?.let {
transaction.hide(it)
}
val fragment = Factory.getOrCreateFragment(index, fragmentManager)
if (fragment.isAdded) {
transaction.show(fragment)
} else {
transaction.add(
containerId,
fragment,
index.toString()
)
}
transaction.commit()
currentIndex = index
currentFragment = fragment
}
fun getCurrentIndex() = currentIndex
fun getCurrentFragment() = currentFragment
object Factory {
fun getOrCreateFragment(@TabIndex index: Int, fragmentManager: FragmentManager) =
when (index) {
ONE_INDEX -> fragmentManager.findFragmentByTag(index.toString())
?: SimpleFragment.newInstance("ONE")
TWO_INDEX -> fragmentManager.findFragmentByTag(index.toString())
?: SimpleFragment.newInstance("TWO")
THREE_INDEX -> fragmentManager.findFragmentByTag(index.toString())
?: SimpleFragment.newInstance("THREE")
FOUR_INDEX -> fragmentManager.findFragmentByTag(index.toString())
?: SimpleFragment.newInstance("FOUR")
else -> throw IllegalStateException("非法参数")
}
}
}
使用
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".navigationbar.NavigationBarActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.example.widgets.navigationbar.NavigationBar
android:id="@+id/navigation_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
class NavigationBarActivity : BaseActivity() {
private lateinit var navigationBar: NavigationBar
private val fragmentHelper by lazy {
FragmentSwitchHelper(
supportFragmentManager,
R.id.fragment_container
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_navigation_bar)
navigationBar = findViewById(R.id.navigation_bar)
val tabs = listOf<Tab>(
Tab(
ONE_INDEX,
"ONE",
R.drawable.tab_home_unselect,
R.drawable.tab_home_selected
),
Tab(
TWO_INDEX,
"TWO",
R.drawable.tab_friends_unselect,
R.drawable.tab_friends_selected
),
Tab(
THREE_INDEX,
"THREE",
R.drawable.tab_find_unselect,
R.drawable.tab_find_selected
),
Tab(
FOUR_INDEX,
"FOUR",
R.drawable.tab_setting_unselect,
R.drawable.tab_setting_selected
)
)
navigationBar.setData(tabs)
navigationBar.setOnItemSelectedListener {
Log.e("TAG", "点击:$it")
fragmentHelper.switchTo(it)
}
navigationBar.setOnItemReselectListener {
Log.e("TAG", "重复点击:$it")
}
fragmentHelper.switchTo(ONE_INDEX)
}
}