Jetpack组件系列文章
Android架构之LifeCycle组件
Android架构之Navigation组件(一)
Android架构之Navigation组件(二)
Android架构之Navigation组件(三)
Android架构之Navigation组件(四)
Android架构之ViewModel组件
Android架构之LiveData组件
Android架构之Room组件(一)
Android架构之Room组件(二)
Android架构之WorkManager组件
Android架构之DataBinding(一)
Android架构之DataBinding(二)
Android架构之Paging组件(一)
Android架构之Paging组件(二)
Jetpack与MVVM架构
可以将一系列目的地归入父级导航图(称为"根图")内的一个嵌套图中. 嵌套图对于整理和重复使用应用界面的各个部分非常有用.
嵌套图可以封装其目的地。与根图一样,嵌套图必须具有标识为起始目的地的目的地。嵌套图之外的目的地(例如根图上 目的地)只能通过其起始目的地访问嵌套图
如这幅图所示,我们可以将fragmentChoose和fragmentSpecify封装成一个Navigation,通过include引入根图中
<!-- (root) nav_graph.xml -->
<?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/fragment">
<include app:graph="@navigation/included_graph" />
<fragment
android:id="@+id/fragment"
android:name="com.example.myapplication.BlankFragment"
android:label="Fragment in Root Graph"
tools:layout="@layout/fragment_blank">
<action
android:id="@+id/action_fragment_to_second_graph"
app:destination="@id/second_graph" />
</fragment>
...
</navigation>
切记的是: inclued_graph.xml必须有app:startDestination起始目的地。
<!-- included_graph.xml -->
<?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/second_graph"
app:startDestination="@id/includedStart">
<fragment
android:id="@+id/includedStart"
android:name="com.example.myapplication.IncludedStart"
android:label="fragment_included_start"
tools:layout="@layout/fragment_included_start" />
</navigation>
这样通过include引入,可以我们方便管理和达到复用的效果.
您可以使用全局操作来创建可由多个目的地共用的通用操作。例如,你可能想要不同目的地中的多个按钮导航到同一应用主屏幕
在 Navigation Editor 中,全局操作由一个指向相关联目的地的小箭头表示,如图 所示。
如需创建全局操作,请执行以下操作:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_nav"
app:startDestination="@id/mainFragment">
...
<action android:id="@+id/action_global_mainFragment"
app:destination="@id/mainFragment"/>
</navigation>
如需在代码中使用某个全局操作,请将该全局操作的资源 ID 传递到每个界面元素的 navigate() 方法,如以下示例所示:
viewTransactionsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Navigation.findNavController(view).navigate(R.id.action_global_mainFragment);
}
});
这样,无论你在哪个Fragment, 使用全局资源ID,都可以跳转到mainFragment.
当然,这些Fragment必须属于同一个导航图。
navigate(int) 接受操作或目的地的资源 ID 作为参数。以下代码段展示了如何导航到 ViewTransactionsFragment
viewTransactionsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Navigation.findNavController(view).navigate(R.id.viewTransactionsAction);
}
});
需要注意一点的是: ViewTransactionsFragment必须要在Navigation中进行注册
对于按钮,您还可以使用 Navigation 类的 createNavigateOnClickListener() 便捷方法导航到目的地,如下例所示:
button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.next_fragment, null));
通常来说,在页面的切换过程中,通常还伴随着App bar中menu菜单的变化,对于不同的页面,App bar中的menu菜单很可能是不一样的。例如,当ActionBar左边的返回按钮被单击时,我们需要响应该事件,返回到上一个页面。既然Navigation和App bar都需要处理页面切换事件,那么,为了方便管理,Jetpack引入了NavigationUi 组件,使App bar中的按钮和菜单能够与导航图中的页面关联起来.
假设我们有两个页面,MainFragment和SettingsFragment.这两个Fragment同属于MainActivity. MainFragment的ActionBar右边有一个按钮,通过该按钮,可以跳转到SettingsFragment.而在SettingsFragment的ActionBar左侧有一个返回按钮,通过该按钮,可以返回MainFragment.
项目的导航图文件nav_graph.xml,可以清晰地看到页面间的关系。MainActivity包好了MainFragment和SettingsFragment.默认加载的是MainFragment.代码如下所示
<?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/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.example.jetpack.MainFragment"
android:label="fragment_main"
tools:layout="@layout/fragment_main" >
</fragment>
<fragment
android:id="@+id/settingFragment"
android:name="com.example.jetpack.settingFragment"
android:label="fragment_setting"
tools:layout="@layout/fragment_setting" />
</navigation>
在menu_settings.xml文件中,为ActionBar添加菜单。需要注意的是,的id与导航图中SettingsFragment的id是一致的,这表示,当该被单击时,将会跳转到id所对应的Fragments.即SettingsFragment, 代码如下所示
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/settingFragment"
android:icon="@drawable/ic_launcher_foreground"
android:title="设置界面"
/>
</menu>
AppBarConfiguration
NavigationUI 使用 AppBarConfiguration 对象管理在应用显示区域左上角的导航按钮行为,导航按钮的行为会根据用户是否位于顶级目的地而变化。
默认情况下,应用的起始目的地是唯一的顶级目的地。当用户位于顶级目的地时,导航按钮会变为抽屉式导航栏图标 ,当用户位于任何其他目的地上时,导航按钮会显示为向上按钮(返回按钮).
简单来说
AppBarConfiguration appBarConfiguration =
new AppBarConfiguration.Builder(navController.getGraph()).build();
当构建AppBarConfiguration时,传入的是相对应的导航图,那么导航图中起始目的地就会显示抽屉式导航栏图标,导航图其余的Fragment就会显示向上按钮,如图所示:
这是导航图中的起始目的地,就会没有向上按钮,
在某些情况下,如果想指定多个顶级目的地。对于这样的情况,您可以改为将一组目的地 ID 传递给构造函数,代码如下所示:
appBarConfiguration = new AppBarConfiguration.Builder(R.id.mainFragment,R.id.settingFragment).build();
这样settingFragment是没有了向上按钮,成为了顶级目的地
在MainActivity中实例化菜单。代码如下所示
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_settings,menu);
return true;
}
我们该如何在代码中 响应菜单单击事件呢? 在没有NavigationUI组件之前,我们需要判断被单击的菜单项,接着编写相应的代码,跳转到对应的页面。现在有了 NavigationUI组件,可以自动帮我们处理好跳转逻辑
public class MainActivity extends AppCompatActivity {
private AppBarConfiguration appBarConfiguration;
private NavController navController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取navController,通过MainActivity空白的NavHostFragment获取
navController = Navigation.findNavController(this,R.id.nav_host_fragment);
//将导航图和AppBarConfiguration关联起来
appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
//将AppBarConfiguration和NavController绑定起来
NavigationUI.setupActionBarWithNavController(this,navController,appBarConfiguration);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
return NavigationUI.onNavDestinationSelected(item,navController)||super.onOptionsItemSelected(item);
}
@Override
public boolean onSupportNavigateUp() {
return NavigationUI.navigateUp(navController,appBarConfiguration)||super.onSupportNavigateUp();
}
}
我们可以通过MainActivity空白的NavHostFragment获取navController 对象,并且通过navController获取导航图 和AppbarConfiguration关联起来.
//获取navController,通过MainActivity空白的NavHostFragment获取
navController = Navigation.findNavController(this,R.id.nav_host_fragment);
//将导航图和AppBarConfiguration关联起来
appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
由于在导航图和菜单的布局文件中,我们为SettingsFragment和menu设置了相同的Id。因此,在onOptionsItemSelected()方法中,通过NavigationUI便可以自动完成页面跳转
同样,覆盖onSupportNavigateUp()方法,当我们在SettingsFragment中单击ActionBar左边的返回按钮时,NavigationUI可以帮助我们从SettingsFragment回到MainFragment。
AppBarConfiguration用于App bar的配置,NavController用于页面的导航和切换。再通过下面这行代码,将App bar和NavController绑定起来。
NavigationUI.setupActionBarWithNavController(this,navController,appBarConfiguration);
需要注意的是,App bar是在MainActivity中进行管理的。当从MainFragment跳转到SettingsFragment时,需要在SettingsFragment中覆盖onCreateOptionsMenu()方法,并在该方法中清除MainFragment所对应的menu
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
menu.clear();
super.onCreateOptionsMenu(menu, inflater);
}
大家一定有一个疑问,为什么需要清除呢?
因为MainFragment和SettingsFragment都是在MainActivtiy中进行显示,在MainActivity配置了App bar, MainFragment和SettingsFragment切换只是在NavHostFragment中进行显示,NavHostFragment 相对来说是属于MainActivity的一个组件,并不会改变Appbar,所以我们需要在SettingsFragment清除menu;
我们可以利用NavController提供的一个名为OnDestinationChangedListener的接口,对页面切换事件进行监听
navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
@SuppressLint("RestrictedApi")
@Override
public void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments) {
// Log.i("true",controller.toString());
Log.i("true",destination.getNavigatorName()+destination.getDisplayName()+destination.getId());
if(destination.getId() == R.id.settingsFragment) {
toolbar.setVisibility(View.GONE);
bottomNavigationView.setVisibility(View.GONE);
} else {
toolbar.setVisibility(View.VISIBLE);
bottomNavigationView.setVisibility(View.VISIBLE);
}
}
});
通过destination.getId 可以获取当前的fragment的id,就可以根据destination.getId判断到了哪个目的地,对该目的地的App bar可以进行切换了,达到不同的Fragment,App bar不一样的效果. 代码示例如下
navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
@SuppressLint("RestrictedApi")
@Override
public void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments) {
// Log.i("true",controller.toString());
Log.i("true",destination.getNavigatorName()+destination.getDisplayName()+destination.getId());
if(destination.getId() == R.id.settingFragment){
getSupportActionBar().setTitle("小鑫啊");
}else if(destination.getId() == R.id.mainFragment){
getSupportActionBar().setTitle("主页啊");
}
}
});
当切换到settingsFragment时,标题变成了小鑫啊,到达了修改App bar的效果,也可以在切换页面的时候,更改App bar的样式.
好了,基本的Navigation的使用就说到这里了,不足之处,望大家见谅,谢谢。
接下来,我们就来说说,Navigation支持的另外两种App bar和另外两种菜单