採用單個Activity嵌套多個Fragment的UI架構模式,已經被大多數的Android工程師所接受。可是,對於Fragment的管理一直是一件比較麻煩的事情,工程師須要經過FragmentManager和FragmentTransaction來管理Fragment之間的切換。java
在Android中,頁面的切換和管理包括應用程序Appbar的管理、Fragment的動畫切換以及Fragment之間的參數傳遞等內容。而且,純代碼的方式使用起來不是特別友好,而且Appbar在管理和使用的過程當中顯得很混亂。所以,Jetpack提供了一個名爲Navigation的組件,旨在方便開發者管理Fragment頁面和Appbar。android
相比以前Fragment的管理須要藉助FragmentManager和FragmentTransaction,使用Navigation組件有以下一些優勢:web
在正式學習Navigation組件以前,咱們須要對Navigation的主要元素有一個簡單的瞭解,Navigation主要由三部分組成。shell
首先,新建一個Android項目,而後在build.gradle中引入以下依賴腳本。瀏覽器
dependencies { def nav_version = "2.3.2" // Java language implementation implementation "androidx.navigation:navigation-fragment:$nav_version" implementation "androidx.navigation:navigation-ui:$nav_version" // Kotlin implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$nav_version" }
在【Project】窗口中,res 目錄下右鍵而後依次選擇 【New】->【Android Resource File】建立 New Resource File 對話框,以下圖所示。
在彈出的界面中,File name可隨意輸入,Resource type選擇Navigation,而後點擊肯定便可。
點擊肯定後,就會在res目錄下建立navigation目錄,以及導航文件nav_graph.xml。安全
打開新建的nav_graph.xml文件,在Design界面能夠看到目前尚未內容,能夠【依次點擊 New Destination】 圖標,而後點擊【 Create new destination】,便可快速建立新的Fragment,這裏分別新建了FragmentA、FragmentB、FragmentC三個fragment。架構
而後,可經過手動配置頁面之間的跳轉關係,點擊某個頁面,右邊會出現一個小圓點,拖曳小圓點指向跳轉的頁面,好比設置跳轉的關係爲FragmentA -> FragmentB -> FragmentC。
而後,咱們再切換到Code面板,能夠看到生成的代碼以下所示。app
<?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/fragmentA"> <fragment android:id="@+id/fragmentA" android:name="com.example.testnavigation.FragmentA" android:label="fragment_a" tools:layout="@layout/fragment_a" > <action android:id="@+id/action_fragmentA_to_fragmentB" app:destination="@id/fragmentB" /> </fragment> <fragment android:id="@+id/fragmentB" android:name="com.example.testnavigation.FragmentB" android:label="fragment_b" tools:layout="@layout/fragment_b" > <action android:id="@+id/action_fragmentB_to_fragmentC" app:destination="@id/fragmentC" /> </fragment> <fragment android:id="@+id/fragmentC" android:name="com.example.testnavigation.FragmentC" android:label="fragment_c" tools:layout="@layout/fragment_c" /> </navigation>
上面的生成的代碼中用到了幾個標籤,含義以下。ide
咱們知道,Fragment須要有一個Activity容器才能正常運行,NavHostFragment就是承載導航內容的容器,而且它須要和Activity綁定。工具
<?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"> <fragment android:id="@+id/fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:navGraph="@navigation/nav_graph" /> </androidx.constraintlayout.widget.ConstraintLayout>
其中, android:name="androidx.navigation.fragment.NavHostFragment"
這行代碼的做用就是告訴Android系統這是一個特殊的Fragment。而且當 app:defaultNavHost="true"
屬性爲true時,該Fragment會自動處理系統返回。
而後咱們運行程序,能夠看到默認展現的是FragmentA頁面,這是由於MainActivity的佈局文件中配置了NavHostFragment,而且給NavHostFragment指定了默認展現的頁面爲FragmentA。
NavController主要用來管理fragment之間的跳轉,每一個 NavHost 均有本身的相應 NavController。能夠經過findNavController來獲取NavController,而後使用NavController的navigate或者navigateUp方法來進行頁面之間的路由操做。
打開FragmentA.java文件,而後在onViewCreated生命週期方法中添加以下代碼。
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_a, container, false); Button btnB = view.findViewById(R.id.btn_b); btnB.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Navigation.findNavController(v).navigate(R.id.action_fragmentA_to_fragmentB); } }); return view; }
運行上面的代碼,而後點擊FragmentA頁面上的按鈕,系統就會經過Navigation.findNavController(v).navigate()
方法導航到FragmentB。
在Fragment之間進行跳轉時,還能夠添加跳轉的動畫。打開nav_graph.xml文件的Design選項,而後在Attributes 面板的 Animations 部分中,點擊要添加的動畫旁邊的下拉箭頭,開發者能夠從如下類型中進行選擇,以下圖所示。
其中,Attributes面板涉及的屬性含義以下。
而後,打開Code面板,生成的代碼以下。
<fragment android:id="@+id/fragmentA" android:name="com.xzh.jetpack.FragmentA" android:label="fragment_a" tools:layout="@layout/fragment_a" > <action android:id="@+id/action_fragmentA_to_fragmentB" app:destination="@id/fragmentB" app:enterAnim="@android:anim/slide_in_left" app:exitAnim="@android:anim/slide_out_right" app:popEnterAnim="@anim/fragment_fade_enter" app:popExitAnim="@anim/fragment_fade_exit" /> </fragment>
在Android中,頁面之間若是要傳遞數據,建議傳遞最少許的數據,由於在 Android 上用於保存全部狀態的總空間是有限的。若是您須要傳遞大量數據,可使用 ViewModel。
Fragment的切換常常伴隨着參數的傳遞,爲了配合Navigation組件在切換Fragment時傳遞參數,Android Studio爲開發者提供了Safe Args和Bundle兩種參數傳遞方式。
使用Bundle傳遞數據時,首先建立 Bundle 對象,而後使用 navigate() 將它傳遞給目的地,以下所示。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) tv.setOnClickListener { Bundle bundle = new Bundle(); bundle.putString("key", "from fragmentA"); Navigation.findNavController(v).navigate(R.id.action_fragmentA_to_fragmentB,bundle); } }
而後,接受方使用getArguments() 方法來獲取Bundle 傳遞的數據,以下所示。
String keyStr = getArguments().getString("key");
首先,在項目的 build.gradle 中添加classpath配置,以下所示。
dependencies { def nav_version = "2.3.2" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" }
而後,在app的 build.gradle添加 apply plugin腳本,以下所示。
apply plugin: 'androidx.navigation.safeargs'
配置完成後記從新rebuild下項目,會生成{module}/build/generated/source/navigation-args/{debug}/{packaged}/{Fragment}Dircetions
,以下所示。
若是須要往目的頁面傳遞數據,首先請按照如下步驟將參數添加到接收它的目的頁面中。Navigation提供了一個子標籤argument能夠用來傳遞參數。
首先,在 Navigation Editor 中,點擊接收參數的目的頁面,在 Attributes 面板中,點擊 Add (+)。而後,在顯示的 Add Argument Link 窗口中,輸入參數名稱、參數類型、參數是否可爲 null,以及默認值(若是須要)點擊 【Add】按鈕,以下所示。
點擊 Text 標籤頁切換到 XML 視圖,會發現生成的代碼以下。
<argument android:name="key" app:argType="string" app:nullable="false" android:defaultValue="navigation參數傳遞" />
而後,咱們在FragmentA.java中使用以下代碼傳遞數據,以下所示。
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_a, container, false); Button btnB = view.findViewById(R.id.btn_b); btnB.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { FragmentADirections.ActionFragmentAToFragmentB action=FragmentADirections.actionFragmentAToFragmentB().setKey("經過safeArgs進行參數傳遞"); Navigation.findNavController(v).navigate(action); } }); return view; }
在接受的頁面的onCreate方法中使用以下的方法進行接受。
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { String name= FragmentBArgs.fromBundle(getArguments()).getKey(); } }
當應用程序接受到某個通知推送,但願用戶在點擊該通知時,可以直接跳轉到展現該通知內容的頁面,這就是深層連接DeepLink最多見的場景,Navigation組件提供了對深層連接(DeepLink)的支持。
DeepLink有兩種應用場景,一種是PendingIntent,另外一種是真實的URL連接,利用這兩種方式均可以跳轉到程序中指定的頁面。
PendingIntent方式通常用在消息通知中,當應用程序接收到某個通知時,而且但願用戶在單擊該通知時直接跳轉到到指定的頁面,那麼就能夠經過PendingIntent來完成。
例如,下面的代碼實現功能是,在MainActivity中單擊按鈕彈出通知欄,點擊通知欄跳轉到指定NotificationActivity頁面中,代碼以下。
public class MainActivity extends AppCompatActivity { NotificationManager manager=null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private void init() { manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){ //注意通知渠道一旦創建就無權修改 NotificationChannel channel =new NotificationChannel("normal","Normal",NotificationManager.IMPORTANCE_DEFAULT); manager.createNotificationChannel(channel); NotificationChannel channel2 =new NotificationChannel("important","Important",NotificationManager.IMPORTANCE_HIGH); manager.createNotificationChannel(channel2); } Button pending=findViewById(R.id.btn_pendingintent); pending.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendNotification(); } }); } void sendNotification(){ Intent intent =new Intent(this,NotificationActivity.class); PendingIntent pi = PendingIntent.getActivity(this,0,intent,0); //使用NotificationCompat兼容8.0之前的系統 // val notification = NotificationCompat.Builder(this,"normal") Notification notification = new NotificationCompat.Builder(this,"important") .setContentTitle("This is content title") .setContentText("This is content text") .setStyle(new NotificationCompat.BigTextStyle().bigText("Learn how to build notification,send any sync data,and use voice actions.Get the " + "official Android IDE and developer tools to build apps for Android.")) .setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher_background))) .setSmallIcon(R.drawable.ic_launcher_foreground) .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher_background)) .setContentIntent(pi) .setAutoCancel(true) .build(); manager.notify(1,notification); } }
而後,須要新建一個通知的目的頁面,爲了MainActivity作區分,咱們在NotificationActivity頁面僅放了一個TextView組件,以下所示。
public class NotificationActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_notification); } }
而後運行上面的代碼,最終的效果以下圖所示。
使用URL連接方式,當用戶經過手機瀏覽器瀏覽網站上的某個頁面時,能夠經過網頁瀏覽器的方式打開對應的應用頁面。若是用戶的手機安裝有咱們得應用程序,那麼經過DeepLink就能打開相應的頁面;若是沒有安裝,那麼網站能夠導航到應用程序的下載頁面,從而引導用戶安裝應用程序。
首先,在導航圖中爲destination添加<deepLink/>
標籤,在app:uri屬性中填入的是你的網站的相應web頁面地址,以下所示。
<fragment android:id="@+id/deepLinkSettingsFragment" android:name="com.michael.deeplinkdemo.DeepLinkSettingsFragment" android:label="fragment_deep_link_settings" tools:layout="@layout/fragment_deep_link_settings"> <!-- 爲destination添加<deepLink/>標籤 --> <deepLink app:uri="www.YourWebsite.com/{params}" /> </fragment>
如上所示,app:uri
裏面填的是網站的相應Web頁面地址,{params}裏面的參數會經過Bundle對象傳遞到頁面中。
而後,爲相應的Activity設置<nav-graph/>
標籤,當用戶在Web中訪問到連接時,你的應用程序便能監聽到,以下所示。
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".DeepLinkActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> <!-- 爲Activity設置<nav-graph/>標籤 --> <nav-graph android:value="@navigation/nav_graph"/> </activity> </application>
通過上面的設置後,接下來就能夠測試咱們的功能了。咱們能夠在Google app中輸入相應的Web地址,也能夠經過adb工具,使用命令行來完成測試操做。
adb shell am start -a android.intent.action.VIEW -d "http://www.YourWebsite.com/fromWeb"
執行完命令,手機便能直接打開deepLinkSettingsFragment。在該Fragment中,咱們能夠經過Bundle對象獲取相應的參數(fromWeb),從而完成後續的操做。
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ View view = inflater.inflate(R.layout.fragment_deep_link_settings, container, false); Bundle bundle = getArguments(); if(bundle != null){ String params = bundle.getString("params"); TextView tvDesc = view.findViewById(R.id.tvDesc); if(!TextUtils.isEmpty(params)){ tvDesc.setText(params); } } return view; }