1 introduction
The Navigation component of Android Jetpack makes it easy to manage Navigation of fragments/activities.
Note: If you want to use the Navigation component in Android Studio, you must use Android Studio 3.3 or later.
The navigation component has three key parts
- NavGraph: A navigation graph that contains A set of pages and their jump relationships, such as A page jumping from PAGE A to page B, and from page B to page C
- NavHost: a blank container that can display navigation pages. The system implements a NavHostFragment by default
- NavController: An object that manages the navigation of the application and controls which pages should be displayed in the NavHost container
2 Easy to use
FirstFragment->SecondFragment; SecondFragment – > ThirdFragment; ThirdFragment – > FirstFragment; ThirdFragment->SecondFragment
Add latest dependencies:
implementation "Androidx. Navigation: navigation - fragments: 2.1.0." "
implementation "Androidx. Navigation: navigation - UI: 2.1.0." "
Copy the code
2.1 Creating a Navigation Diagram
- Right click on the RES folder
- Select New->Android Resource File
- On the first line, enter a File name such as nav_graph
- In the second line, select Navigation from the Resource Type drop down list and click OK.
After clicking OK, Android Studio will create a Navigation resource directory in the RES directory that contains the files we just created.
Double-click the file to open the file as shown below. We can use the graphical interface to edit it, or use the code to edit it. The lower right corner can be switched.
Click the plus sign (1) in the figure above to add the three fragments created just now. The page jump relationship can be directly connected manually. For example, connecting an arrow from FirstFragment to SecondFragment means jumping from FirstFragment to SecondFragment.
If a line is clicked, the attribute information of the line will be displayed on the far right of the figure. We can define it by ourselves, including the ID of the line, the destination to be navigated, and the animation for page switching. Animation system has several built-in, and we can also define tween drawing by ourselves. Which page to switch to when it pops up.
Switch to the code bar and you can see the resulting code as follows:
<?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_demo_1"
app:startDestination="@id/firstFragment">
<fragment
android:id="@+id/firstFragment"
android:name="com.chs.androiddailytext.jetpack.navigation.FirstFragment"
android:label="fragment_frist"
tools:layout="@layout/fragment_frist">
<action
android:id="@+id/action_firstFragment_to_secondFragment"
app:destination="@id/secondFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
</fragment>
<fragment
android:id="@+id/secondFragment"
android:name="com.chs.androiddailytext.jetpack.navigation.SecondFragment"
android:label="fragment_second"
tools:layout="@layout/fragment_second">
<action
android:id="@+id/action_secondFragment_to_thirdFragment"
app:destination="@id/thirdFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
</fragment>
<fragment
android:id="@+id/thirdFragment"
android:name="com.chs.androiddailytext.jetpack.navigation.ThirdFragment"
android:label="fragment_third"
tools:layout="@layout/fragment_third">
<action
android:id="@+id/action_thirdFragment_to_firstFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"
app:popUpTo="@id/firstFragment" />
<action
android:id="@+id/action_thirdFragment_to_secondFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"
app:popUpTo="@+id/secondFragment" />
<deepLink app:uri="www.chs.com/{userName}" />
</fragment>
</navigation>
Copy the code
- The root tag is Navigation, and it needs to have a property startDestination that represents the first interface to display by default, with FirstFragment set here
- Each fragment tag represents a fragment class. You can write not only the fragment tag, but also the activity/ Dialog tag. It represents the default ability of Naviagtion to navigate both the fragment and the activity. DialogFragment.
- Each action TAB is like each line in the image above, representing the destination when the switch is performed, the switch animation, and so on.
- The animation for the page switch is a simple tween animation. It is very simple, such as defining an animation that slides in from the right as follows
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="100%" android:toXDelta="0%"
android:fromYDelta="0%" android:toYDelta="0%"
android:duration="200"/>
</set>
Copy the code
2.2 Adding NavHost to the Activity
Create an Activity and add FragmentNavHost to its XML file
<?xml version="1.0" encoding="utf-8"? >
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<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:navGraph="@navigation/nav_graph" />
</FrameLayout>
Copy the code
- Android: Name is the implementation class of NavHost. Here is NavHostFragment
app:navGraph
The properties are the files we created earlier under the RES folderapp:defaultNavHost="true"
This means that we can block the system’s back button so that when we click the phone’s back button, we can return to the previous page as an activity.
2.3 Enabling Navigation
view.findViewById(R.id.button).setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString("title"."I was sent from the front.");
Navigation.findNavController(v).navigate(R.id.action_firstFragment_to_secondFragment,bundle);
});
Copy the code
Navigate is as simple as finding the NavController method with Navigation#findNavController and calling its navigate method.
- The first parameter is the ACTION ID.
- The second parameter, bundle, is used to pass parameters between the two interfaces without passing them. Pass in the target page
getArguments()
Method to retrieve the bundle and get data.
Instead of using bundles directly to transfer data, Google recommends using bundlesSafe ArgsTo pass the data, because it can ensure that the data is type safe.
To use afe Args, first add afe Args gradle plugin dependencies to your project’s build.gradle folder
classpath "Androidx. Navigation: navigation - safe - the args - gradle - plugin: 2.1.0." "
Copy the code
Then add the plugin to the build.gradle file in your app
apply plugin: "androidx.navigation.safeargs"// Kotlin uses the following method to introduce the apply plugin:"androidx.navigation.safeargs.kotlin"
Copy the code
Then add the argument tag to the nav_graph navigation file you created initially
<fragment
android:id="@+id/firstFragment"
android:name="com.chs.androiddailytext.jetpack.navigation.FirstFragment"
android:label="fragment_frist"
tools:layout="@layout/fragment_frist">
<action
android:id="@+id/action_firstFragment_to_secondFragment"
app:destination="@id/secondFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
<argument android:name="title"
android:defaultValue="i am title"
app:argType="string"/>
</fragment>
Copy the code
The argument TAB adds the parameter name, parameter type, and default value. The plugin will automatically generate several classes in \app\build\generated\source\navigation-args\debug as shown below
Began to use the following several class, first in FirstFragment directly through FirstFragmentArgs. Builder () method to create a bundle object
view.findViewById(R.id.button).setOnClickListener(v -> {
Bundle bundle = new FirstFragmentArgs.Builder().setTitle("I was sent from the front.").build().toBundle();
Navigation.findNavController(v).navigate(R.id.action_firstFragment_to_secondFragment,bundle);
});
Copy the code
Then use in SecondFragment FirstFragmentArgs. FromBundle method to receive the value
Bundle arguments = getArguments();
if(arguments! =null){
String title = FirstFragmentArgs.fromBundle(getArguments()).getTitle();
tvTitle.setText(title);
}
Copy the code
This will also normally receive the correct pass parameters, and is type-safe.
For passing parameters, since these fragments are in the same activity, we can also use ViewModel and LiveData from the Jetpack component library to share parameters. This not only ensures type-safety, but also keeps data from being lost when an activity is rebuilt due to screen rotation. ViewModel for Android Jetpack.
2.3 the Activity to jump
NavController navController = new NavController(this);
NavigatorProvider navigatorProvider = navController.getNavigatorProvider();
NavGraph navGraph = new NavGraph(new NavGraphNavigator(navigatorProvider));
ActivityNavigator navigator = navigatorProvider.getNavigator(ActivityNavigator.class);
ActivityNavigator.Destination destination = navigator.createDestination();
//id Can be any resource ID
destination.setId(R.id.bottom_nav_activity);
destination.setComponentName(new ComponentName(getApplication().getPackageName(),
"com.chs.androiddailytext.jetpack.botton_navigation.BottomNavActivity"));
navGraph.addDestination(destination);
navGraph.setStartDestination(destination.getId());
navController.setGraph(navGraph);
Copy the code
NavController does not perform the specific navigation operations directly, but hands them off to the subclasses of Navigator: ActivityNavigator, FragmentNavigator, DialogFragmentNavigator, You can even inherit a Navigator to make a different jump.
Here the jump Activity creates an ActivityNavigator and jumps to it using its full class name.
The jump to an Activity looks like a hassle, so just start an Activity and you’re done. That’s not gonna help.
Firstly, this part of the code can be packaged to make it easier. Secondly, the jump mode of Activity and Fragment is unified. Finally, in componential project development, if there is no dependency between different modules, when they want to jump to each other, they need to use the full class name of the Activity to jump. In the previous component-based development project, we might introduce Ali’s ARouter routing framework or other routing frameworks or write our own routing framework. Now we can also directly use Navigation, and it is very convenient to realize the jump between components with a little modification.
2.4 deepLink
DeepLink: A direct jump to a page in your app, such as from the notification bar to the details page.
Navigation can create two different types of deep links: display deep links and implicit deep links
PendingIntent uses PendingIntent to navigate to a specific page, such as notifications, shortcuts, and so on, such as the following by notification jump.
view.findViewById(R.id.button1).setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString("userName"."The sea");
// PendingIntent pendingIntent = Navigation.findNavController(v).createDeepLink().setArguments(bundle)
// .setDestination(R.id.thirdFragment)
// .createPendingIntent();
PendingIntent pendingIntent = new NavDeepLinkBuilder(requireContext())
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.thirdFragment)
.setArguments(bundle)
.createPendingIntent();
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(requireContext());
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
notificationManager.createNotificationChannel(new NotificationChannel(
"deepLink"."deepLinkName", NotificationManager.IMPORTANCE_HIGH
));
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(requireContext(), "deepLink")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("Test deepLink")
.setContentText("Ha ha ha.")
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
notificationManager.notify(10,builder.build());
});
Copy the code
You can use the Navigation. FindNavController (v). CreateDeepLink () or new NavDeepLinkBuilder (context) two ways to create a deep link PendingIntent.
Note that when the application is opened with a display deep link, the current stack is cleared and replaced with nodes on the current deep link. When a chart is nested (that is, a label with a label inside it) then each chart is added to the corresponding stack.
Implicit linking is when a user clicks on a link and is redirected to a page through a URI. To set up implicit links, start by adding a deepLink tag to a fragment in the nav_graph.xml file you created initially. It was added earlier in the nav_graph.xml file
<deepLink app:uri="www.chs.com/{userName}" />
Copy the code
The URI does not declare whether it is HTTP or HTTPS, so both match. Inside the braces are the parameters passed.
Then go to the manifest.xml file and add a
<activity android:name=".jetpack.navigation.NavigationActivity">
<nav-graph android:value="@navigation/nav_graph"/>
</activity>
Copy the code
During build, the Navigation component replaces the
element to match deep links. Apk path: app- > build – > outputs – > debug – > app-debug.apk
<activity android:name="com.chs.androiddailytext.jetpack.navigation.NavigationActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="http" /> <data android:scheme="https" /> <data android:host="www.chs.com" /> <data android:pathPrefix="/" /> </intent-filter> </activity>Copy the code
You can use ADB to test implicit deep linking by turning on command line input
adb shell am start -a android.intent.action.VIEW -d "http://www.chs.com/Divad"
Copy the code
In the pop-up window of the system, select use our application to open, you can jump to the corresponding page.
2.5 Bottom Navigation
You can also use Navigation to navigate the top app bar, side slide drawer, and bottom.
To complete one of the most commonly used Bottom navigation, Bottom navigation is very easy to implement because there is a ready-made template. Just right-click under a package, new-> Activity ->Bottom Navigaton Activity, and create an activity directly with the Bottom navigation.
However, we do not do so in the learning stage, I suggest that blank activities, little by little to add, go through the process is easier to get familiar with.
Create a BottomNavActivity and three fragments: OneFragment, TowFragment, and ThreeFragment.
- Go to the res->navigation folder and create a new navigation graph named nav_bottom_graph.xml. The fragments in this diagram have no relation to each other, so there is no need to write any action. It is very simple:
<?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_bottom_graph"
app:startDestination="@id/oneFragment">
<fragment
android:id="@+id/oneFragment"
android:name="com.chs.androiddailytext.jetpack.botton_navigation.OneFragment"
android:label="fragment_one"
tools:layout="@layout/fragment_one" />
<fragment
android:id="@+id/towFragment"
android:name="com.chs.androiddailytext.jetpack.botton_navigation.TowFragment"
android:label="fragment_tow"
tools:layout="@layout/fragment_tow" />
<fragment
android:id="@+id/threeFragment"
android:name="com.chs.androiddailytext.jetpack.botton_navigation.ThreeFragment"
android:label="fragment_three"
tools:layout="@layout/fragment_three" />
</navigation>
Copy the code
If an ActionBar is referenced, the content of the Android: Label attribute is displayed in the title bar as the title of the page.
- Create a menu, menu is our bottom menu of each child information
- Right click on the RES folder
- Select New->Android Resource File
- In the first line of File name enter a File name such as bottom_nav.xml
- In the second line, select Menu from the Resource Type drop-down list, and then click OK.
<?xml version="1.0" encoding="utf-8"? >
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/oneFragment"
android:icon="@drawable/ic_home"
android:contentDescription="Home page"
android:title="Home page" />
<item
android:id="@+id/towFragment"
android:icon="@drawable/ic_list"
android:contentDescription="Page 2"
android:title="Page 2" />
<item
android:id="@+id/threeFragment"
android:icon="@drawable/ic_feedback"
android:contentDescription="Three pages"
android:title="Three pages" />
</menu>
Copy the code
- Introduce the BottomNavigationView and NavHostFragment in the BottomNavActivity layout file
<?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"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/bottom_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_bottom_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="@menu/bottom_nav"/>
</LinearLayout>
Copy the code
The NavHostFragment associates the navigation graph with the App :navGraph property, and the BottomNavigationView associates the menu created earlier with the App: Menu property
- Finally, use it in the Activity
public class BottomNavActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bottom_navigation);
BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_nav);
// Navigation controller
NavController controller = Navigation.findNavController(this,R.id.bottom_fragment);
// The bottom navigation configuration
AppBarConfiguration configuration = new AppBarConfiguration.Builder(bottomNavigationView.getMenu()).build();
// Associate the controller with the bottom navigation configuration
NavigationUI.setupActionBarWithNavController(this,controller,configuration);
// associate the bottomNavigationView with the controllerNavigationUI.setupWithNavController(bottomNavigationView,controller); }}Copy the code
OK bottom navigation view set up, effect
3 Principle Analysis
Let’s take a look at the Navigation workflow based on the first hop example.
3.1 NavHostFragment# onCreate
Start your Activity with a layout file that contains a NavHostFragment, which is the host area of the page. Start with its onCreate method.
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Context context = requireContext();
mNavController = new NavHostController(context);
mNavController.setLifecycleOwner(this);
mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
// Set the default state - this will be updated whenever
// onPrimaryNavigationFragmentChanged() is calledmNavController.enableOnBackPressed( mIsPrimaryBeforeOnCreate ! =null && mIsPrimaryBeforeOnCreate);
mIsPrimaryBeforeOnCreate = null;
mNavController.setViewModelStore(getViewModelStore());
onCreateNavController(mNavController);
Bundle navState = null;
if(savedInstanceState ! =null) {
navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE);
if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
mDefaultNavHost = true;
getParentFragmentManager().beginTransaction()
.setPrimaryNavigationFragment(this)
.commit();
}
mGraphId = savedInstanceState.getInt(KEY_GRAPH_ID);
}
if(navState ! =null) {
// Navigation controller state overrides arguments
mNavController.restoreState(navState);
}
if(mGraphId ! =0) {
// Set from onInflate()
mNavController.setGraph(mGraphId);
} else {
// See if it was set by NavHostFragment.create()
final Bundle args = getArguments();
final intgraphId = args ! =null ? args.getInt(KEY_GRAPH_ID) : 0;
finalBundle startDestinationArgs = args ! =null
? args.getBundle(KEY_START_DESTINATION_ARGS)
: null;
if(graphId ! =0) { mNavController.setGraph(graphId, startDestinationArgs); }}}Copy the code
Create a NavHostController class associated with the Fragment lifecycle and set various properties. First look at the constructor of NavHostController
public NavHostController(@NonNull Context context) {
super(context);
}
public NavController(@NonNull Context context) {
mContext = context;
while (context instanceof ContextWrapper) {
if (context instanceof Activity) {
mActivity = (Activity) context;
break;
}
context = ((ContextWrapper) context).getBaseContext();
}
mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider));
mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));
}
Copy the code
Add two Navigators to NavigatorProvider: NavGraphNavigator and ActivityNavigator
- NavGraphNavigator: When we created NavGraph, we specified a default first page to display (startDestination) and the navigator was used to navigate to that page.
- ActivityNavigator: As the name suggests, used to navigate activities
Go back to the NavHostFragment onCreate method and you can see onCreateNavController(mNavController); methods
protected void onCreateNavController(@NonNull NavController navController) {
navController.getNavigatorProvider().addNavigator(
new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}
Copy the code
Here we add two more navigators to the NavigatorProvider:
- DialogFragmentNavigator: Used to navigate a DialogFragment
- FragmentNavigator: Used to navigate fragments
3.2 the Navigator
NavigatorProvider has a HashMap inside to store these navigators. NavGraphNavigator, ActivityNavigator, DialogFragmentNavigator, FragmentNavigator, these classes are used for navigation.
public abstract class Navigator<D extends NavDestination> {
@Retention(RUNTIME)
@Target({TYPE})
@SuppressWarnings("UnknownNullness") // TODO https://issuetracker.google.com/issues/112185120
public @interface Name {
String value(a);
}
@NonNull
public abstract D createDestination(a);
@Nullable
public abstract NavDestination navigate(@NonNull D destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras);
public abstract boolean popBackStack(a);
@Nullable
public Bundle onSaveState(a) {
return null;
}
public void onRestoreState(@NonNull Bundle savedState) {}
public interface Extras {}}Copy the code
- The generics of this class are subclasses of NavDestination, which is essentially a page at a time.
- Navigator subclasses need to add a Name annotation, such as @navigator.name (“activity”) in ActivityNavigator, FragmentNavigator adds @navigator. Name(“fragment”) to the FragmentNavigator, which is used as the key when stored in the HashMap of the incoming and incoming NavigatorProvider
- CreateDestination creates a Destination, that is, a page, and the abstract method is implemented by subclasses
- Navigate method, which navigates to the target page. The abstract method is implemented by subclasses
- OnSaveState, onRestoreState Save status, restore status
- Extras provides some additional behaviors, such as transitions
Navigate to the parent class by looking at the more common ActivityNavigator and FragmentNavigator, NavGraphNavigator, and to see which of the two abstract methods they implement: createDestination and Navigate
3.3 ActivityNavigator
ActivityNavigator# createDestination method creates a ActivityNavigator. The Destination object
public static class Destination extends NavDestination {
private Intent mIntent;
private String mDataPattern;
public Destination(@NonNull NavigatorProvider navigatorProvider) {
this(navigatorProvider.getNavigator(ActivityNavigator.class));
}
public Destination(@NonNull Navigator<? extends Destination> activityNavigator) {
super(activityNavigator);
}
@CallSuper
@Override
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
super.onInflate(context, attrs);
TypedArray a = context.getResources().obtainAttributes(attrs,
R.styleable.ActivityNavigator);
String targetPackage = a.getString(R.styleable.ActivityNavigator_targetPackage);
if(targetPackage ! =null) {
targetPackage = targetPackage.replace(NavInflater.APPLICATION_ID_PLACEHOLDER,
context.getPackageName());
}
setTargetPackage(targetPackage);
String className = a.getString(R.styleable.ActivityNavigator_android_name);
if(className ! =null) {
if (className.charAt(0) = ='. ') {
className = context.getPackageName() + className;
}
setComponentName(new ComponentName(context, className));
}
setAction(a.getString(R.styleable.ActivityNavigator_action));
String data = a.getString(R.styleable.ActivityNavigator_data);
if(data ! =null) {
setData(Uri.parse(data));
}
setDataPattern(a.getString(R.styleable.ActivityNavigator_dataPattern));
a.recycle();
}
@NonNull
public final Destination setIntent(@Nullable Intent intent) {
mIntent = intent;
return this;
}
@NonNull
public final Destination setTargetPackage(@Nullable String packageName) {
if (mIntent == null) {
mIntent = new Intent();
}
mIntent.setPackage(packageName);
return this;
}
@NonNull
public final Destination setComponentName(@Nullable ComponentName name) {
if (mIntent == null) {
mIntent = new Intent();
}
mIntent.setComponent(name);
return this;
}
@NonNull
public final Destination setAction(@Nullable String action) {
if (mIntent == null) {
mIntent = new Intent();
}
mIntent.setAction(action);
return this;
}
@NonNull
public final Destination setData(@Nullable Uri data) {
if (mIntent == null) {
mIntent = new Intent();
}
mIntent.setData(data);
return this;
}
@NonNull
public final Destination setDataPattern(@Nullable String dataPattern) {
mDataPattern = dataPattern;
return this; }... }Copy the code
The constructor saves the full class name of the current Navigator into a member variable of Destination’s parent class.
ActivityNavigator. Destination inside the class and there are many methods such as setAction associated with Intent, setData, etc., are used to create the Intent to Intent and set parameters.
ActivityNavigator#navigate
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
if (destination.getIntent() == null) {
throw new IllegalStateException("Destination " + destination.getId()
+ " does not have an Intent set.");
}
Intent intent = new Intent(destination.getIntent());
if(args ! =null) {
intent.putExtras(args);
String dataPattern = destination.getDataPattern();
if(! TextUtils.isEmpty(dataPattern)) {// Fill in the data pattern with the args to build a valid URI
StringBuffer data = new StringBuffer();
Pattern fillInPattern = Pattern.compile("\ \ {(. +?) \ \}");
Matcher matcher = fillInPattern.matcher(dataPattern);
while (matcher.find()) {
String argName = matcher.group(1);
if (args.containsKey(argName)) {
matcher.appendReplacement(data, "");
//noinspection ConstantConditions
data.append(Uri.encode(args.get(argName).toString()));
} else {
throw new IllegalArgumentException("Could not find " + argName + " in "
+ args + " to fill data pattern "+ dataPattern); } } matcher.appendTail(data); intent.setData(Uri.parse(data.toString())); }}if (navigatorExtras instanceof Extras) {
Extras extras = (Extras) navigatorExtras;
intent.addFlags(extras.getFlags());
}
if(! (mContextinstanceof Activity)) {
// If we're not launching from an Activity context we have to launch in a new task.
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
if(navOptions ! =null && navOptions.shouldLaunchSingleTop()) {
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
}
if(mHostActivity ! =null) {
final Intent hostIntent = mHostActivity.getIntent();
if(hostIntent ! =null) {
final int hostCurrentId = hostIntent.getIntExtra(EXTRA_NAV_CURRENT, 0);
if(hostCurrentId ! =0) { intent.putExtra(EXTRA_NAV_SOURCE, hostCurrentId); }}}final int destId = destination.getId();
intent.putExtra(EXTRA_NAV_CURRENT, destId);
if(navOptions ! =null) {
// For use in applyPopAnimationsToPendingTransition()
intent.putExtra(EXTRA_POP_ENTER_ANIM, navOptions.getPopEnterAnim());
intent.putExtra(EXTRA_POP_EXIT_ANIM, navOptions.getPopExitAnim());
}
if (navigatorExtras instanceof Extras) {
Extras extras = (Extras) navigatorExtras;
ActivityOptionsCompat activityOptions = extras.getActivityOptions();
if(activityOptions ! =null) {
ActivityCompat.startActivity(mContext, intent, activityOptions.toBundle());
} else{ mContext.startActivity(intent); }}else {
mContext.startActivity(intent);
}
if(navOptions ! =null&& mHostActivity ! =null) {
int enterAnim = navOptions.getEnterAnim();
int exitAnim = navOptions.getExitAnim();
if(enterAnim ! = -1|| exitAnim ! = -1) { enterAnim = enterAnim ! = -1 ? enterAnim : 0; exitAnim = exitAnim ! = -1 ? exitAnim : 0; mHostActivity.overridePendingTransition(enterAnim, exitAnim); }}return null;
}
Copy the code
To start an Activity, an Intent object is required. The navigate method for the ActivityNavigator retrieves the Intent object via Destination, sets parameters, and sets the mode for the Activity to start. Set the switch animation, etc., and finally start an Activity with the startActivity method.
3.4 FragmentNavigator
FragmentNavigator# createDestination created a FragmentNavigator. The Destination object
public static class Destination extends NavDestination {
private String mClassName;
public Destination(@NonNull NavigatorProvider navigatorProvider) {
this(navigatorProvider.getNavigator(FragmentNavigator.class));
}
public Destination(@NonNull Navigator<? extends Destination> fragmentNavigator) {
super(fragmentNavigator);
}
@CallSuper
@Override
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
super.onInflate(context, attrs);
TypedArray a = context.getResources().obtainAttributes(attrs,
R.styleable.FragmentNavigator);
String className = a.getString(R.styleable.FragmentNavigator_android_name);
if(className ! =null) {
setClassName(className);
}
a.recycle();
}
@NonNull
public final Destination setClassName(@NonNull String className) {
mClassName = className;
return this; }... }Copy the code
There is much less code here than in ActivityNavigator, mainly the method for setting a ClassName. Create a Fragment object based on the ClassName reflection.
FragmentNavigator# navigate method
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
if (mFragmentManager.isStateSaved()) {
Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
+ " saved its state");
return null;
}
String className = destination.getClassName();
if (className.charAt(0) = ='. ') {
className = mContext.getPackageName() + className;
}
final Fragment frag = instantiateFragment(mContext, mFragmentManager,
className, args);
frag.setArguments(args);
final FragmentTransaction ft = mFragmentManager.beginTransaction();
intenterAnim = navOptions ! =null ? navOptions.getEnterAnim() : -1;
intexitAnim = navOptions ! =null ? navOptions.getExitAnim() : -1;
intpopEnterAnim = navOptions ! =null ? navOptions.getPopEnterAnim() : -1;
intpopExitAnim = navOptions ! =null ? navOptions.getPopExitAnim() : -1;
if(enterAnim ! = -1|| exitAnim ! = -1|| popEnterAnim ! = -1|| popExitAnim ! = -1) { enterAnim = enterAnim ! = -1 ? enterAnim : 0; exitAnim = exitAnim ! = -1 ? exitAnim : 0; popEnterAnim = popEnterAnim ! = -1 ? popEnterAnim : 0; popExitAnim = popExitAnim ! = -1 ? popExitAnim : 0;
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
}
ft.replace(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
final @IdRes int destId = destination.getId();
final boolean initialNavigation = mBackStack.isEmpty();
// TODO Build first class singleTop behavior for fragments
final booleanisSingleTopReplacement = navOptions ! =null && !initialNavigation
&& navOptions.shouldLaunchSingleTop()
&& mBackStack.peekLast() == destId;
boolean isAdded;
if (initialNavigation) {
isAdded = true;
} else if (isSingleTopReplacement) {
// Single Top means we only want one instance on the back stack
if (mBackStack.size() > 1) {
// If the Fragment to be replaced is on the FragmentManager's
// back stack, a simple replace() isn't enough so we
// remove it from the back stack and put our replacement
// on the back stack in its place
mFragmentManager.popBackStack(
generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
}
isAdded = false;
} else {
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
isAdded = true;
}
if (navigatorExtras instanceof Extras) {
Extras extras = (Extras) navigatorExtras;
for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
}
}
ft.setReorderingAllowed(true);
ft.commit();
// The commit succeeded, update our view of the world
if (isAdded) {
mBackStack.add(destId);
return destination;
} else {
return null; }}Copy the code
InstantiateFragment method: instantiateFragment method: instantiateFragment method: instantiateFragment method: instantiateFragment Finally, use the FragmentTransaction replace method to place the fragment into the corresponding container.
The replace method is used here, and we know that the replace method recreates the fragment every time, so it’s not very friendly to create a bottom Navigation page with a Navigation and have the fragment rebuilt every time you click on the switch page. You can override the navigate method by inheriting the FragmentNavigator method and changing replace to hide and show
3.5 NavGraphNavigator
NavGraphNavigator#createDestination
public NavGraph createDestination(a) {
return new NavGraph(this);
}
public class NavGraph extends NavDestination implements 可迭代<NavDestination> {
final SparseArrayCompat<NavDestination> mNodes = newSparseArrayCompat<>(); . }Copy the code
You can see that the createDestination of NavGraphNavigator does not create a NavDestination object directly, but creates a NavGraph object, It has an internal collection, mNodes, that holds a set of NavDestination objects.
The NavGraph object was actually parsed and created from the original nav_graph.xml. The mNodes collection stores the nodes of nav_graph.xml, which are essentially pages.
So where is the nav_graph.xml file parsed? Mnavcontroller.setgraph (mGraphId) is the id of nav_graph.xml
public void setGraph(@NavigationRes int graphResId) {
setGraph(graphResId, null);
}
public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}
public void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) {
if(mGraph ! =null) {
// Pop everything from the old graph off the back stack
popBackStackInternal(mGraph.getId(), true);
}
mGraph = graph;
onGraphCreated(startDestinationArgs);
}
Copy the code
You can see that in the second overloaded method, a NavGraph object is created through the getNavInflater().inflate method, which is passed to the third overloaded method and assigned to the member variable mGraph, and the first page is displayed in the onGraphCreated method.
At this point we know that the nav_graph.xml file we originally created will eventually be parsed into a NavGraph object and then associated with the individual NavController. So even without that file, we can create a NavGraph object ourselves with the required parameters, since XML configuration is not very flexible.
How do you parse this? Look at a inflate method
public NavGraph inflate(@NavigationRes int graphResId) {
Resources res = mContext.getResources();
XmlResourceParser parser = res.getXml(graphResId);
finalAttributeSet attrs = Xml.asAttributeSet(parser); . String rootElement = parser.getName(); NavDestination destination = inflate(res, parser, attrs, graphResId);if(! (destinationinstanceof NavGraph)) {
throw new IllegalArgumentException("Root element <" + rootElement + ">"
+ " did not inflate into a NavGraph");
}
return(NavGraph) destination; .Copy the code
NavGraph is a subclass of NavDestination, creates a NavDestination object, determines if it is a NavGraph object, if it is a NavGraph object and returns it, if it is not, throws an exception.
private NavDestination inflate(@NonNull Resources res, @NonNull XmlResourceParser parser,
@NonNull AttributeSet attrs, int graphResId)
throws XmlPullParserException, IOException { Navigator<? > navigator = mNavigatorProvider.getNavigator(parser.getName());final NavDestination dest = navigator.createDestination();
dest.onInflate(mContext, attrs);
final int innerDepth = parser.getDepth() + 1;
int type;
int depth;
while((type = parser.next()) ! = XmlPullParser.END_DOCUMENT && ((depth = parser.getDepth()) >= innerDepth || type ! = XmlPullParser.END_TAG)) {if(type ! = XmlPullParser.START_TAG) {continue;
}
if (depth > innerDepth) {
continue;
}
final String name = parser.getName();
Argument, deepLink action...
if (TAG_ARGUMENT.equals(name)) {
inflateArgumentForDestination(res, dest, attrs, graphResId);
} else if (TAG_DEEP_LINK.equals(name)) {
inflateDeepLink(res, dest, attrs);
} else if (TAG_ACTION.equals(name)) {
inflateAction(res, dest, attrs, parser, graphResId);
} else if (TAG_INCLUDE.equals(name) && dest instanceof NavGraph) {
final TypedArray a = res.obtainAttributes(attrs, R.styleable.NavInclude);
final int id = a.getResourceId(R.styleable.NavInclude_graph, 0);
((NavGraph) dest).addDestination(inflate(id));
a.recycle();
} else if (dest instanceof NavGraph) {
((NavGraph) dest).addDestination(inflate(res, parser, attrs, graphResId));
}
return dest;
}
Copy the code
First parse the root tag of navigation, which is resolved to be a NavGraph object, so call its addDestination method and place the object resolved by the child tag into the member variable mNodes.
NavGraphNavigator#navigate
public NavDestination navigate(@NonNull NavGraph destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras) {
int startId = destination.getStartDestination();
if (startId == 0) {
throw new IllegalStateException("no start destination defined via"
+ " app:startDestination for "
+ destination.getDisplayName());
}
NavDestination startDestination = destination.findNode(startId, false);
if (startDestination == null) {
final String dest = destination.getStartDestDisplayName();
throw new IllegalArgumentException("navigation destination " + dest
+ " is not a direct child of this NavGraph");
}
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
startDestination.getNavigatorName());
return navigator.navigate(startDestination, startDestination.addInDefaultArgs(args),
navOptions, navigatorExtras);
}
Copy the code
Find the id of the first page you want to launch, create a NavDestination, and use the name of the tag to get the corresponding Navigation map in the mNavigatorProvider HashMap. The NavGraphNavigator is not the ultimate executor, and it still assigns tasks to specific groups such as ActivityNavigator, FragmentNavigator, and DialogFragmentNavigator.
conclusion
At this point, the Navigation workflow is finished. To summarize:
- The first thing you need to do is have a container called NavHost that’s hosting the page. The system has a real NavHostFragment by default that initializes the NavController inside
- In order to do navigation you have to have a navigation controller called NavController
- It has two very important things in it: NavGraph and NavigatorProvider
- The NavGraph contains a set of NavDestinations, each of which is a page of navigation destinations
- NavigatorProvider has a HashMap inside of it that stores Navigator, which is an abstract class with three important subclasses FragmentNavigator, ActivityNavigator, DialogFragmentNavigator is used to navigate fragments, activities, DialogFragments, and a subclass called NavGraphNavigator that parses files that we wrote in XML into a NavGraph, Associated with NavController, the first page is displayed.
- Each of these classes implements two methods of the parent class, one of which is
createDestination
The getDestination () method is used to create a destinationnavigate
Method is really used for navigation