[Android Jetpack] Use custom View in Navigation

Insert picture description hereNavigation is one of the important components in Jetpack, used to design and organize App page jumps. Since the official recommendation is to use Framgent to carry the page implementation, the first thing that comes to mind when it comes to Navigation is Fragment. But in fact, Navigation also supports other types of page implementations, such as custom views. This article will introduce the use of custom View in Navigation.

Before the formal introduction, let’s review the basic use of Navigation:

Basic structure of Navigation


The use of Navigation mainly involves the following objects

  • Graph
    uses XML to design the APP page (Destination) and the jump path between each page. Android Studio provides an editor to edit Graph
  • NavHost
    NavHost is a container used to host all nodes in the graph. Navigation provides a default implementation of NavHost for Fragment NavHostFragment. It can be understood that all Fragments in graph are ChildFragment. In the custom View scenario introduced today, there is also a need for NavHost implementation for custom View.
  • Controller
    Each NavHost has a Controller, which manages the jump between the nodes in the NavHost
  • Navigator
    Controller implements specific jumps by calling Navigator, and Navigator undertakes specific jump implementations

working principle


Each page in Navigation is a Destination, which can be Fragment, Activity or View. Link the source Destination with the destination Destination through Action, and jump from the current Destination to the destination Destination through Action.

Similar to Launch Activity, when the APP starts, a starting Destination needs to be defined as the home page display.

As mentioned earlier, NavHost has specific implementations for different Destinations, and NavController also has different acquisition methods according to the type of Destination, but they are all similar:

  • Fragment.findNavController ()
  • View.findNavController()
  • Activity.findNavController(viewId: Int)

After getting the Controller, you can navigate(int)jump through, for example

findNavController().navigate(R.id.action_first_view_to_second_view)
findNavController().navigate(R.id.second_view)

Implementation


The basic composition and working principle of Navigation were introduced above, and then we will enter the topic to realize Navigation based on custom View.

The following needs to be achieved:

  • ViewNavigator
  • Attributes for ViewNavigator
  • ViewDestination
  • NavigationHostView
  • Graph file

ViewNavigator

Navigation provides a way to customize Navigator: use @Navigator.Nameannotations.
We define a name for screen_viewthe Navigator, in the xml Graph can be defined by this name corresponding NavDestination.
NavDestination and Navigator are restricted by generics:Navigator<out NavDestination>

@Navigator.Name("screen_view")
class ViewNavigator(private val container: ViewGroup) : Navigator<ViewDestination>() {
    
    
    override fun navigate(...) {
    
    ...}
    override fun createDestination(): ViewDestination = ViewDestination(this)
    override fun popBackStack(): Boolean {
    
    ...}
}

Attributes for ViewNavigator

Define custom attributes used in Xml for Navigator layoutId,

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="ViewNavigator">
        <attr name="layoutId" format="reference" />
    </declare-styleable>

</resources>

ViewDestination

@NavDestination.ClassTypeAllow us to define our ownNavDestination

@NavDestination.ClassType(ViewGroup::class)
class ViewDestination(navigator: Navigator<out NavDestination>) : NavDestination(navigator) {
    
    

    @LayoutRes var layoutId: Int = 0

    override fun onInflate(context: Context, attrs: AttributeSet) {
    
    
        super.onInflate(context, attrs)
        context.resources.obtainAttributes(attrs, R.styleable.ViewNavigator).apply {
    
    
            layoutId = getResourceId(R.styleable.ViewNavigator_layoutId, 0)
            recycle()
        }
    }
}

In onInflate, receive and parse layoutIdthe value of the custom attribute

NavigationHostView

Define the implementation of NavHost NavigationHostFrame, which is mainly used to create Controller, register Navigator type and set Graph for it

class NavigationHostFrame(...) : FrameLayout(...), NavHost {
    
    
    private val navigationController = NavController(context)
    init {
    
    
        Navigation.setViewNavController(this, navigationController)
        navigationController.navigatorProvider.addNavigator(ViewNavigator(this))
        navigationController.setGraph(R.navigation.navigation)
    }
    override fun getNavController() = navigationController
}

Graph file

In the Graph file, by <screen_view/>defining NavDestination

<?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/main_navigation"
    app:startDestination="@id/first_screen_view"
    tools:ignore="UnusedNavigation">

    <screen_view
        android:id="@+id/first_screen_view"
        app:layoutId="@layout/screen_view_first"
        tools:layout="@layout/screen_view_first">

        <action
            android:id="@+id/action_first_screen_view_to_second_screen_view"
            app:destination="@id/second_screen_view"
            app:launchSingleTop="true"
            app:popUpTo="@+id/first_screen_view"
            app:popUpToInclusive="false" />

        <action
            android:id="@+id/action_first_screen_view_to_last_screen_view"
            app:destination="@id/last_screen_view"
            app:launchSingleTop="true"
            app:popUpTo="@+id/first_screen_view"
            app:popUpToInclusive="false" />

    </screen_view>

    <screen_view
        android:id="@+id/second_screen_view"
        app:layoutId="@layout/screen_view_second"
        tools:layout="@layout/screen_view_second">

        <action
            android:id="@+id/action_second_screen_view_to_screen_view_third"
            app:destination="@id/screen_view_third"
            app:launchSingleTop="true"
            app:popUpTo="@+id/main_navigation"
            app:popUpToInclusive="true" />

    </screen_view>

    <screen_view
        android:id="@+id/last_screen_view"
        app:layoutId="@layout/screen_view_last"
        tools:layout="@layout/screen_view_last" />

    <screen_view
        android:id="@+id/screen_view_third"
        app:layoutId="@layout/screen_view_third"
        tools:layout="@layout/screen_view_third" />

</navigation>

Open the Navigation editor of Android Studio and see the result after Xml:
Insert picture description here

Setup in Activity

Finally, use this NavigationHostView as a container in the layout of the Activity, and associate NavController with NavHost in the code

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.my.sample.navigation.NavigationHostView
        android:id="@+id/main_navigation_host"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
    
    
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    navController = Navigation.findNavController(mainNavigationHost)
    Navigation.setViewNavController(mainNavigationHost, navController)
}

Call NavController in onBackPressed` to let each NavDestination support BackPress

override fun onSupportNavigateUp(): Boolean = navController.navigateUp()
override fun onBackPressed() {
    
    
      if (!navController.popBackStack()) {
    
    
          super.onBackPressed()
      }
}

Conclusion


Navigation based on Fragment provides an out-of-the-box implementation, and at the same time reserves an extensible interface through annotations, which is convenient for developers to customize the implementation, and even enjoy the traversal brought by the Android Studio editor.

In the early days of Fragment's instability, many alternatives to Fragment appeared for UI segmentation. If these frameworks are still used in your project, then you can now happily adapt Navigation for them~

Guess you like

Origin blog.csdn.net/vitaviva/article/details/109281582