内容简介:Navigation is an essential part of Android development. The most common way to do it is via Intents and Fragment transactions. This works great for simple cases but, as the framework evolved, handling navigation became harder with more complex UI designs.F
Navigation is an essential part of Android development. The most common way to do it is via Intents and Fragment transactions. This works great for simple cases but, as the framework evolved, handling navigation became harder with more complex UI designs.
Fragments inside Fragments, deep links, bottom navigation bars, navigation drawers… You probably just felt a shiver down your spine picturing yourself handling a few of these together with just Intents and Fragment transactions.
Jetpack’s Navigation Component is Google’s attempt to earn back the Android developer’s love. Those examples given above? Navigation Component can handle all of them at the same time with a few lines of code.
It’s becoming a must have in any Android developer’s skill set!
In this tutorial, you’ll learn use Navigation Component for:
- Shared element transitions.
- Controlling the Action Bar.
- Handling bottom navigation.
Note : This tutorial assumes you’re familiar with the basics of nav graphs. If you aren’t, please review Navigation Component for Android Part 2: Graphs and Deep Links first.
As you explore Navigation Component, you’ll work on an app named My Little Doggo . It’s an app that shows you a list of random dog images and lets you mark your favorites.
OK, time to look at some cute dogs. :]
Getting Started
Download the project materials by clicking the Download Materials button at the top or bottom of this tutorial.
Launch Android Studio 3.6 or later and select Open an existing Android Studio project . Then navigate to and select the starter project folder. You’ll see a structure like this:
Explore the project for a bit. Focus on the presentation package, where all the UI related code resides.
Then, go to the res package. You’ll see there’s already a package called navigation with three different nav graphs. The main nav graph is app.xml . Its only purpose is to nest the other two, doggo_list and favorites.xml , which you’ll work with later in this tutorial.
Note : While the app uses Room , Coroutines and Flow , you don’t need prior knowledge of any of these to complete the tutorial. However, if you want to learn about, checkout Android Jetpack Architecture Components: Getting Started for Room and Kotlin Coroutines Tutorial for Android: Getting Started .
Build and run the project. You’ll see a simple screen with a list of doggos. The app already uses a simple implementation of Navigation Component that lets you click a doggo to see it in full screen.
This is a great app to show to any UX designer, but it has some problems:
- Action Bar doesn’t update.
- Full screen view just snaps in.
- Button can’t navigate to your favorites.
It’ll take some work, but the doggos will keep you company every step of the way. :]
Fetching Dependencies
To kick things off, you need to add some dependencies:
- Material Components : Needed for transitions and bottom navigation.
- Navigation UI : Responseable for handling the action bar, bottom navigation and navigation drawer.
Head to the app build.gradle . At the bottom, right below the navigation dependency, add these two lines:
implementation "androidx.navigation:navigation-ui-ktx:$nav_version" implementation "com.google.android.material:material:$material_version"
The versions are already in the root build.gradle . Sync Gradle and run the app to make sure everything is OK.
Sharing Elements Between Screens
Shared element transitions are a neat way to express continuity and flow smoothly between screens. You can use them to help the user better understand the flow of information when you have common elements between screens.
To use shared element transitions, you need to define which views transition between screens and give each of those transitions a unique name.
Since the app uses Fragments to navigate, you have to tinker with Fragment transitions by postponing them and starting them later at the right time so they don’t interfere.
Adapting the Adapter
Expand presentation and open DoggosAdapter.kt .
As mentioned before, each shared element transition needs a unique name. In this case, the picture URL is a good option. In bind()
method, add the following line below load(item.picture){...}
code block:
transitionName = item.picture
This sets the transitionName
to the corresponding Doggo
picture url.
That’s all for the Adapter.
Well done! Build and run the app to make sure you didn’t break anything. The app will launch and behave as before.
You won’t see any differences in the animation yet. You still need to tell Navigation Component which views it should transition.
Adding Shared Elements to NavController
Now, click the doggos package and open DoggoListFragment.kt .
First, add a helper method right at the bottom of the class, which will be used to simplify navigation:
private fun navigate(destination: NavDirections, extraInfo: FragmentNavigator.Extras) = with(findNavController()) { // 1 currentDestination?.getAction(destination.actionId) ?.let { navigate(destination, extraInfo) //2 } }
Here is what is happening in this function:
- Make sure the current destination exists, by checking if the passed destination’s action id resolves to an action on the current destination. This avoids attempting to navigate on non-existent destinations
- Passing
FragmentNavigatorExtras
instance with the extra information intoNavController
through itsnavigate
method.
Now to use it, navigate to method called createAdapter()
. This method creates the Adapter for the RecyclerView and is where you pass a lambda to DoggosAdapter
.
Delete the findNavController().navigate(toDoggoFragment)
line and add the following in its place:
//1 val extraInfoForSharedElement = FragmentNavigatorExtras( //2 view to doggo.picture ) //3 navigate(toDoggoFragment, extraInfoForSharedElement)
Here’s a code breakdown:
-
FragmentNavigatorExtras
is the class you use to tell Navigation Component which views you want to share, along with the corresponding transition name, which you previously set as the picture URL. - Writing
view to doggo.picture
is the same as writingPair(view, doggo.picture)
. - In this code, you pass the created
FragmentNavigatorExtras
instance with the transition information into the helper method created earliernavigate
method.
You’re halfway done!
Build and run the app. You still won’t see any difference in the animation, but the app would run and behave like before.
Next, you’ll tell the destination Fragment that there’s an element to transition.
Walking the Doggo to Another View
You need to define which kind of transition you want the Fragment to do. So, you’ll create a transition and set it to the Fragment’s sharedElementEnterTransition
property.
First, right-click the res package and select New ▸ Android Resource Directory . On the Resource type dropdown, select transition . It’ll automatically change the directory name:
Now, click OK . You’ll see a transition directory inside res .
Next, right-click transition and select New ▸ Transition resource file . Call it shared_element_transition and click OK . Then delete everything inside and paste the following:
<?xml version="1.0" encoding="utf-8"?> <changeBounds xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:interpolator/fast_out_slow_in" />
Note : There are a few types of transitions, each with different properties. You can even group them together using something called a TransitionSet . Read more about these in the official documentation .
You have your transition defined. Now, hand it to the destination Fragment.
First, expand the doggodetail package and open DoggoFragment.kt . Looking at the code, you can tell DoggoFragment
already uses Navigation Component.
It fetches the doggo’s picture URL and favorite status through navArgs()
and uses them to set up its UI.
Now, at the bottom of DoggoFragment
, add the following method:
private fun setSharedElementTransitionOnEnter() { sharedElementEnterTransition = TransitionInflater.from(context) .inflateTransition(R.transition.shared_element_transition) }
TransitionInflater
, be sure to import
TransitionInflater
from
androidx.transition
and
not
android.transition
.
The app will crash if you import the wrong dependency! If androidx.transition
doesn’t appear, it means you didn’t import Material Components. Go to the Fetching Dependencies section of the tutorial to see how it’s done.
You’re setting sharedElementEnterTransition
in the method, but you’re not calling it anywhere. That won’t do.
Fix this by calling setSharedElementTransitionOnEnter()
and postponeEnterTransition()
in onViewCreated()
:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val (picture, isFavorite) = args // Add these two lines below setSharedElementTransitionOnEnter() postponeEnterTransition() setupFavoriteButton(picture, isFavorite) image_view_full_screen_doggo.load(picture) }
You already know what setSharedElementTransitionOnEnter()
is for, but what’s with postponeEnterTransition()
?
Well, the images you want to transition are loaded into the Fragment view by Glide . Loading images takes time, which means that the view runs its transitions before the image is available, messing up the animation. The trick here is to postpone the Fragment enter transition and resume it after the images finish loading.
Pretty clever, huh?
Note : If you’ve never used Glide before, be sure to check Glide Tutorial for Android: Getting Started .
To do this, you’re going to take advantage of the fact that Glide lets you add request listeners to its image loading.
First, add the following method at the bottom of DoggoFragment
, right below setSharedElementTransitionOnEnter()
:
private fun startEnterTransitionAfterLoadingImage( imageAddress: String, imageView: ImageView ) { Glide.with(this) .load(imageAddress) .dontAnimate() // 1 .listener(object : RequestListener<Drawable> { // 2 override fun onLoadFailed( e: GlideException?, model: Any?, target: com.bumptech.glide.request.target.Target<Drawable>?, isFirstResource: Boolean ): Boolean { startPostponedEnterTransition() return false } override fun onResourceReady( resource: Drawable, model: Any, target: com.bumptech.glide.request.target.Target<Drawable>, dataSource: DataSource, isFirstResource: Boolean ): Boolean { startPostponedEnterTransition() return false } }) .into(imageView) }
Now, resolve all the reference errors. When multiple imports are possible, be sure to pick the ones prefixed with com.bumptech.glide
.
This code is the basic Glide usage with two extra calls:
- You don’t want Glide to mess up things with its default crossfade animation. As such, you call
dontAnimate()
to avoid it. -
RequestListener
needs you to overrideonLoadFailed
andonResourceReady
. You callstartPostponedEnterTransition()
in both of them because you need to even if the request fails. If you don’t call it, the UI will freeze after callingpostponeEnterTransition()
.
Your transition is almost ready!
Now, go back to onViewCreated()
in DoggoFragment.kt and replace image_view_full_screen_doggo.load(picture)
with:
image_view_full_screen_doggo.apply { //1 transitionName = picture //2 startEnterTransitionAfterLoadingImage(picture, this) }
Here you:
transitionName
The shared element transition is now complete. Whew!
Build and run, then give the app a whirl. Congrats on your cool transitions!
Wait, what? Where’s the return transition? Did a doggo run away with it?
Teaching the RecyclerView to Stay
No, a doggo didn’t run away with your transition. They’re well-trained!
You’re facing a problem you had before, but with a different component. The dog pictures displayed by the RecyclerView also need to be loaded. This load takes more time than the RecyclerView needs to set up everything else.
The fix? Same as before. Call postponeEnterTransition()
followed by startPostponedEnterTransition()
. The difference is, this time you’ll do it with the RecyclerView.
First, go back to DoggoListFragment.kt . At the end of the setupRecyclerView()
, right below addOnScrollListener
, add the following code:
//1 postponeEnterTransition() //2 viewTreeObserver.addOnPreDrawListener { //3 startPostponedEnterTransition() true }
There are three new elements here:
onPreDraw startPostponedEnterTransition()
Build and run the app again. Don’t you love it when things work correctly?
Notice that the other images appear and disappear without any animation at all.
You can solve this by going to res ▸ navigation ▸ doggo_list.xml and replacing the action tag of doggoListFragment
that navigates to DoggoFragment
with below:
<action android:id="@+id/to_doggoFragment" app:destination="@id/doggoFragment" app:enterAnim="@anim/fragment_fade_enter" app:exitAnim="@anim/fragment_fade_exit" app:popEnterAnim="@anim/fragment_fade_enter" app:popExitAnim="@anim/fragment_fade_exit" />
Build and run the app. Poof! No more animation glitches!
This was the most complex part of the tutorial. Congrats on making it this far!
Controlling the Action Bar
The Action Bar is one of the most important design elements in Android. It not only provides consistency between apps but also allows users to quickly interact with a familiar set of elements.
Navigation Component provides default support for Action Bars through the NavigationUI
class. The Action Bar in this app is the theme’s default.
Navigation Component also guarantees the principles of navigation for the Action Bar are followed:
- Up and Back are identical within your app task.
- The Up button never exits your app.
In the presentation package, open MainActivity.kt . At the top of the class, right above onCreate
, add the following properties:
private val navController by lazy { findNavController(R.id.nav_host_fragment) } private val appBarConfiguration by lazy { AppBarConfiguration(navController.graph) }
Now resolve the import about findNavController
choosing findNavController(Activity.Int)(androidx...)
.
These should already be familiar to you from the previous tutorial. [TODO FPE: Which tutorial?] This NavController
is the same one used to navigate in DoggoListFragment
.
Now you need to connect the Action Bar to NavController
. Inside the setupActionBar()
, add the following one liner:
setupActionBarWithNavController(navController, appBarConfiguration)
Resolve the reference error, then build and run the app.
You’ll see the Action Bar now updates the title and shows the Up button. Success!
OK, the Up button doesn’t work. Deep inside, you knew it was too good to be true. :]
There’s one last step. Now you need to override onSupportNavigateUp
so NavController
will handle clicks on the Navigation button. Add the following method above the setupActionBar()
method declaration:
override fun onSupportNavigateUp(): Boolean { return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() }
If you were using your own Toolbar instead of the default Action Bar, you wouldn’t need to override onSupportNavigateUp()
. With Toolbar, Navigation automatically handles click events for the Navigation button.
Build and run the app. You now have a working Up button!
Adding a Menu Item
Sometimes, you want certain screens to show menu items in the Action Bar. With Navigation Component, it only takes a few lines of code.
There’s already an About Fragment
in the app’s nav graph, so you’ll add a menu item that navigates to that Fragment.
First, you need to create the menu item.
Right-click your res package and select New ▸ Android Resource File . Then, on the Resource type dropdown, choose menu . Call it menu_about
. Delete everything in it and paste the following:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/aboutFragment" android:title="@string/about" android:icon="@drawable/ic_settings_24dp" app:showAsAction="always"/> </menu>
Just your typical menu item. However, take a look at the item’s ID.
If you open the doggo_list.xml nav graph, you’ll notice that AboutFragment
has the same ID. This isn’t a coincidence. These IDs must match for the navigation to work.
Now, open DoggoFragment.kt . You’ll add the menu item here. In onCreateView()
, right before the return, add this line:
setHasOptionsMenu(true)
This tells the system that the Action Bar in this Fragment should display a menu item.
Next, tell the system which menu item to display and what to do with it. Paste this code after onCreateView()
:
// 1 override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.menu_about, menu) } // 2 override fun onOptionsItemSelected(item: MenuItem): Boolean { return item.onNavDestinationSelected(findNavController()) || super.onOptionsItemSelected(item) }
Resolve the import errors. In these method overrides, you:
- Inflate the menu item.
- Call
onNavDestinationSelected
, which is aNavigationUI
helper method. This method takes in theNavController
and, if the IDs of the destination and the menu item match, uses it to navigate to that destination.
Build and run the app. Try your new button:
Notice anything strange? As soon as you press the Up button, you’re back to the start destination. The back stack is effectively popping back to the nav graph start destination.
You can fix this by adding android:menuCategory="secondary"
to your menu item inside the menu_about.xml . Add it below android:id
:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/aboutFragment" android:menuCategory="secondary" android:title="@string/about" android:icon="@drawable/ic_settings_24dp" app:showAsAction="always"/> </menu>
This way, onNavDestinationSelected()
knows it shouldn’t pop the whole back stack.
Build your app, and try it out.
Look at you go! All that’s missing now is a Bottom Navigation setup inside the app.
Implementing Bottom Navigation
Each button of a bottom navigation bar represents a top-level destination. You should only use bottom navigation when you have three to five top-level destinations of equal importance. This app only has two, but, after all, it’s a demo.
The way you represent destinations varies with their number:
- Three destinations : Display icons and text labels for all.
- Four destinations : Active destinations display an icon and text label. Inactive destinations display icons and text labels are recommended.
- Five destinations : Active destinations display an icon and text label. Inactive destinations use icons and use text labels if space permits.
Note : Icons are always mandatory but text labels are optional. If you want text, keep it short. Otherwise, you’ll face the Material Design Police for truncating or wrapping text in a bottom nav bar.
Updating Styles and Layouts
Now you’ll use the BottomNavigationView
from Material Components. In the app, navigate to res ▸ values ▸ styles.xml . Update your themes to use Material Components.
If you don’t do this, the bottom navigation bar won’t display correctly.
Simply replace AppCompat
with MaterialComponents
i.e Theme.AppCompat.DayNight.DarkActionBar
is replaced by Theme.MaterialComponents.DayNight.DarkActionBar
. Once done your styles.xml would be like below:
<resources> <!-- Base application Theme --> <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> <!-- Splash Screen Theme --> <style name="SplashTheme" parent="Theme.MaterialComponents.NoActionBar"> <item name="android:windowBackground">@drawable/splash_background</item> <item name="android:windowTranslucentStatus">true</item> <item name="android:windowTranslucentNavigation">true</item> </style> </resources>
To set the destination buttons, you’ll use a menu. Right-click on the menu package, select New ▸ Menu resource file . Name it menu_bottom_nav
. Delete everything inside and add this menu:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/doggoList" android:icon="@drawable/ic_list_white_24dp" android:title="@string/doggos" app:showAsAction="ifRoom" /> <item android:id="@+id/favorites" android:icon="@drawable/ic_favorite_white_24dp" android:title="@string/favorites" app:showAsAction="ifRoom" /> </menu>
The first item is for the doggo list. The second item is for the favorites list, which you couldn’t access until now. As with the Action Bar menu item, the IDs of these items must match the IDs of the actual nav graphs.
Now that you have the menu, you need to add a BottomNavigationView
to the layout where the NavHostFragment
is.
First, navigate to res ▸ layout and open activity_main.xml . Add the navigation view at the end of the LinearLayout
:
<?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" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main_container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".presentation.MainActivity"> <fragment android:id="@+id/nav_host_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/app" /> <!-- Bottom Navigation Component added --> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottom_navigation" android:layout_width="match_parent" android:layout_height="wrap_content" app:menu="@menu/menu_bottom_nav" /> </LinearLayout>
You should already see it in the layout preview. Build and run the app to see the real deal!
Cute, but the buttons don’t do anything. You still need to connect the BottomNavigationView
to the Navigation Component.
Wiring the NavController to Bottom Navigation
First, open MainActivity.kt . Add this line in setupBottomNavigationBar()
:
bottom_navigation.setupWithNavController(navController)
Resolve any import errors. You’re accessing the bottom nav through bottom_navigation
, a synthetic property created by Kotlin through the ID of the BottomNavigationView
in the layout.
Build and run the app. The buttons now work correctly.
Not only do the buttons work, but since you’re already controlling the Action Bar with the Navigation Component, the name of the screen updates when you click the destinations.
They go great together! However, you probably noticed the Up button appears in the Action Bar when you click Favorites . That shouldn’t happen in top-level destinations.
Fortunately, there’s a way to fix it.
Fixing The Action Bar Navigation Button
The problem is that Navigation Component doesn’t know which destinations, other than the starting one, are top-level destinations.
The AppBarConfiguration
class provides a simple solution. While still in MainActivity.kt , change appBarConfiguration
initialization to:
private val appBarConfiguration by lazy { AppBarConfiguration( topLevelDestinationIds = setOf( R.id.doggoListFragment, R.id.favoritesFragment ) ) }
Here, you specify exactly which destinations should be considered top-level. You must pass in the IDs of the Fragments you want to treat as top-level destinations.
Build and run. That’s it!
The Multiple Back Stacks Problem
There’s one more thing before you go. Notice the back stack of a top-level destination is destroyed when you go to the other one:
This is a known issue . Navigation Component doesn’t support multiple back stacks yet.
While efforts to support the feature are in progress, it might take a while as it involves changing code at the Fragment level.
Where to Go From Here?
Impressive! You made it to the end with no doggo bites, and, best of all, now you can use Navigation Component to leverage some pretty neat navigation patterns.
Download the completed final version of the project by clicking the Download Materials button at the top or bottom of this tutorial.
For additional examples of Navigation, as well as the navigation principles, check the official documentation .
You can also check Google’s basic and advanced navigation samples.
For bottom navigation guidelines, check the material guidelines .
I hope you enjoyed this tutorial. If you have any questions, tips or want to show off your cool mapping app, feel free to join the discussion below!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
微商团队管理实战手册
杜一凡 / 人民邮电出版社 / 2015-11 / 45.00元
回顾淘宝,用了10年时间才发展了不到1000万的卖家,再看微商,其仅一年时间就拥有了超过1000万的卖家。进入2015年,微商的发展之路虽有小坎坷,但前景依然被看好。然而任何一个想要做大、做强的微商都要以团队形式来发展,独立的个体只会举步维艰。 本书全面解读微商团队管理的营销书。全书共分为六章,分别从微商团队的商业秘密、微商团队的战略布局、管理基本功、建立高效团队、精通管理工具、未来发展等方......一起来看看 《微商团队管理实战手册》 这本书的介绍吧!