再过两个月,我就30周岁了,开启人生的第四个十年。人生有几个十年,我已经度过了三个。我内心并未感觉自己三十岁,但每次听到同事们说出他们的年龄,心里暗暗比较比他们大了好几岁,才意识到他们真好,我曾经也是他们这样。自古三十而立,成家立业,我现在既没有成家,也不知道我这个写码的工作算不算立业,因为我还不知道过了30岁,还能写几年代码。但不管未来如何,当下好好写码,保持学习,未来也应该不会很差吧。
上面纯属扯淡,下面开始今天的正文。现在主流的app首页都是底部几个Tab,上面是Fragment展示内容,就像下图这样。实现这样的需求很简单,我想这应该是Android工程师必备的能力吧。虽然说很容易,没有什么技术难点,但我看到过一些人的实现并不是很完美,多多少少有点问题,在某些场景下会出现bug。
上面内容部分有两种方式实现:
下面的Tab也有两种方式实现:
上面的两种方式可以与下面两种方式任一配合实现。
布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=""xmlns:tools=""android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><androidx.fragment.app.FragmentContainerViewandroid:id="@+id/fragmentContainerView"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1" /><Viewandroid:layout_width="match_parent"android:layout_height="1px"android:background="#f2f2f2" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="56dp"android:background="@color/white"android:orientation="horizontal"android:paddingBottom="4dp"><RadioButtonandroid:id="@+id/radio1"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:background="@null"android:button="@null"android:checked="true"android:drawableTop="@drawable/nav_1"android:gravity="center"android:paddingTop="10dp"android:text="Android"android:textColor="@color/nav_color"android:textSize="12sp" /><FrameLayoutandroid:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"><RadioButtonandroid:id="@+id/radio2"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@null"android:button="@null"android:drawableTop="@drawable/nav_2"android:gravity="center"android:paddingTop="10dp"android:text="Kotlin"android:textColor="@color/nav_color"android:textSize="12sp" /><!--tvBadge:用于显示数量--><TextViewandroid:id="@+id/tvBadge"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_marginStart="14dp"android:layout_marginTop="4dp"android:background="@drawable/shape_red_badge"android:gravity="center"android:minWidth="16dp"android:paddingHorizontal="4dp"android:paddingVertical="1dp"android:textColor="@color/white"android:textSize="10sp"android:visibility="gone"tools:text="99+"tools:visibility="visible" /></FrameLayout><RadioButtonandroid:id="@+id/radio3"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:background="@null"android:button="@null"android:drawableTop="@drawable/nav_3"android:gravity="center"android:paddingTop="10dp"android:text="Jetpack"android:textColor="@color/nav_color"android:textSize="12sp" /><RadioButtonandroid:id="@+id/radio4"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:background="@null"android:button="@null"android:drawableTop="@drawable/nav_4"android:gravity="center"android:paddingTop="10dp"android:text="Java"android:textColor="@color/nav_color"android:textSize="12sp" /></LinearLayout>
</LinearLayout>
l(nav_2/3/4 类似)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android=""><item android:drawable="@drawable/icon_nav_1_checked" android:state_checked="true" /><item android:drawable="@drawable/icon_nav_1" />
</selector>
l
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android=""><item android:color="@color/blue" android:state_checked="true" /><item android:color="@color/black" />
</selector>
HomeActivity 代码:
class MainActivity : FragmentActivity, CompoundButton.OnCheckedChangeListener {//Fragment对应的Tag,用于在FragmentManager添加和寻找Fragmentcompanion object {const val TAG_1 = "tag1"const val TAG_2 = "tag2"const val TAG_3 = "tag3"const val TAG_4 = "tag4"const val TAG_CHECKED = "checkedTag"}//当前选中的tabprivate var checkedTag = TAG_1override fun onCreate(savedInstanceState: Bundle?) {Create(savedInstanceState)setContentView(R.layout.activity_main)if (savedInstanceState == null) {//如果不是销毁重建,才添加fragmentval fragment1 = wInstance()supportFragmentManager.beginTransaction().add(R.id.fragmentContainerView, fragment1 , TAG_1)mitNow()} else {//重建时,恢复checkedTagcheckedTag = String(TAG_CHECKED)!!}radio1.setOnCheckedChangeListener(this)radio2.setOnCheckedChangeListener(this)radio3.setOnCheckedChangeListener(this)radio4.setOnCheckedChangeListener(this)}//保存checkedTag,待重建时恢复override fun onSaveInstanceState(outState: Bundle) {SaveInstanceState(outState)outState.putString(TAG_CHECKED, checkedTag)}//Radio选中事件监听override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {if (isChecked) {changeRadioButton(buttonView.id)//将上一个选中的RadioButton置为非选中状态when (buttonView.id) {//根据当前选中的tab,切换显示的FragmentR.id.radio1 -> {changeFragment(TAG_1) { wInstance() }}R.id.radio2 -> {changeFragment(TAG_2) { wInstance() }}R.id.radio3 -> {changeFragment(TAG_3) { wInstance() }}R.id.radio4 -> {changeFragment(TAG_4) { wInstance() }}}}}//将id不是checkedId的RadioButton置为不选中状态private fun changeRadioButton(checkedId: Int) {if (checkedId != R.id.radio1) radio1.isChecked = falseif (checkedId != R.id.radio2) radio2.isChecked = falseif (checkedId != R.id.radio3) radio3.isChecked = falseif (checkedId != R.id.radio4) radio4.isChecked = false}//切换显示的Fragmentprivate fun changeFragment(fragmentTag: String, createFragment: () -> Fragment) {val fragmentManager = supportFragmentManagerval beginTransaction = fragmentManager.beginTransaction()//先找找之前有没有添加过该Fragmentvar fragment = fragmentManager.findFragmentByTag(fragmentTag)if (fragment== null) {//没有添加过fragment= createFragment()//创建Fragment,并添加,fragmentTag一定要传beginTransaction.add(R.id.fragmentContainerView, fragment, fragmentTag)}//找到当前显示的Fragment,并隐藏fragmentManager.findFragmentByTag(checkedTag)?.let {beginTransaction.hide(it)}//显示选中的Fragment,注意这里提交事务使用的commitNowbeginTransaction.show(fragment)mitNow()checkedTag = fragmentTag} }
需要注意几点:
以上几点在其他实现方式中同样生效。
布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=""xmlns:app=""android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><androidx.fragment.app.FragmentContainerViewandroid:id="@+id/fragmentContainerView"android:layout_height="0dp"android:layout_weight="1"/>&le.android.material.bottomnavigation.BottomNavigationViewandroid:id="@+id/bottomNavigationView"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="bottom"android:background="@color/white"app:itemIconTint="@color/nav_color"app:itemTextAppearanceActive="@style/itemTextAppearanceActiveStyle"app:itemTextAppearanceInactive="@style/itemTextAppearanceInactiveStyle"app:itemTextColor="@color/nav_color"app:labelVisibilityMode="labeled"app:menu="@menu/nav_menu" />
</LinearLayout>
app:itemIconTint 设置图标选中和未选中的颜色。
app:itemTextColor 设置文字选中和未选中的颜色。
itemTextAppearanceActive、itemTextAppearanceInactive设置文本选中和未选中的大小。
app:labelVisibilityMode设置文本显示模式,labeled为总是显示。
l
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android=""><itemandroid:id="@+id/item_1"android:icon="@drawable/nav_1"android:title="Android"/><itemandroid:id="@+id/item_2"android:icon="@drawable/nav_2"android:title="Kotlin"/><itemandroid:id="@+id/item_3"android:icon="@drawable/nav_3"android:title="Jetpack"/><itemandroid:id="@+id/item_4"android:icon="@drawable/nav_4"android:title="Java"/>
</menu>
HomeActivity 代码
class MainActivity : FragmentActivity(), BottomNavigationView.OnNavigationItemSelectedListener {//Fragment对应的Tag,用于在FragmentManager添加和寻找Fragmentcompanion object {const val TAG_1 = "tag1"const val TAG_2 = "tag2"const val TAG_3 = "tag3"const val TAG_4 = "tag4"const val TAG_CHECKED = "checkedTag"}//当前选中的tabprivate var checkedTag = TAG_1override fun onCreate(savedInstanceState: Bundle?) {Create(savedInstanceState)setContentView(R.layout.activity_main)if (savedInstanceState == null) {val fragment1 = Fragment1()supportFragmentManager.beginTransaction().add(R.id.fragmentContainerView, fragment1, TAG_1)mitNow()} else {checkedTag = String(TAG_CHECKED)!!}bottomNavigationView.setOnNavigationItemSelectedListener(this)}override fun onSaveInstanceState(outState: Bundle) {SaveInstanceState(outState)outState.putString(TAG_CHECKED, checkedTag)}//BottomNavigationView选中切换事件override fun onNavigationItemSelected(item: MenuItem): Boolean {if (supportFragmentManager.isStateSaved) return falsewhen (item.itemId) {R.id.item_1-> {changeFragment(TAG_1) { Fragment1() }}R.id.item_2 -> {changeFragment(TAG_2) { Fragment2() }}R.id.item_3 -> {changeFragment(TAG_3) { Fragment3() }}R.id.item_4 -> {changeFragment(TAG_4) { Fragment4() }}}return true}//切换显示的Fragmentprivate fun changeFragment(fragmentTag: String, createFragment: () -> Fragment) {if (fragmentTag == checkedTag) return //如果选中的tag是当前显示的tag不处理val fragmentManager = supportFragmentManagerval beginTransaction = fragmentManager.beginTransaction()var fragment = fragmentManager.findFragmentByTag(fragmentTag)if (fragment == null) {fragment = createFragment()beginTransaction.add(R.id.fragmentContainerView, fragment, fragmentTag)}fragmentManager.findFragmentByTag(this.checkedTag)?.let {beginTransaction.hide(it)}beginTransaction.show(fragment)mitNow()this.checkedTag = fragmentTag}//设置badge数量private fun setCartBadgeCount(count: Int) {if (0 == count) {//移除veBadge(R.id.nav2)} else {//显示OrCreateBadge(R.id.nav2).run {backgroundColor = Color(this@MainActivity, d)maxCharacterCount = 3//最多几位数,超过100,显示为99+number = count}}}
}
和第一种类似,只是监听事件不一样。另外BottomNavigationView 是支持显示和移除Badge的,显示时也可以不设置数量,这样就只显示一个小红点。
布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android=""xmlns:app=""android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><androidx.viewpager2.widget.ViewPager2android:id="@+id/viewPager2"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:orientation="horizontal" />&le.android.material.bottomnavigation.BottomNavigationViewandroid:id="@+id/bottomNavigationView"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="bottom"android:background="@color/white"app:itemIconTint="@color/nav_color"app:itemTextAppearanceActive="@style/itemTextAppearanceActiveStyle"app:itemTextAppearanceInactive="@style/itemTextAppearanceInactiveStyle"app:itemTextColor="@color/nav_color"app:labelVisibilityMode="labeled"app:menu="@menu/nav_menu" />
</LinearLayout>
android:orientation="horizontal"设置ViewPager2为水平滑动。
HomeActivity 代码:
class HomeActivity : FragmentActivity(), BottomNavigationView.OnNavigationItemSelectedListener {override fun onCreate(savedInstanceState: Bundle?) {Create(savedInstanceState)setContentView(R.layout.activity_home)bottomNavigationView.setOnNavigationItemSelectedListener(this)//页面切换监听isterOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {override fun onPageSelected(position: Int) {when (position) {0 -> {bottomNavigationView.selectedItemId = R.id.item_1}1 -> {bottomNavigationView.selectedItemId = R.id.item_2}2 -> {bottomNavigationView.selectedItemId = R.id.item_3}3 -> {bottomNavigationView.selectedItemId = R.id.item_4}}}})viewPager2.adapter = object : FragmentStateAdapter(this) {override fun createFragment(position: Int): Fragment {return when (position) {0 -> {Fragment1()}1 -> {Fragment2()}2 -> {Fragment3()}3 -> {Fragment4()}else -> {throw IllegalArgumentException()}}}override fun getItemCount(): Int {return 4}}}//BottomNavigationView选中切换事件override fun onNavigationItemSelected(item: MenuItem): Boolean {if (supportFragmentManager.isStateSaved) return falsewhen (item.itemId) {R.id.item_1-> {viewPager2.currentItem = 0}R.id.item_2-> {viewPager2.currentItem = 1}R.id.item_3-> {viewPager2.currentItem = 2}R.id.item_4 -> {viewPager2.currentItem = 3}}return true}
}
ViewPager2 是基于RecyclerView实现的,FragmentStateAdapter是RecyclerView.Apdater子类,有两个方法需要我们实现:createFragment 根据position返回一个Fragment;getItemCount 返回Fragment数量。Fragment相关的操作、选中状态保存等都不需要我们处理,FragmentStateAdapter已经处理好,并且当Fragment数量很多时,会回收不需要的Fragment。
这里还遗留了一些问题,比如Fragment里面也有水平滑动的View,会被ViewPager2拦截滑动不了。还有这里的写法似乎有些不妥。等我后面多用用这玩意再来补充。
本文发布于:2024-02-04 20:58:31,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170716272759547.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |