background
The update of Android knowledge is a little more frequent than the background, the following is mainly to explain the use of the latest technology stack to build a most basic framework, incidentally practice hands, which encountered a little hole, I hereby record.
Set up BaseActivity
abstract class BaseActivity<T : ViewDataBinding> : AppCompatActivity { constructor() : super() private lateinit var mLoadingDialog: LoadingDialog var mBinding: T? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); StatusBar().fitSystemBar(this) mLoadingDialog = LoadingDialog(this) mBinding = DataBindingUtil.setContentView(this, getLayoutId()) initData(savedInstanceState) } override fun onDestroy() { super.onDestroy() mBinding? .unbind() } abstract fun initData(savedInstanceState: Bundle?) abstract fun getLayoutId(): Int * in * * * show loading/fun showLoading () {mLoadingDialog. ShowDialog (this, False)} / * * * dismiss loading dialog * / fun dismissLoading () {mLoadingDialog. DismissDialog ()} / * * / * * set the toolbar name protected fun setToolbarTitle(view: TextView, title: / / protected fun setToolbarBackIcon(view: ImageView, id: Int) { view.setBackgroundResource(id) } }Copy the code
Build the base class BaseFragment
package com.example.command.base import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.databinding.DataBindingUtil import androidx.databinding.ViewDataBinding import androidx.fragment.app.Fragment import com.example.command.R import com.example.command.databinding.BaseFragmentLayoutBinding import com.example.command.widget.LoadingDialog import com.kingja.loadsir.core.LoadService private const val TAG = "BaseFragment" abstract class BaseFragment<T : ViewDataBinding> : Fragment() { var mBinding: T? = null private lateinit var mContext: Context private lateinit var mLoadingDialog: LoadingDialog private lateinit var loadService: LoadService<Any> private lateinit var mBaseContainBinding: BaseFragmentLayoutBinding override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { mBaseContainBinding = DataBindingUtil.inflate(inflater, R.layout.base_fragment_layout, container, false) mBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false) mBaseContainBinding.baseContainer.addView(mBinding?.root) return mBaseContainBinding.root } override fun onAttach(context: Context) { super.onAttach(context) mContext = context } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) mLoadingDialog = LoadingDialog(view.context) initData() } abstract fun initData() override fun onDestroy() { super.onDestroy() mBinding?.unbind() } abstract fun getLayoutId(): Int/show load * * * * / private fun showLoading () {mLoadingDialog. ShowDialog (mContext, false) } /** * dismiss loading dialog */ private fun dismissLoading() { mLoadingDialog.dismissDialog() } private var Time: Long = 0 Private var oldMsg: String? = NULL /** * Only one MSG of the same type is displayed. */ fun showToast(msg: String) { if (msg ! = oldMsg) { Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show() time = System.currentTimeMillis() } else { if (System.currentTimeMillis() - time > 2000) { Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show() time = System.currentTimeMillis() } } oldMsg = msg }Copy the code
Write the MainActivity class
(1) the activity. The main. XML
<? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> </data> <LinearLayout android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:fitsSystemWindows="true" android:background="@color/color_ffffff"> <androidx.fragment.app.FragmentContainerView android:id="@+id/nav_host_container" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" app:defaultNavHost="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/nav_view" android:layout_width="match_parent" android:layout_height="wrap_content" app:menu="@menu/bottom_nav_view" /> </LinearLayout> </layout>Copy the code
Here the layout uses FragmentContainerView+BottomNavigationView, instead of the traditional FrameLayout+GroupBottom.
(2)menu
<? The XML version = "1.0" encoding = "utf-8"? > <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/navigation_home" android:icon="@drawable/ic_baseline_home_24" android:title="@string/title_home"/> <item android:id="@+id/navigation_dashboard" android:icon="@drawable/ic_baseline_call_split_24" android:title="@string/title_dashboard"/> <item android:id="@+id/navigation_personal" android:icon="@drawable/ic_baseline_person_outline_24" android:title="@string/title_notifications"/> </menu>Copy the code
(3) Create a Fragment file
(1) naviation_home. XML
<? The XML version = "1.0" encoding = "utf-8"? > <navigation 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/navigation_home" app:startDestination="@id/fragment_home"> <fragment android:id="@+id/fragment_home" android:name="com.example.home.HomeFragment" tools:layout="@layout/fragment_home"/> </navigation>Copy the code
Android :id=”@+id/ navigation_HOME “; android:id=”@+id/ navigation_HOME “; If the home Id is “navigation_HOME”, then the navigation Id should be “navigation_HOME”.
(2) HomeFragement
class HomeFragment : BaseFragment<FragmentHomeBinding>() {
override fun getLayoutId(): Int {
return R.layout.fragment_home
}
override fun initData() {
}
}
Copy the code
Other similar
(4) MainActivity
package com.example.study import android.os.Bundle import android.view.View import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import androidx.navigation.NavController import com.alibaba.android.arouter.facade.annotation.Route import com.example.command.base.BaseActivity import com.example.command.ktx.setupWithNavController import com.example.command.support.Constants import com.example.study.databinding.ActivityMainBinding private const val TAG = "MainActivity" class MainActivity : BaseActivity<ActivityMainBinding>() { private var currentNavController: LiveData<NavController>? = null override fun getLayoutId(): Int { return R.layout.activity_main } override fun initData(savedInstanceState: Bundle?) { if (savedInstanceState == null) { setupBottomNavigationBar() } } override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) setupBottomNavigationBar() } /** * Navigation bind BottomNavigationView */ private fun setupBottomNavigationBar() {val navGraphIds = listOf(R.navigation.naviation_home, R.navigation.navigation_dashboard,R.navigation.navigation_personal) val controller = mBinding?.navView?.setupWithNavController( navGraphIds = navGraphIds, fragmentManager = supportFragmentManager, containerId = R.id.nav_host_container, intent = intent ) controller?.observe(this, Observer { navController -> //setupActionBarWithNavController(navController) navController.addOnDestinationChangedListener { _, destination, _ -> run { val id = destination.id mBinding?.navView?.visibility = View.VISIBLE } } }) currentNavController = controller } override fun onSupportNavigateUp(): Boolean { return currentNavController?.value?.navigateUp() ?: false } }Copy the code
(4) custom BottomNavigationView setupWithNavController method
fun BottomNavigationView.setupWithNavController(
navGraphIds: List<Int>,
fragmentManager: FragmentManager,
containerId: Int,
intent: Intent
): LiveData<NavController> {
// Map of tags
val graphIdToTagMap = SparseArray<String>()
// Result. Mutable live data with the selected controlled
val selectedNavController = MutableLiveData<NavController>()
var firstFragmentGraphId = 0
// First create a NavHostFragment for each NavGraph ID
navGraphIds.forEachIndexed { index, navGraphId ->
val fragmentTag = getFragmentTag(index)
// Find or create the Navigation host fragment
val navHostFragment = obtainNavHostFragment(
fragmentManager,
fragmentTag,
navGraphId,
containerId
)
// Obtain its id
val graphId = navHostFragment.navController.graph.id
if (index == 0) {
firstFragmentGraphId = graphId
}
// Save to the map
graphIdToTagMap[graphId] = fragmentTag
// Attach or detach nav host fragment depending on whether it's the selected item.
Log.d(TAG, "setupWithNavController: $selectedItemId and $graphId")
Log.d(TAG, "setupWithNavController: $fragmentTag and $graphId")
if (this.selectedItemId == graphId) {
// Update livedata with the selected graph
selectedNavController.value = navHostFragment.navController
attachNavHostFragment(fragmentManager, navHostFragment, index == 0)
} else {
detachNavHostFragment(fragmentManager, navHostFragment)
}
}
// Now connect selecting an item with swapping Fragments
var selectedItemTag = graphIdToTagMap[this.selectedItemId]
val firstFragmentTag = graphIdToTagMap[firstFragmentGraphId]
var isOnFirstFragment = selectedItemTag == firstFragmentTag
// When a navigation item is selected
setOnNavigationItemSelectedListener { item ->
// Don't do anything if the state is state has already been saved.
if (fragmentManager.isStateSaved) {
Log.d(TAG, "setupWithNavController: isStateSaved ")
false
} else {
val newlySelectedItemTag = graphIdToTagMap[item.itemId]
if (selectedItemTag != newlySelectedItemTag) {
// Pop everything above the first fragment (the "fixed start destination")
fragmentManager.popBackStack(firstFragmentTag,
FragmentManager.POP_BACK_STACK_INCLUSIVE)
val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
as NavHostFragment
Log.d(TAG, "setupWithNavController: $firstFragmentTag and $newlySelectedItemTag")
// Exclude the first fragment tag because it's always in the back stack.
if (firstFragmentTag != newlySelectedItemTag) {
// Commit a transaction that cleans the back stack and adds the first fragment
// to it, creating the fixed started destination.
fragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.nav_default_enter_anim,
R.anim.nav_default_exit_anim,
R.anim.nav_default_pop_enter_anim,
R.anim.nav_default_pop_exit_anim)
.attach(selectedFragment)
.setPrimaryNavigationFragment(selectedFragment)
.apply {
// Detach all other Fragments
graphIdToTagMap.forEach { _, fragmentTagIter ->
if (fragmentTagIter != newlySelectedItemTag) {
detach(fragmentManager.findFragmentByTag(firstFragmentTag)!!)
}
}
}
.addToBackStack(firstFragmentTag)
.setReorderingAllowed(true)
.commit()
Log.d(TAG, "setupWithNavController: $firstFragmentTag")
}
selectedItemTag = newlySelectedItemTag
isOnFirstFragment = selectedItemTag == firstFragmentTag
selectedNavController.value = selectedFragment.navController
true
} else {
false
}
}
}
// Optional: on item reselected, pop back stack to the destination of the graph
setupItemReselected(graphIdToTagMap, fragmentManager)
// Handle deep link
setupDeepLinks(navGraphIds, fragmentManager, containerId, intent)
// Finally, ensure that we update our BottomNavigationView when the back stack changes
fragmentManager.addOnBackStackChangedListener {
if (!isOnFirstFragment && !fragmentManager.isOnBackStack(firstFragmentTag)) {
this.selectedItemId = firstFragmentGraphId
}
// Reset the graph if the currentDestination is not valid (happens when the back
// stack is popped after using the back button).
selectedNavController.value?.let { controller ->
if (controller.currentDestination == null) {
controller.navigate(controller.graph.id)
}
}
}
return selectedNavController
}
private fun BottomNavigationView.setupDeepLinks(
navGraphIds: List<Int>,
fragmentManager: FragmentManager,
containerId: Int,
intent: Intent
) {
navGraphIds.forEachIndexed { index, navGraphId ->
val fragmentTag = getFragmentTag(index)
// Find or create the Navigation host fragment
val navHostFragment = obtainNavHostFragment(
fragmentManager,
fragmentTag,
navGraphId,
containerId
)
// Handle Intent
if (navHostFragment.navController.handleDeepLink(intent)
&& selectedItemId != navHostFragment.navController.graph.id) {
this.selectedItemId = navHostFragment.navController.graph.id
}
}
}
private fun BottomNavigationView.setupItemReselected(
graphIdToTagMap: SparseArray<String>,
fragmentManager: FragmentManager
) {
setOnNavigationItemReselectedListener { item ->
val newlySelectedItemTag = graphIdToTagMap[item.itemId]
val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
as NavHostFragment
val navController = selectedFragment.navController
// Pop the back stack to the start destination of the current navController graph
navController.popBackStack(
navController.graph.startDestination, false
)
}
}
private fun detachNavHostFragment(
fragmentManager: FragmentManager,
navHostFragment: NavHostFragment
) {
fragmentManager.beginTransaction()
.detach(navHostFragment)
.commitNow()
}
private fun attachNavHostFragment(
fragmentManager: FragmentManager,
navHostFragment: NavHostFragment,
isPrimaryNavFragment: Boolean
) {
Log.d(TAG, "attachNavHostFragment: ")
fragmentManager.beginTransaction()
.attach(navHostFragment)
.apply {
if (isPrimaryNavFragment) {
setPrimaryNavigationFragment(navHostFragment)
}
}
.commitNow()
}
private fun obtainNavHostFragment(
fragmentManager: FragmentManager,
fragmentTag: String,
navGraphId: Int,
containerId: Int
): NavHostFragment {
// If the Nav Host fragment exists, return it
val existingFragment = fragmentManager.findFragmentByTag(fragmentTag) as NavHostFragment?
existingFragment?.let { return it }
Log.d(TAG, "obtainNavHostFragment existingFragment : $existingFragment ")
// Otherwise, create it and return it.
val navHostFragment = NavHostFragment.create(navGraphId)
fragmentManager.beginTransaction()
.add(containerId, navHostFragment, fragmentTag)
.commitNow()
return navHostFragment
}
private fun FragmentManager.isOnBackStack(backStackName: String): Boolean {
val backStackCount = backStackEntryCount
for (index in 0 until backStackCount) {
if (getBackStackEntryAt(index).name == backStackName) {
return true
}
}
return false
}
private fun getFragmentTag(index: Int) = "bottomNavigation#$index"
Copy the code
Three, the effect diagram
(1)
(2)
(3)