Simple understanding and use of Android navigation
1. Basic concepts
1.1. Background
The UI architecture pattern of nesting multiple Fragments in a single Activity has been accepted by most Android engineers. It is necessary to manage the switching between Fragments through FragmentManager and FragmentTransaction.
In Android, the switching and management of pages includes the management of the application Appbar, the animation switching of Fragments, and the parameter transfer between Fragments. Moreover, the pure code method is not particularly friendly to use, and the Appbar is very confusing in the process of management and use. Therefore, Jetpack provides a component called Navigation, which is designed to facilitate developers to manage Fragment pages and Appbar.
Compared with the previous Fragment management that needs to rely on FragmentManager
and FragmentTransaction
, using the Navigation component has the following advantages:
- Visual page navigation diagram, which is convenient for us to sort out the relationship between pages
- Complete navigation between pages through destination and action
- Easy to add page switching animation
- Type-safe parameter passing between pages
- Unified management of menu/bottom navigation/drawer blue menu navigation through the Navigation UI class
- Support deep link DeepLink
1.2. Meaning
- Navigation is an important part of the Android Jetpack component package. With the help of Single Activity and multiple Fragment fragments, it optimizes the overhead of Android Activity startup and simplifies data communication between Activities.
- Built-in support for jumping of ordinary Fragment, Activity and DialogFragment components, that is, all Dialog or PopupWindows are recommended to use DialogFragment to implement, which can cover all commonly used jumping scenarios and unify the management of the return stack.
- In addition, state storage and recovery can be achieved based on Fragment.
Suppose you are a traditional Activity-based developer, and now you want to migrate to the Navigation navigation architecture, you must have the following questions:
- All use Fragment? How to provide support for the startup type (singleTask, singleTop) of the original Activity jump?
- How to pass data between Fragments?
- How should onActivityForResult implement an "onFragmentResult" now?
2. Composition
2.1、Navigation graph
An XML resource containing all navigation related information
- xml file, including all managed Fragments, start target, page change target, and return target.
2.2、NavHostFragment
A special kind of Fragment, a container for hosting navigation content
<fragment
android:id="@+id/navHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNaHost="true"
app:navGraph="@navigation/nav_graph_main" />
2.3、NavController
Manage the objects of application navigation, and realize operations such as jumping between Fragments
- It is used to manage the navigation actions in NavHost, and is usually written in the click event to complete Fragment switching.
textView.setOnClickListener {
findNavController().navigate(R.id.action_Fragment1_to_Fragment2)
}
3. Basic use
3.1. Introducing dependencies
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.5.0'
3.2. Create a navigation view
First make sure that AndroidStudio is above 3.3
- 1. Right
res
click, clickNew -> Android Resource Directory
Resource type
2. Select from the drop-down list in the second line of the panel that appearsNavigation
, and then clickOK
- 3. There will be an additional
navigation
resource directory under the res directory, right-click on the directory, clickNew -> Navigation Resource File
, enter the name of the resource file to be created, name it herenav_graph
, click ok, and onenav_graph.xml
will be created.
3.3. Configure graph: add fragment
The newly created nav_graph.xml switches to the design mode, clicks the plus sign at 2, and selects Create new destination to quickly create a new Fragment. Here, the newly created, , and FragmentA
three FragmentB
fragments FragmentC
are
created, and the page can be configured manually The jump relationship between, click on a page, a small dot will appear on the right,
拖曳小圆点
pointing to the page to jump to, here set the jump relationship to FragmentA -> FragmentB -> FragmentC
.
Switch to the Code column, you can see that the following code is generated
<?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/nav_graph"
app:startDestination="@id/fragmentA">
<fragment
android:id="@+id/fragmentA"
android:name="com.example.testnavigation.FragmentA"
android:label="fragment_a"
tools:layout="@layout/fragment_a" >
<action
android:id="@+id/action_fragmentA_to_fragmentB2"
app:destination="@id/fragmentB" />
</fragment>
<fragment
android:id="@+id/fragmentB"
android:name="com.example.testnavigation.FragmentB"
android:label="fragment_b"
tools:layout="@layout/fragment_b" >
<action
android:id="@+id/action_fragmentB_to_fragmentC2"
app:destination="@id/fragmentC" />
</fragment>
<fragment
android:id="@+id/fragmentC"
android:name="com.example.testnavigation.FragmentC"
android:label="fragment_c"
tools:layout="@layout/fragment_c" />
</navigation>
navigation
It is the root tag, the first page started by default through startDestination configuration, here is FragmentAfragment
A tag represents a fragment. In fact, not only fragments but also activities can be configured here, and even customizedaction
The label defines the behavior of the page jump, which is equivalent to each line in the above figure. The destination defines the target page of the jump, and can also define the animation when jumping, etc.- When the action_FragmentA_to_FragmentB2 is called, it will change from FragmentA -> FragmentB
3.4, add NavHostFragment
Configure NavHostFragment in the layout file of MainActivity
<?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">
<fragment
android:id="@+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:name
Specify NavHostFragmentapp:navGraph
Specify the navigation view, that is, the built nav_graph.xmlapp:defaultNavHost=true
It means that the return key of the system can be intercepted. It can be understood that the function of the return key is implemented for the fragment by default. In this way, during the jump process of the fragment, when we press the return key, the fragment can return to the previous one just like the activity page up
Now that we run the program, we can run normally and see the page displayed by FragmentA. This is because NavHostFragment is configured in the layout file of MainActivity, and the navigation view is specified for NavHostFragment, and the default display is specified by startDestination in the navigation view. FragmentA.
3.5. Manage jumps between fragments through NavController
As mentioned above, the jump relationship between the three fragments is FragmentA -> FragmentB -> FragmentC, and FragmentA can already be displayed, so how to jump to FragmentB? This requires the use of NavController. Open the FragmentA class and give it to the
layout TextView defines a click event
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tv.setOnClickListener {
val navController = Navigation.findNavController(it)
navController.navigate(R.id.action_fragmentA_to_fragmentB2)
}
}
If it is found that the layout file cannot be imported automatically, the high probability is to add a plug-in to app.build‘kotlin-android-extensions’
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
}
It can be seen that it is very simple to manage the jump of fragments through navController. First, get the navController object, then call its navigate method, and pass in the id of the action defined in the previous nav_graph.
In the same way, set a click event for the TextView in FragmentB, so that it jumps to FragmentC when clicked
Run the program, FragmentA -> FragmentB -> FragmentC
, press the back button at this time, it will also return one by one page, if the previous app:defaultNavHost
setting is set to false
, after pressing the back button, you will find that you will return to the desktop directly.
3.5.1. Acquisition of NavController and its capabilities
In the above example, we can get the Fragment's subordinate NavController through the Fragment's extension method, and there are some overloaded methods:
// 根据 viewId 向上查找
NavController findNavController(Activity activity, int viewId)
// 根据 view 向上查找
NavController findNavController(View view)
In essence, findNavController is to find the NavController corresponding to the parent NavHostFragment closest to the specified view in the current view tree, and it is only for understanding at present.
Capabilities of NavController
For the application layer, we only deal with NavController in the entire Navigation framework, which provides common functions such as jumping, returning, and obtaining the return stack.
4. Pass parameters when jumping
4.1. Pass parameters through the navigate method with bundle parameters
Parameters can be passed to the destination by specifying the bundle parameter, for example:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tv.setOnClickListener {
val navController = Navigation.findNavController(it)
val bundle = Bundle()
bundle.putString("key", "test")
navController.navigate(R.id.action_fragmentA_to_fragmentB2, bundle)
}
}
In the destination Fragment, the bundle can be obtained directly through the getArguments() method.
super.onCreate(savedInstanceState)
val value = arguments?.getString("key")
...
}
4.2. Through the safeArgs plug-in
Compared with the traditional way of passing parameters, afe args has the advantage of safe parameter types, and with the official support of Google, it is very convenient to pass parameters.
1. 根build.gradle
Add the plugin under the project
buildscript {
ext.kotlin_version = "1.3.72"
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.4"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.0"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
2, and then app的build.gradle
referenced in'androidx.navigation.safeargs.kotlin'
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
id 'androidx.navigation.safeargs.kotlin'
}
3. After adding the plug-in, return to nav_graph, switch to design mode, and add the parameters that need to be received to the target page
Here you need to pass parameters when FragmentA jumps to FragmentB, so set parameters for FragmentB, click FragmentB, click + on the right side of Arguments on the right panel, enter the key value of the parameter, specify the parameter type and default value, and you can quickly add parameters
4. After adding,
rebuild
click on the project, safeArgs will automatically generate some codes, /build/generated/source/navigation-args
you can see in the directory that
safeArgs will generate corresponding classes according to the fragment tag in nav_graph,
- The action tag will be
“类名+Directions”
named after, - The argument tag will be
“类名+Args”
named after .
After using safeArgs, pass parameters like this
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tv.setOnClickListener {
val navController = Navigation.findNavController(it)
//通过safeArgs传递参数
val navDestination = FragmentADirections.actionFragmentAToFragmentB2("test")
navController.navigate(navDestination)
// 普通方式传递参数
// val bundle = Bundle()
// bundle.putString("key", "test")
// navController.navigate(R.id.action_fragmentA_to_fragmentB2, bundle)
}
}
The receiving parameters are like this
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
arguments?.let {
val value = FragmentBArgs.fromBundle(it).key
.......
}
.......
}
5. Animation
5.1. Action parameter setting animation
enterAnim: 跳转时的目标页面动画
exitAnim: 跳转时的原页面动画
popEnterAnim: 回退时的目标页面动画
popExitAnim:回退时的原页面动画
Add anim
slide_in_right.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="500"
android:fromXDelta="100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>
slide_out_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="500"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="-100%"
android:toYDelta="0%" />
</set>
slide_in_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="500"
android:fromXDelta="-100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>
slide_out_right.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="500"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="100%"
android:toYDelta="0%" />
</set>
Add to action: You can use alpha, scale, rotate, translate these effects according to different needs
<action
android:id="@+id/action_page1_to_action_page2"
app:destination="@id/page2Fragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
5.2. Shared elements
If two pages have similar elements, you can use this method to give the visual a sense of being carried over continuously.
Add the attribute transitionName to the elements shared by the two pages, and the value of the attribute must be the same.
fragment_one.xml
<ImageView
android:id="@+id/catImageView"
android:layout_width="200dp"
android:layout_height="200dp"
android:src="@mipmap/cat"
android:transitionName="catImage" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="cat"
android:transitionName="catText" />
fragment_two.xml
<ImageView
android:id="@+id/catImageView"
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@mipmap/cat"
android:transitionName="catImage" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="cat"
android:transitionName="catText" />
PageOneFragment.kt
Assign the transitionName of the xml element to NavController
val extras = FragmentNavigatorExtras(
catImageView to "catImage",
textView to "catText")
catImageView.setOnClickListener {
findNavController().navigate(
R.id.action_page1_to_action_page2,
null,
null,
extras)
}
PageTwoFragment.java
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setSharedElementEnterTransition( TransitionInflater.from(requireContext())
.inflateTransition(R.transition.shared_image));
}
shared_image.xml
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeBounds/>
<changeImageTransform/>
</transitionSet>
6. Frequently asked questions
1. How to provide support for the startup type of Fragment jump (singleTask, singleTop)?
Stack management: click destination, you can also see popUpTo
, popUpToInclusive
, launchSingleTop
1, in the right panel launchSingleTop
: if the stack already contains the specified interface to jump to, then only one will be kept, if not specified, two interfaces with the same interface will appear in the stack Fragment data can be understood as a singleTop similar to activity, that is, the stack top reuse mode, but it is a little different, for example, it will FragmentA@1 -> FragmentA@2,FragmentA@1
be destroyed, but if it is FragmentA@01>FragmentB@02>FragmentA@03
, FragmentA@1
it will not be destroyed.
2. popUpTo
(tag): Indicates jumping to a certain tag and popping the elements above the tag out of the stack.
3. popUpToInclusive
: If it is true, the tag will pop up, if it is false, it will not
Example: FragmentA -> FragmentB -> FragmentC -> FragmentA
FragmentC -> FragmentA
If the action is set to popUpTo=FragmentA ,popUpToInclusive=false
, then the elements in the stack will change to
the last, and you will find that you need to press the return key twice to return to the desktop
When setting popUpToInclusive=true
, the elements in the stack change so that
you only need to press the return key once to return to the desktop. From this, you can understand the meaning of popUpTo and popUpToInclusive.
2. How to communicate between Fragments?
The communication in Fragment can also be divided into two scenarios. Suppose there are two Fragments in the return stack, namely A and B.
- If A and B are in the same level submap, the interaction can be completed by creating ViewModels at the navigation map level at both ends.
For example, the current return stack is
NavGraphA -> NavDestinationB -> NavDestinationC -> NavDestinationD
1. If you want to realize the communication between C and D, you need to use Node B to create a ViewModel.
val vm by navGraphViewModels<TitleVm>(R.id.nav_destination_b)
R.id.home is the nearest public parent Graph of the two, and the communication between the two is valid until the parent Graph is destroyed.
2. If A and B are not in the same-level subgraph, they can use the closest public parent Graph to complete the communication.
For example, the current return stack is
NavGraphA -> NavDestinationB -> NavGraphC -> NavDestinationD
If you want to realize the communication between B and D, you need to use A node to create ViewModel.
val vm by navGraphViewModels<TitleVm>(R.id.home)
3. Redrawing of navigation fragment
3.1, Fragment life cycle
The Fragment life cycle given by the official before the Navigation appears is as follows: ( 注意onDestroyView之处
)
After the LIfecycle, Navigation and other components appear, the official Fragment life cycle diagram is as follows: (PS: Fragment Lifecycle && View Lifecycle)
Fragment under the Navigation framework The life cycle is divided into
Fragment Lifecycle
and View Lifecycle
, View Lifecycle is taken out separately, the reason is that Navigation
the non-stack top under the framework Fragment
will be destroyed View, that is A跳转到B页面: A会执行onDestroyView销毁其 View
(everything related to View, such as: Databinding, RecyclerView will be destroyed), But Fragment本身会存在
(the member variables of Fragment itself will not be destroyed)
The correct state flow under the Navigation framework should be similar to this:
A opens B through action, A goes
onResume
to onDestroyView
, B goes from onAttach
execution to onResume
, when B returns to A through the system return key, A starts from the above figure onCreateView流转到onResume
, during this process A's View undergoes destruction and reconstruction, View (binding instance) The object instance is different, but the instance of Fragment A is always the same.
In such a scenario, suppose A has a network news list RecyclerView, and RecyclerView is destroyed and rebuilt along with View. How to save the data in it to avoid refreshing the data every time you return to A (resulting in: last browsing data, location loss, additional network resource consumption), so the data items of Adapter in RecyclerView are very critical!
Common storage methods are:
- 1. Passed
Fragment
member variables - 2. ViewModel. In ViewModel's ViewModelScope, request network data through coroutines, save it in ViewModel (ViewModel life cycle runs through Fragment), save data through LiveData and ordinary variables, and restore data
onViewCreated
later
reference
1. Android navigation series - getting started
2. Android navigation series - advanced
3. Getting started with Navigation components
4. Android official architecture component Navigation: Fragment management framework that doesn't work
5. Navigation-02-Fragment life cycle
6. Fragment reconstruction phenomenon