Navigation Architecture Component 的一些概念
導(dǎo)航原則
任何應(yīng)用內(nèi)導(dǎo)航的目標(biāo)應(yīng)該是為用戶提供一致且可預(yù)測的體驗(yàn),為了實(shí)現(xiàn)這一目標(biāo),導(dǎo)航架構(gòu)組件可幫助您構(gòu)建符合以下幾個導(dǎo)航原則的應(yīng)用程序停巷。
- 堆棧用于表示應(yīng)用程序的“導(dǎo)航狀態(tài)”
- 前進(jìn)(Up)按鈕永遠(yuǎn)不會退出您的應(yīng)用
- 前進(jìn)(Up)和后退(Back)在您的應(yīng)用程序任務(wù)中是等效的
- 如果一直后退不能退出你的應(yīng)用胖眷,那就跟前進(jìn)沒什么兩樣了厌杜。
- 深度鏈接到目標(biāo)或?qū)Ш降酵荒繕?biāo)應(yīng)產(chǎn)生相同的堆棧
- 應(yīng)該是跟Activity任務(wù)棧管理一致的意思吧
目標(biāo) destination
一個destination是你在你的App上能導(dǎo)航到的任何地方奉呛,雖然destination通常是碎片(Fragment),但是Navigation Architecture Component也支持其它類型的destination:
- Activity
- 導(dǎo)航圖(navigation graph)或者次級導(dǎo)航圖(navigation subgraph)
- 自定義的destination類型
操作 action
一個導(dǎo)航圖與目標(biāo)之間的聯(lián)系稱作操作,下圖就展示了一個應(yīng)用的導(dǎo)航圖夯尽,來自于一個包含了6個目標(biāo)瞧壮,目標(biāo)之間被5個action連接的應(yīng)用
提示:如果要在Android Studio中使用導(dǎo)航架構(gòu)組件,則必須使用Android Studio 3.2 Canary 14或更高版本呐萌。不知道AS 3.2正式版好久出馁痴,應(yīng)該快了谊娇。
角色
NaviHostFragmnet
簡單例子
1. 實(shí)現(xiàn)類似首頁多Fragment的切換
效果
這個業(yè)務(wù)很常見肺孤,一般用于應(yīng)用首頁,但如圖切換的四個Fragment并不是前進(jìn)與后退關(guān)系济欢,而是同級橫向切換關(guān)系赠堵。
以前的實(shí)現(xiàn)方案
我自己考慮的實(shí)現(xiàn)方案是優(yōu)先使用Android官方庫
- 使用FragmentTabHost
設(shè)置好Tab和對應(yīng)的Fragment,FragmentTabHost可以自動幫我們做tab和Fragment之間的綁定,切換Fragment的業(yè)務(wù)代碼也給我們封裝好了法褥。不過FragmentTabHost的切換方案是先add所有的fragment,再執(zhí)行dettach上一個fragment茫叭,attach要切換的fragment。所以每次切換到fragment半等,fragment的生命周期都會從onCreateView開始執(zhí)行揍愁,一直執(zhí)行到onResume,這一點(diǎn)也要考慮下杀饵。
- 使用BottomNavigationView + 自己寫的切換代碼
FragmentTabHost雖然幫我們實(shí)現(xiàn)了切換莽囤,但是切換Fragment的方式也是固定的,如果我想fragment之前已經(jīng)被add顯示過,之后再切換到它切距,不執(zhí)行它的onCreateView直接show行不行朽缎,當(dāng)然行,那就要自己的這份代碼了谜悟。
- 使用TabLayout + 自己寫的切換代碼
這個跟方案二差不多话肖,不過通常都搭配ViewPager使用
- 自定義Tab + 自己寫的切換代碼
這個較2,3方案就是導(dǎo)航的View也是自己定義葡幸,一般用于簡單的導(dǎo)航需求最筒。
試試BottomNavigationView+Navigation 新方案
我比較關(guān)注Navigation是怎樣實(shí)現(xiàn)Fragment切換的
1.創(chuàng)建NavHostFragment
NavHostFragment可以看作放置顯示Fragment的位置容器控件,本質(zhì)就是個Fragment。
跟Fragment的用法差不多训措,有兩種創(chuàng)建方式
- 靜態(tài)方式,貼在xml布局文件上
比如我這里卢鹦,activity_main.xml:
<?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:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent"
tools:context=".MainActivity">
<fragment
android:layout_width="match_parent"
android:id="@+id/main_nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true"
android:layout_weight="1"
android:layout_height="wrap_content"/>
<android.support.design.widget.BottomNavigationView
android:layout_width="match_parent"
android:id="@+id/main_bottom_nav"
app:menu="@menu/main_bottom_nav"
android:background="@drawable/main_nav_background"
android:layout_height="60dp"/>
</LinearLayout>
Activity創(chuàng)建的時候會自動解析xml,把fragment添加進(jìn)Fragmentmanger,在Activity中悄泥,通過 (NavHostFragment) fragmentManager.findFragmentById(R.id.main_nav_host)就能獲取到你添加的NavHostFragment實(shí)例虏冻。
- java代碼動態(tài)創(chuàng)建
官方例子:
NavHostFragment finalHost = NavHostFragment.create(R.navigation.example_graph);
getSupportFragmentManager().beginTransaction()
.replace(R.id.nav_host, finalHost)
.setPrimaryNavigationFragment(finalHost) // this is the equivalent to app:defaultNavHost="true"
.commit();
NavHostFragment有create方法獲取一個新實(shí)例,然后添加到fragmentManager里弹囚,跟xml配置差不多厨相,還是需要一個容器,setPrimaryNavigationFragment(finalHost)是設(shè)置back鍵攔截
一些參數(shù)設(shè)置
BottomNavigationView的菜單參數(shù)鸥鹉,main_bottom_nav.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/oneFragment"
android:icon="@drawable/ic_one"
android:title="@string/title_one" />
<item
android:id="@+id/twoFragment"
android:icon="@drawable/ic_two"
android:title="@string/title_two" />
<item
android:id="@+id/threeFragment"
android:icon="@drawable/ic_three"
android:title="@string/title_three" />
<item
android:id="@+id/fourFragment"
android:icon="@drawable/ic_four"
android:title="@string/title_four" />
</menu>
四個Fragment
代碼略
naviHostFragment的參數(shù)
NaviHostFragment的navGraph參數(shù)蛮穿,像菜單一樣也可用xml寫,這是很方便的毁渗,用xml就意味著AS有GUI界面能夠讓你拖拽點(diǎn)點(diǎn)點(diǎn)生成代碼践磅。
所以現(xiàn)在項(xiàng)目的res又有了新類型的資源文件:navigation
如果需要AS支持navigation資源文件編輯,還要再Settings里面開啟Enable Navigation Editor
比如我現(xiàn)在四個碎片的例子灸异,nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="@id/oneFragment">
<fragment
android:name="me.newtrekwang.navigationdemo.nav1.OneFragment"
android:id="@+id/oneFragment"
android:label="one_fragment"
tools:layout="@layout/one_fragment"
/>
<fragment
android:name="me.newtrekwang.navigationdemo.nav2.TwoFragment"
android:id="@+id/twoFragment"
android:label="two_fragment"
tools:layout="@layout/two_fragment"
/>
<fragment
android:id="@+id/threeFragment"
android:name="me.newtrekwang.navigationdemo.nav3.ThreeFragment"
android:label="three_fragment"
tools:layout="@layout/three_fragment" />
<fragment
android:id="@+id/fourFragment"
android:name="me.newtrekwang.navigationdemo.nav4.FourFragment"
android:label="four_fragment"
tools:layout="@layout/four_fragment" />
</navigation>
我把navigation里包含的每個標(biāo)簽看作是目標(biāo)府适,所以目標(biāo)就必須有自己的id等屬性,比如fragment肺樟,id為目標(biāo)id,name為目標(biāo)完成類名檐春,label是標(biāo)簽,layout方便AS解析預(yù)覽么伯。
nav_graph預(yù)覽
可以看到預(yù)覽里疟暖,總動解析出host為MainActivity那個navHostFragment,以及四個我新定義的Fragment,oneFragment標(biāo)記為start,表示為起始顯示的Fragment.
因?yàn)楝F(xiàn)在是四個碎片之間橫向切換,所以他們之間沒有Action
BottomNavigationView與NavController綁定
回到MainActivity
public class MainActivity extends AppCompatActivity {
private BottomNavigationView bottomNavigationView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bottomNavigationView = findViewById(R.id.main_bottom_nav);
BottomNavigationViewHelper.disableShiftMode(bottomNavigationView);
NavHostFragment navHostFragment = (NavHostFragment)getSupportFragmentManager().findFragmentById(R.id.main_nav_host);
NavController navController = navHostFragment.getNavController();
NavigationUI.setupWithNavController(bottomNavigationView,navController);
}
@Override
public boolean onSupportNavigateUp() {
return Navigation.findNavController(this,R.id.main_nav_host).navigateUp();
}
}
Navigation Architecture Component 的設(shè)計(jì)是導(dǎo)航都通過NaviController負(fù)責(zé)導(dǎo)航田柔,NavController可以通過三種靜態(tài)方法獲得
- NavHostFragment.findNavController(Fragment)
- Navigation.findNavController(Activity, @IdRes int viewId)
- Navigation.findNavController(View)
NavHostFragment.findNavController(fragment)就是不斷遍歷fragment的getParentFragment()俐巴,直到找到一個NavHostFragment實(shí)例,然后返回它的navController.我們這里MainActivty很顯然可以直接獲取到navHostFragment硬爆,然后調(diào)用getNavController即刻欣舵。
Navigation.findNavController(Activity, @IdRes int viewId),Navigation.findNavController(View)其實(shí)是一個方法摆屯,最終會遍歷view的 view.getParent()邻遏,直到找到tag為NavController的View,然后返回controller虐骑。
所以獲取NavController的來源一種是NavHostFragment准验,另一種是tag為NaviController的View。
最后NavigationUI.setupWithNavController(bottomNavigationView,navController)廷没,NavigationUI把bottomNavigationView和navController綁定在一起糊饱,這樣一個碎片切換的業(yè)務(wù)幾行代碼就搞定了,大家可以實(shí)踐下颠黎,貌似比之前的方案少了很多代碼量另锋。
NavigationUI 怎么替我們綁定bottomNavigationView和navController的滞项?
NavigationUI替我們實(shí)現(xiàn)的切換是啥樣的?Fragment切換時的生命周期如何夭坪?令我有點(diǎn)小失望的是文判,每次切換到新Fragment都會new 一次Fragment,然后執(zhí)行它的生命周期方法室梅,是的戏仓,就是重新new 一次,再replace,你可以再Fragment里加幾個log驗(yàn)證一下亡鼠。