一.Navigation
- Navigation (翻译过来就是导航)是一个框架,用于在 Android 应用中的“目标”之间导航,该框架提供一致的 API,无论目标是作为 Fragment、Activity 还是其他组件实现;
- 如:Activity是有回退栈管理的,而Fragment没有,如果使用Navigation,则可以让Fragment有回退栈管理的,这只是Navigation的其中一部分的作用;
1.Navigation三大件
-
导航组件由以下三个关键部分组成
-
**导航图:**在一个集中位置包含所有导航相关信息的 XML 资源。这包括应用内所有单个内容区域(称为目标)以及用户可以通过应用获取的可能路径;
-
**NavHost:**显示导航图中目标的空白容器。导航组件包含一个默认 NavHost 实现 (NavHostFragment),可显示Fragment 目标;
-
**NavController:**在 NavHost 中管理应用导航的对象。当用户在整个应用中移动时,NavController 会安排NavHost 中目标内容的交换;
-
在应用中导航时,您告诉
NavController
,您想沿导航图中的特定路径导航至特定目标,或直接导航至特定目标。NavController
便会在NavHost
中显示相应目标;
2.Navigation优势
- 处理 Fragment 事务。
- 默认情况下,正确处理往返操作。
- 为动画和转换提供标准化资源。
- 实现和处理深层链接。
- 包括导航界面模式(例如抽屉式导航栏和底部导航),用户只需完成极少的额外工作。
- Safe Args - 可在目标之间导航和传递数据时提供类型安全的 Gradle 插件。
ViewModel
支持 - 您可以将ViewModel
的范围限定为导航图,以在图表的目标之间共享与界面相关的数据。
此外,您还可以使用 Android Studio 的 Navigation Editor 来查看和编辑导航图。
注意:Navigation 组件需要 Android Studio 3.3 或更高版本,并且依赖于 Java 8 语言功能。
3.Navigation能做什么
-
处理 Fragment 事务;
-
弥补Fragment没有回退栈的问题;
-
提高开发效率;
-
可以更轻松的处理深层连接(DeepLink);
-
包括导航界面模式(例如抽屉式导航栏和底部导航),用户只需完成极少的额外工作;
DeepLink:深度链接技术,主要应用场景是通过Web页面直接调用Android原生app,并且把需要的参数通过Uri的形式,直接传递给app;
二.使用
1.依赖
def nav_version = "2.3.0"
// Java language implementation
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
2.新建资源文件
- 在navigation目录下新建xml文件(名字自定义),如nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/afragment">
<!--不定义startDestination,会报错-->
<!--startDestination:默认显示的Fragment-->
<!--实现fragment之间的跳转-->
<action
android:id="@+id/to_bfragment"
app:destination="@+id/bfragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"/>
<fragment
android:id="@+id/afragment"
android:name="com.jack.a2021_09_29day05.AFragment"
android:label="afragment"
tools:layout="@layout/fragment_a"
/>
<fragment
android:id="@+id/bfragment"
android:name="com.jack.a2021_09_29day05.BFragment"
android:label="bfragment"
tools:layout="@layout/fragment_b"
/>
<fragment
android:id="@+id/cfragment"
android:name="com.jack.a2021_09_29day05.CFragment"
android:label="cfragment"
tools:layout="@layout/fragment_c"
/>
<fragment
android:id="@+id/dfragment"
android:name="com.jack.a2021_09_29day05.DFragment"
android:label="dfragment"
tools:layout="@layout/fragment_d"
/>
</navigation>
- activity_main.xml中定义
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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/nav_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true"
/>
<!--navGraph:需要指定目标文件-->
<!--defaultNavHost:回退栈交给activity管理-->
</RelativeLayout>
3.Fragment之间的跳转
- 在nav_graph.xml中定义action,步骤2中已经添加
<action
android:id="@+id/to_bfragment"
app:destination="@+id/bfragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"/>
- AFragment的跳转逻辑
public class AFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_a, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//方式一
view.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Navigation.findNavController(v).navigate(R.id.to_bfragment);
}
});
//方式二
//view.findViewById(R.id.button).setOnClickListener(Navigation.createNavigateOnClickListener(R.id.to_bfragment));
}
}
- 经过以上步骤,实现了Navigation最基本的使用,步骤2中activity_main.xml中定义的defaultNavHost使fragment具备了activity回退栈的效果;
- 关于参数携带,也只是api简单的调用,具体细节大家可以按照项目需求来进行学习,另外补充一点,参数传递有更友好的方法(比较简单)-Navigation(二)使用safe args传递参数;
三.结合BottomNavigationView使用
- BottomNavigationView的具体使用这里就略了,将Navigation与BottomNavigationView组合起来的代码大致贴一下
1.使用
- activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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/nav_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true"
/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@android:color/white"
android:layout_alignParentBottom="true"
app:menu="@menu/menu"
app:itemIconTint="@drawable/menu_select"
app:itemTextColor="@drawable/menu_select"
app:labelVisibilityMode="labeled"/>
</RelativeLayout>
- menu目录下的menu.xml
- 这里会有一个坑,id需要添加:未添加的情况下,点击mainactivity的底部导航栏不会切换对应的fragment;
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/afragment"
android:icon="@mipmap/index"
android:title="首页"></item>
<item
android:id="@+id/bfragment"
android:icon="@mipmap/cate"
android:title="分类"
/>
<item
android:id="@+id/cfragment"
android:icon="@mipmap/cart"
android:title="购物车" />
<item
android:id="@+id/dfragment"
android:icon="@mipmap/me"
android:title="我的" />
</menu>
- MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NavController navController = Navigation.findNavController(this, R.id.nav_fragment);
//NavigationUI类
NavigationUI.setupWithNavController((BottomNavigationView)findViewById(R.id.bottomNavigationView),navController);
}
}
- 感兴趣可以学习-Navigation(三)NavigationUI的使用
2.调整图标和标题的位置
- 单单定义dimens.xml定义属性,还不足以满足所有的业务需求
<!-- BottomNavigationView 相关 -->
<!-- 使用BottomNavigationView时,选中放大,本质上是底部的Text字体变大了-->
<!-- 1.处理选中时放大,默认选中是14sp,不选中是12sp,更改设置,可重写-->
<!-- 在design_bottom_navigation_item文件中,给两个TextView设置textSize可以达到同样的效果,而且效果更好-->
<!--重写了design_bottom_navigation_item.xml之后,textview的大小恢复成默认值,需要在design_bottom_navigation_item.xml中对textview设置目标的文字大小-->
<!-- <dimen tools:override="true" name="design_bottom_navigation_text_size">@dimen/sp_10</dimen>-->
<!-- <dimen tools:override="true" name="design_bottom_navigation_active_text_size">@dimen/sp_10</dimen>-->
<!-- 2.调整图片和字之间的间距,默认是5dp,调整间距,重写-->
<dimen tools:override="true" name="design_bottom_navigation_margin">@dimen/dp_14</dimen>
<!-- 3.导航栏高度,默认是56dp,调整高度,重写-->
<dimen tools:override="true" name="design_bottom_navigation_height">@dimen/dp_62</dimen>
-
定义design_bottom_navigation_margin可以设置ImageView距离top的间距,如果BottomNavigationView的高度比较高,则layout_marginBottom不起作用,如果还要设定textview距离底部的间距,则需要重新design_bottom_navigation_item.xml文件;
-
自定义design_bottom_navigation_item.xml文件,目的:主要是调整textview相关的间距,ImageView的相关属性也可以调整
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:override="true">
<ImageView
android:id="@+id/icon"
android:layout_width="@dimen/dp_21"
android:layout_height="@dimen/dp_21"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/design_bottom_navigation_margin"
android:layout_marginBottom="@dimen/design_bottom_navigation_margin"
android:duplicateParentState="true" />
<TextView
android:id="@+id/smallLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="@dimen/dp_13"
android:textSize="@dimen/sp_10"
android:duplicateParentState="true"
android:text="small" />
<TextView
android:id="@+id/largeLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="@dimen/dp_13"
android:duplicateParentState="true"
android:textSize="@dimen/sp_10"
android:text="large"
android:visibility="invisible" />
</merge>
- 关于BottomNavigationView的使用,结合下面两篇文章
四.深度链接
- 这篇文章讲解的不错-Navigation(四)DeepLink的使用
五.实战中遇到的细节问题
-
单单在dimens.xml定义属性4个属性(上文中有提及)可能还不足以满足所有需求,需要结合重写design_bottom_navigation_item.xml文件来处理。
-
另外,记录一个疑问,项目使用的是组件化,然后使用新建多个不同文件夹的dimens.xml来处理适配,在默认的组件中的dimens.xml新添加上面的4个属性,未起到效果。于是在对应的module中新建了dimens.xml,问题得到解决。具体原因不明,待后续排查。
评论区