简介内容摘自:导航 | Android 开发者 | Android Developers
导航是指支持用户导航、进入和退出应用中不同内容片段的交互。Android Jetpack 的导航组件可帮助您实现导航,无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模式,该组件均可应对。导航组件还通过遵循一套既定原则来确保一致且可预测的用户体验。
导航组件由以下三个关键部分组成:
在应用中导航时,您告诉 NavController,您想沿导航图中的特定路径导航至特定目标,或直接导航至特定目标。NavController 便会在 NavHost 中显示相应目标。
导航组件提供各种其他优势,包括以下内容:
此外,您还可以使用 Android Studio 的 Navigation Editor 来查看和编辑导航图。
Navigation 是一个框架,用于在 Android 应用中的“目标函数”之间导航,该框架提供一致的 API,无论目标函数是作为Fragment、Activity 还是其他组件实现。
实现底部导航栏效果,分别在使用和不使用Navigation框架的情况下实现,并对比代码量。
public class MainActivity extends AppCompatActivity {
private BottomNavigationView mNavigationView;
private FragmentManager mFragmentManager;
private Fragment[] fragments;
private int lastFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNavigationView = findViewById(R.id.main_navigation_bar);
initFragment();
initListener();
}
private void initFragment() {
HomeFragment mHomeFragment = new HomeFragment();
PlanFragment mPlanFragment = new PlanFragment();
GameFragment mGameFragment = new GameFragment();
SettingFragment mSettingFragment = new SettingFragment();
fragments = new Fragment[]{
mHomeFragment, mPlanFragment, mGameFragment, mSettingFragment};
mFragmentManager = getSupportFragmentManager();
//默认显示HomeFragment
mFragmentManager.beginTransaction()
.replace(R.id.main_page_controller, mHomeFragment)
.show(mHomeFragment)
.commit();
}
private void initListener() {
mNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.home:
if (lastFragment != 0) {
MainActivity.this.switchFragment(lastFragment, 0);
lastFragment = 0;
}
return true;
case R.id.plan:
if (lastFragment != 1) {
MainActivity.this.switchFragment(lastFragment, 1);
lastFragment = 1;
}
return true;
case R.id.game:
if (lastFragment != 2) {
MainActivity.this.switchFragment(lastFragment, 2);
lastFragment = 2;
}
return true;
case R.id.setting:
if (lastFragment != 3) {
MainActivity.this.switchFragment(lastFragment, 3);
lastFragment = 3;
}
return true;
}
return false;
}
});
}
private void switchFragment(int lastFragment, int index) {
FragmentTransaction transaction = mFragmentManager.beginTransaction();
transaction.hide(fragments[lastFragment]);
if (!fragments[index].isAdded()){
transaction.add(R.id.main_page_controller,fragments[index]);
}
transaction.show(fragments[index]).commitAllowingStateLoss();
}
}
目前Android Studio中已经提供了结合Navigation的底部导航样板,可以直接使用。下文都将以此样板代码为主,并以此为基础加以修改。
dependencies {
...
implementation 'androidx.navigation:navigation-fragment:2.2.2'
implementation 'androidx.navigation:navigation-ui:2.2.2'
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BottomNavigationView navView = findViewById(R.id.nav_view);
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(navView, navController);
}
}
显而易见,在实现近乎相同的效果下,使用Navigation框架无疑能减少大量的样板代码,开发者再也无须关注麻烦的事务逻辑了。为什么使用Jetpack不用多说了吧。
Navigation组件的三个关键部分,其中导航图应该是比较新的概念,下面分别进行简单的介绍。
显示导航图中目标的空白容器,对应布局文件中
它有几个独特的属性:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="0dp"
android:layout_marginStart="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/bottom_nav_menu" />
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
androidx.constraintlayout.widget.ConstraintLayout>
在 NavHost 中管理应用导航的对象。当用户在整个应用中移动时,NavController 会安排 NavHost 中目标内容的交换。可以通过 Navigation.findNavController(Activity activity, int viewId)
或 Navigation.findNavController(View v)
方法获取实例。
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
导航图是在一个位置集中包含所有导航相关信息的 XML 资源,同时提供了图形化管理导航信息的方式。使用样板时会默认创建此文件,否则需要手动创建。
由于实现的效果是底部导航栏,所以fragment之间的切换由底部导航组件控制,具体实现效果的相关代码为:
BottomNavigationView navView = findViewById(R.id.nav_view);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupWithNavController(navView, navController);
这里有一个没有介绍到的类,NavigationUI。不过很好理解,类如其名,就是连接导航和UI的类,通过这个类,导航可以和一些组件绑定,比如ToolBar,BottomNavigationView等,这里的NavigationUI.setupWithNavController
方法就有诸多重载。
使用底部导航进行页面切换时,AppBar上的文本有时也要求进行改变。这个功能在样板代码中已经替我们实现了,相关代码为:
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
此时,AppBar上的文本会随目标的切换而改变,且对应导航图中目标的label属性。如果注释掉这部分代码,AppBar会默认显示项目名。
通过在导航图中添加行为,再由NavController控制,即可实现直接跳转,比如说允许HomeFragment能通过点击某个按钮直接跳转到NotificationsFragment,NotificationsFragment也同样能跳转回去。
在导航图中进行连线,表示添加两个目标之间的导航行为。
然后在两个fragment中添加相应的代码。此处只展示HomeFragment,NotificationsFragment类似。通过按钮的点击事件,获取NavController,并调用navigate方法进行跳转,其中的R.id.xxx_to_xxx由之前的连线(Add action)生成。
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Button button = getView().findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NavController navController = Navigation.findNavController(v);
navController.navigate(R.id.action_navigation_home_to_navigation_notifications);
}
});
}
一、在导航图中为目标设置Arguments,这种方法适合传递一些简单静态的数据。
选择需要传递数据的目标(Destination),在右侧Arguments栏中设置数据。比如想要知道是由哪个目标跳转到NotificationsFragment的。为其设置一个类型为String,键为“from”,默认值为“unknown”的数据。
此时,再选中导航到NotificationsFragment的连线,右侧Argument Default Values栏中就会出现刚才设置的数据,在此对参数值进行重载,即可实现导航时的数据传递。
这些参数同样可以在xml中查看和设置。通过xml文件可以看出,HomeFragment的action中包含了要传递的数据"from",默认值为“Home”,NotificationsFragment本身也拥有这个数据,默认值为“unkonwn”。
<fragment
android:id="@+id/navigation_home"
android:name="com.month.bottomnavigationdemo.ui.home.HomeFragment"
android:label="@string/title_home"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/action_navigation_home_to_navigation_notifications"
app:destination="@id/navigation_notifications">
<argument
android:name="from"
android:defaultValue="Home" />
action>
fragment>
<fragment
android:id="@+id/navigation_notifications"
android:name="com.month.bottomnavigationdemo.ui.notifications.NotificationsFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications" >
<argument
android:name="from"
app:argType="string"
android:defaultValue="unknown" />
<action
android:id="@+id/action_navigation_notifications_to_navigation_home"
app:destination="@id/navigation_home" />
fragment>
在NotificationsFragment中调用getArguments().getXXX(String key)
获取参数即可。
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//获取参数
String value = getArguments().getString("from");
notificationsViewModel.setText("come from" + value);
}
如果从HomeFragment点击按钮导航至NotificationsFragment,则会显示“come from Home”,由底部导航栏导航则显示“come from unknown”,因为并没有数据传递,所以取得了默认值。
这种方法非常简单,就不过多赘述了,直接上代码。
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bundle bundle = new Bundle();
bundle.putString("key","value");
NavController navController = Navigation.findNavController(v);
navController.navigate(R.id.action_navigation_notifications_to_navigation_home, bundle);
}
});
三、使用Safe Args
Jetpack学习笔记:架构组件——Navigation之使用Safe Args传递数据
在导航图中选中导航连线,在右侧的Animations栏中添加对应的动画即可。
如果此时由A导航至B,则四种动画对应:
由于系统默认提供的动画显示效果不是特别明显,这里使用了自定义的几个简单动画。