前言
我相信你必定见过这样的App主页面,底部或许顶部有多个按钮,点击之后会切换当时的页面,滑动当时页面也会切换底部按钮,这儿我用几个App的主页面来说明一下吧
淘宝
微博
CSDN App
支付宝
能够说绝大部分App都是这种主页面布局形式,当然还有许多,在这儿举列子是让你有一个概念罢了。
那么来看看本文中实现的作用是怎么样的。假如不满意,我想也就不浪费你的时间了。
正文
从上面的一些APP主页面,在之前这种页面是经过什么来做的呢?这儿有好几种组合: ① Activity + Fragment + TabLayout + ViewPager。 ② Activity + Fragment + RadioGroup(RadioButton) + ViewPager。 相信你在许多的博客上或许自己的项目上看过或许运用过。而现在能够经过另一个更简洁的方法,那便是Activity+ Navigation + Fragment。尽管你看着仅仅少了一个控件罢了,但实践上,大部分的作业都是由Navigation (导航)来完结。
说了这么多也该正式操作了,既然是写博客,天然要详细一些了,那么咱们就从创立AS项目开始吧。这应该够详细了吧,首要创立一个名为AppHomeNavigation的项目。如下图所示,包名我就缩减了一下。 创立好之后如下图所示: 从这个图来看,项目自身没有任何问题,为了保险起见,建议先运转一下。
1. 增加依靠
Navigation 是JetPack中的组件,感兴趣能够去检查Google JetPack官方文档。而假如你想单独检查的Navigation 内容,能够点击Navigation 文档。
翻开你的app下的build.gradle。在dependencies闭包中增加如下依靠:
def nav_version = "2.3.2"
// navigation依靠 ui 和 fragment
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
增加位置如下图所示:
增加好之后,点击Sync进行项目同步,同步时会自动下载这些依靠库并装备到你的项目中。
增加完了依靠,就得先来简略介绍一下这个Navigation了,Navigation分为三大件:导航图、NavHost、NavController。
为了方便我介绍下面的三个概念,这儿假设有A、B、C三个Fragment。
现在要从A切换到B
导航图:读取这个切换方针及途径 NavHost:包括A、B、C的容器,用于显现Fragment。 NavController:在得知切换方针时,操控NavHost去显现B这个Fragment。
这么一说,你是否有一些了解了呢?
2. 增加导航图
鼠标右键点击res → New → Android Resource File 然后会弹出一个窗体,在这个窗体里边设置文件名称,并选择文件类型,然后点击OK。 然后检查这个nav_graph.xml,你会发现有报错 不过先不必忧虑,由于这个里边是用来指向Fragment的,可是现在没有,那就创立。 然后先在com.llw.navigation下新建一个fragment包。 然后建一个Fragment类,这儿命名我就用ABCDE来命名了,实践开发中是必定不能这样的。
然后在layout新建一个布局fragment_a.xml 然后修正一下这个布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="A Fragment"
android:textColor="#000"
android:textSize="24sp" />
</LinearLayout>
布局有了,然后进入到AFragment中绑定这个布局的id。
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);
}
}
那么这个AFragment就弄好了,同样依照上面的过程创立BCDE的Fragment,这个重复的过程我就不写了,由于有灌水的涉嫌。 留意之前fragment_a.xml中我放了一个TextView用来表明这个是A,那么其他的xml中也要放置对应的BCDE,这样你切换的时分才干看到差异。
好了,下面能够翻开这个nav_graph.xml进行Fragment的增加,在navigation标签下增加对AFragment的增加。
<!--AFragment-->
<fragment
android:id="@+id/afragment"
android:name="com.llw.navigation.fragment.AFragment"
android:label="afragment"
tools:layout="@layout/fragment_a" />
上面的也很简略,id表明它在导航图的标识,name指明这个Fragment的途径,包名+类名。label便是标签罢了。layout便是绑定这个Fragment对应的布局。
这儿你必定回想,方才不是在AFragment的onCreateView方法的回来中指明这这个布局吗? 那么这儿又增加是为什么,由于你假如在导航图中指明晰某一个Fragment的布局,那么在代码中就能够不必指明,也能够两者都指明,但至少要有一个地方指明,所以我这样写是能够的。为了让看的人更了解罢了,尽管是多此一举。
那么这个nav_graph.xml的其他的Fragment也要增加,如下所示
<?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">
<!--AFragment-->
<fragment
android:id="@+id/afragment"
android:name="com.llw.navigation.fragment.AFragment"
android:label="afragment"
tools:layout="@layout/fragment_a" />
<!--BFragment-->
<fragment
android:id="@+id/bfragment"
android:name="com.llw.navigation.fragment.BFragment"
android:label="bfragment"
tools:layout="@layout/fragment_b" />
<!--CFragment-->
<fragment
android:id="@+id/cfragment"
android:name="com.llw.navigation.fragment.CFragment"
android:label="cfragment"
tools:layout="@layout/fragment_c" />
<!--DFragment-->
<fragment
android:id="@+id/dfragment"
android:name="com.llw.navigation.fragment.DFragment"
android:label="dfragment"
tools:layout="@layout/fragment_d" />
<!--EFragment-->
<fragment
android:id="@+id/efragment"
android:name="com.llw.navigation.fragment.EFragment"
android:label="efragment"
tools:layout="@layout/fragment_e" />
</navigation>
然后你会发现还报错,那么你能够现在navigation标签中增加
tools:ignore="UnusedNavigation"
它就不报错了,这句话的意思是未运用导航的许可。由于我现在还没有运用这个nav_graph.xml所以要加上这一句话告诉AS,让它放心。等咱们真正去运用时,是没有影响的,去不去掉都行。
3. 增加NavHost
这个在上面是介绍过的,它是用来装载和显现Fragment的,都知道Fragment是要依附在Activity上的,那么很明显这个NavHost也是要放在Activity中,那么下面翻开activity_main.xml。修正代码如下:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">
<!--NavHost-->
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
可能这儿你会比较生疏,这儿的id,等下要在MainActivity中指明的,这儿的name指明的是androidx.navigation.fragment.NavHostFragment,这个特点就表明这个fragment指明的便是NavHost,然后它还要增加需要显现的子Fragment,那么就经过navGraph来绑定这个导航图,之前导航图里边不是就有五个Fragment吗?所以这样NavHost的任务就完结了。
可是这时分又有一个问题,那便是我的这个NavHost初始显现哪一个Fragment,这一点Google的人也想到了,能够在导航图中指明。
翻开nav_graph.xml。经过startDestination来指明发动Activity时显现的第一个Fragment。
app:startDestination="@id/afragment"
这儿我指明晰AFragment。那么值钱提到的三大件,就还差一个NavController。这个是用来操控NavHost显现Fragment,尽管我方才在导航图nav_graph.xml中指明晰第一个要显现的Fragment,可是它还短少这个显现的动机,而这个动机由NavController来供给。
4. NavController操控显现Fragment
进入到MainActivity,在onCreate增加一句代码:
//获取navController
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
这时分你运转代码,然后你就会发现,显现了AFragment。
惊不惊喜意不意外?明明这个NavController还什么都没有做的,为什么就能够显现了呢?实践上它已经在作业了,仅仅你没有留意罢了。
Navigation.findNavController(this, R.id.nav_host_fragment);
经过这一行代码这个作业开关就已经翻开了,翻开中读取导航图中第一个要显现Fragment,然后显现在NavHostFragment中。
5. Fragment之间跳转并传值
平时在实践的开发中常常会从一个Fragment跳转到另一个Fragment,并且带一些参数过去,之前这些跳转都是比较麻烦的,需要自己去写一些业务逻辑,并且还很容易出问题,让人谈之色变。可是在Navigation中,这个状况得到了很大的改善。
那么详细来看一下是怎么做的,比如我现在从AFragment跳转到BFragment。
下面便是见证骚操作的时分了。翻开nav_graph.xml,修正AFragment。
<!--AFragment-->
<fragment
android:id="@+id/afragment"
android:name="com.llw.navigation.fragment.AFragment"
android:label="afragment"
tools:layout="@layout/fragment_a">
<!--增加动作-->
<action
android:id="@+id/action_afragment_to_bfragment"
app:destination="@id/bfragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim" />
</fragment>
这儿又多了一个新的action标签,表明动作,id的命名要标准,从这个命名来看就知道要从A跳转到B。然后便是destination特点,它指明一个跳转到的Fragment。enterAnim表明进入BFragment的动画,exitAnim表明退出BFragment的动画,这些都是Navgation中自带的。
现在动作写好了,那么下面就需要一个地方来触发这个动作,能够写一个简略的按钮来触发。
在fragment_a.xml中修正布局如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="A Fragment"
android:textColor="#000"
android:textSize="24sp" />
<Button
android:id="@+id/jumpBFragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="跳转到 BFragment"
android:textAllCaps="false"
android:textSize="16sp" />
</LinearLayout>
然后进入到AFragment中,绑定id,增加点击工作。代码如下:
package com.llw.navigation.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import com.llw.navigation.R;
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);
Button jumpBFragment = view.findViewById(R.id.jumpBFragment);
jumpBFragment.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Navigation.findNavController(getView())
//导航
.navigate(R.id.action_afragment_to_bfragment);
}
});
}
}
这儿仅有欠好了解的便是navigate,表明导航的意思,这儿面我传入了方才界说在nav_graph.xml中的action的id。就表明这个导航将要履行这个actiion,那么它就会跳转到BFragment。运转一下
很明显,越过去了,不过感觉还少了点什么,由于往常Fragment之间跳转时都会传递参数过去,那么这个也要传参数,而Navigation也供给了这个功能,能够经过Bundle进行传参。
所以只需简略的修正这个点击的方法就能够了。
jumpBFragment.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bundle bundle = new Bundle();
bundle.putString("content","How are you?");
Navigation.findNavController(getView())
//导航
.navigate(R.id.action_afragment_to_bfragment,bundle);
}
});
这种Bundle传递参数我相信都不会生疏,那么在BFragment怎么去接收呢?
翻开BFragment,修正代码如下:
package com.llw.navigation.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.llw.navigation.R;
public class BFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
String content = getArguments().getString("content");
Toast.makeText(getActivity(),content,Toast.LENGTH_SHORT).show();
return inflater.inflate(R.layout.fragment_b, container, false);
}
}
这儿经过
getArguments().getString("content");
来获取方才放入Bundle里边的值,你曾经可能没有见过这个方法,可是你只需把getArguments当成是getBundle()就好了解了,由于它实践上便是回来了一个Bundle。 然后看一下运转的作用吧。
能够看到是不是已经传递了传输过来,简略吧。
现在你会发现跳转是没有问题,可是回退呢?怎么回去呢?假如你这个时分在BFragment点击体系的回来键,你会发现直接退出当时使用了,由于这个时分Fragment仍是属于MainActivity,那么它运用的便是Activity的回来栈,可当时只要一个Activity,所以它就退无可退,只能封闭使用,回到桌面了。为了解决这个问题,Navigation也供给了一个特点,在activity_main.xml中的的fragment中增加一个特点
app:defaultNavHost="true"
然后这个时分你再试一下,从A到B,然后点击体系回来键,就会回来到A,然后再点一下回来键就会退出当时使用。
神不神奇?app:defaultNavHost=”true”表明这个回退栈由NavController来管理,当这个退无可退时才会调用Activity的回退栈。默许便是false,能够不加。
6. 增加底部导航
鼠标右键点击res → New → Android Resource File,然后选择Menu
之后翻开menu.xml去增加子项。
<?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/afragment"
android:icon="@mipmap/ic_home"
android:title="主页" />
<item
android:id="@+id/bfragment"
android:icon="@mipmap/ic_category"
android:title="类别" />
<item
android:id="@+id/cfragment"
android:icon="@mipmap/ic_find"
android:title="发现" />
<item
android:id="@+id/dfragment"
android:icon="@mipmap/ic_msg"
android:title="音讯" />
<item
android:id="@+id/efragment"
android:icon="@mipmap/ic_mine"
android:title="我的" />
</menu>
之前我是有5个Fragment,那么这儿增加5个item,并且item的id要和之前导航图的fragment的id保持一致。这儿面的图标其实很容易搞到。
ic_home.png
ic_category.png
ic_find.png
ic_msg.png
ic_mine.png
现在menu创立好了,那么能够在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">
<!--NavHost-->
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/bottom_navigation"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<!--底部导航-->
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_alignParentBottom="true"
android:background="#FFF"
app:menu="@menu/menu" />
</RelativeLayout>
经过menu来指定导航栏的菜单,这样就把方才的item都增加进去了
现在你经过预览已经能够看到这个底部导航栏了,不是吗?不过保险起见,我仍是运转一手。
能够看到,底部的导航栏已经出来了,并且还能够点击,点击之后还有动画作用,并且图标和文字的色彩还有变化,由于实践上我仅仅放了灰色图标罢了。那么这些作业就都是BottomNavigationView帮咱们完结的,的确是省了不少工作,当然这个动画作用和点击之后的色彩都是能够让开发者自行改的。这是Google要做的UI统一,经过material来实现一些作用和动画。
7. 底部导航栏操控Fragment切换
在上面已经增加了底部导航栏,可是这个导航栏还没有和NavHost绑定起来,所以天然就无法在切换底部导航的一起,改变NavHost中的Fragment。进入到MainActivity。
public class MainActivity extends AppCompatActivity {
BottomNavigationView bottomNavigation;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bottomNavigation = findViewById(R.id.bottom_navigation);
//获取navController
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
//经过setupWithNavController将底部导航和导航操控器进行绑定
NavigationUI.setupWithNavController(bottomNavigation,navController);
}
}
像这样绑定之后,你现在点击底部导航之后,NavController就会操控NavHost去显现相应的Fragment。
不过在运转之前把BFragment中接收参数并且弹Toast的代码删掉,不然切换的时分拿不到这个参数,就会ANR。
运转看看吧。
下面来改一下切换后的图标色彩和文字色彩吧。
右键点击drawable然后新建一个menu_item_selected.xml。
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#888888" android:state_checked="false" />
<item android:color="#1296DB" android:state_checked="true" />
</selector>
意思很简略,便是设置未选中和选中时的色彩。进入activity_mian.xml修正BottomNavigationView
<!--底部导航-->
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_alignParentBottom="true"
android:background="#FFF"
app:itemIconTint="@drawable/menu_item_selected"
app:itemTextColor="@drawable/menu_item_selected"
app:labelVisibilityMode="labeled"
app:menu="@menu/menu" />
app:itemIconTint="@drawable/menu_item_selected"
app:itemTextColor="@drawable/menu_item_selected"
能够看到itemIconTint和itemTextColor别离表明图标和文字,这儿传入方才传入的色彩样式。
app:labelVisibilityMode="labeled"
这个labeled表明一向显现标签文字,它还有三种形式,别离是auto、selected、unlabeled。
auto表明自动,默许便是这种形式,selected和auto差不多,unlabeled表明一向不显现标签文字。能够自行去测验。
8. 运转作用图和源码
那么下面再运转一下吧。
能够看到现在有许多APP都是这样的作用。那么本篇文章要做的工作就做完了。
源码地址:AppHomeNavigation
总结
其实我这儿没有做经过滑动Fragment来切换BottomNavigationView。假如要做的话,就要加ViewPager来操控Fragment,而不是NavHost来操控了,那样就脱离了这个文章的目的了。并且运用ViewPager的话便是相当于你把BottomNavigationView替换RadioButton或许TabLayout来运用,这种方法也有许多,我就过多的说明晰,上高水长,后会有期~