โต้ตอบกับคอมโพเนนต์การนำทางแบบเป็นโปรแกรม

คอมโพเนนต์การนำทางให้วิธีต่างๆ ในการสร้างและโต้ตอบโดยใช้โปรแกรม กับองค์ประกอบการนำทางบางอย่าง

สร้าง NavHostFragment

คุณสามารถใช้ NavHostFragment.create() เพื่อสร้าง NavHostFragment แบบเป็นโปรแกรม ด้วยแหล่งข้อมูลกราฟที่เฉพาะเจาะจง ดังที่แสดงในตัวอย่างด้านล่าง

Kotlin

val finalHost = NavHostFragment.create(R.navigation.example_graph)
supportFragmentManager.beginTransaction()
    .replace(R.id.nav_host, finalHost)
    .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true"
    .commit()

Java

NavHostFragment finalHost = NavHostFragment.create(R.navigation.example_graph);
getSupportFragmentManager().beginTransaction()
    .replace(R.id.nav_host, finalHost)
    .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true"
    .commit();

โปรดทราบว่า setPrimaryNavigationFragment(finalHost) ช่วยให้NavHost สกัดกั้นการกดปุ่มย้อนกลับของระบบ นอกจากนี้ คุณยังใช้ลักษณะการทำงานนี้ใน XML ของ NavHost โดยเพิ่ม app:defaultNavHost="true" หากคุณกำลังใช้ ลักษณะการทำงานของปุ่มย้อนกลับที่กำหนดเอง และไม่ต้องการให้ NavHost สกัดกั้นการกดปุ่ม "ย้อนกลับ" คุณก็สามารถข้าม null ถึง setPrimaryNavigationFragment()

เริ่มต้นด้วยการนำทาง 2.2.0 คุณสามารถรับการอ้างอิง NavBackStackEntry สำหรับปลายทางใดๆ ในสแต็กการนำทางโดยการเรียกใช้ NavController.getBackStackEntry(), ไปให้ถึงรหัสปลายทาง หากสแต็กด้านหลังมีอินสแตนซ์มากกว่า 1 รายการ ของปลายทางที่ระบุ getBackStackEntry() จะแสดงผลอินสแตนซ์ที่อยู่บนสุด จากกองซ้อน

NavBackStackEntry ที่แสดงผลระบุ Lifecycle ViewModelStore และ SavedStateRegistry ที่ระดับปลายทาง ออบเจ็กต์เหล่านี้จะใช้ได้ตลอดอายุการใช้งานของปลายทางในกลุ่มแบ็ก เมื่อปลายทางที่เกี่ยวข้องถูกดึงออกจาก Back Stack, Lifecycle จะถูกทำลาย, ระบบจะไม่บันทึกสถานะอีกต่อไป และล้างออบเจ็กต์ ViewModel

พร็อพเพอร์ตี้เหล่านี้จะให้ Lifecycle และร้านค้าสำหรับออบเจ็กต์ ViewModel รายการและ ชั้นเรียนที่ใช้ได้ สถานะที่บันทึกไว้ ไม่ว่าจะ ประเภทปลายทางที่คุณใช้ ซึ่งจะเป็นประโยชน์อย่างยิ่งเมื่อทำงานร่วมกับ ประเภทปลายทางที่ไม่มี Lifecycle เชื่อมโยงโดยอัตโนมัติ เช่น ปลายทางที่กำหนดเอง

ตัวอย่างเช่น คุณสามารถสังเกต Lifecycle ของ NavBackStackEntry ได้เช่นเดียวกับ คุณจะเห็น Lifecycle ของส่วนย่อยหรือกิจกรรม นอกจากนี้ NavBackStackEntry เป็น LifecycleOwner ซึ่งหมายความว่าคุณจะใช้บัญชีนี้ได้เมื่อ สังเกต LiveData หรือคอมโพเนนต์อื่นๆ ที่รับรู้ถึงวงจร ดังที่แสดงใน ตัวอย่างต่อไปนี้

Kotlin

myViewModel.liveData.observe(backStackEntry, Observer { myData ->
    // react to live data update
})

Java

myViewModel.getLiveData().observe(backStackEntry, myData -> {
    // react to live data update
});

สถานะของวงจรจะอัปเดตโดยอัตโนมัติทุกครั้งที่คุณเรียกใช้ navigate() สถานะของอายุการใช้งานสำหรับปลายทางที่ไม่ได้อยู่ที่ด้านบนสุดของแบ็กเอนด์ ย้ายจาก RESUMED ไปยัง STARTED หากปลายทางยังคงปรากฏภายใต้ ปลายทาง FloatingWindow เช่น ปลายทางของกล่องโต้ตอบ หรือไปยัง STOPPED หรือไม่เช่นนั้น

การแสดงผลลัพธ์ไปยังปลายทางก่อนหน้า

ในการนำทาง 2.3 ขึ้นไป NavBackStackEntry จะให้สิทธิ์เข้าถึง SavedStateHandle SavedStateHandle เป็นการแมปคีย์-ค่าที่ใช้จัดเก็บและเรียกข้อมูลได้ ค่าเหล่านี้จะยังคงอยู่แม้จะผ่านกระบวนการตายไปแล้ว ซึ่งรวมถึงการกำหนดค่า และยังคงเข้าถึงได้ผ่านออบเจ็กต์เดียวกัน โดยการใช้ตัวระบุ SavedStateHandle คุณสามารถเข้าถึงและส่งข้อมูลระหว่างปลายทางได้ ซึ่งจะเป็นประโยชน์อย่างยิ่งในการเป็นกลไกในการดึงข้อมูลกลับจาก ปลายทางหลังจากนำออกจากกอง

หากต้องการส่งข้อมูลกลับไปยังปลายทาง A จากปลายทาง B ก่อนอื่นให้ ตั้งค่าปลายทาง A เพื่อฟังผลลัพธ์ใน SavedStateHandle ในการดำเนินการนี้ ให้เรียก NavBackStackEntry โดยใช้ getCurrentBackStackEntry() API แล้ว observe ต่อ LiveData โดย SavedStateHandle

Kotlin

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    val navController = findNavController();
    // We use a String here, but any type that can be put in a Bundle is supported
    navController.currentBackStackEntry?.savedStateHandle?.getLiveData<String>("key")?.observe(
        viewLifecycleOwner) { result ->
        // Do something with the result.
    }
}

Java

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    NavController navController = NavHostFragment.findNavController(this);
    // We use a String here, but any type that can be put in a Bundle is supported
    MutableLiveData<String> liveData = navController.getCurrentBackStackEntry()
            .getSavedStateHandle()
            .getLiveData("key");
    liveData.observe(getViewLifecycleOwner(), new Observer<String>() {
        @Override
        public void onChanged(String s) {
            // Do something with the result.
        }
    });
}

ในปลายทาง B คุณต้องsetผลลัพธ์ใน SavedStateHandle ของ ปลายทาง A โดยใช้ getPreviousBackStackEntry() API

Kotlin

navController.previousBackStackEntry?.savedStateHandle?.set("key", result)

Java

navController.getPreviousBackStackEntry().getSavedStateHandle().set("key", result);

หากต้องการจัดการผลการค้นหาเพียงครั้งเดียว คุณต้องโทร remove() ใน SavedStateHandle เพื่อล้างผลลัพธ์ หากไม่นำ ผลลัพธ์ LiveData จะยังคงแสดงผลลัพธ์ล่าสุดไปยัง อินสแตนซ์ Observer รายการใหม่

ข้อควรพิจารณาเมื่อใช้ปลายทางของกล่องโต้ตอบ

เมื่อคุณnavigateไปยังจุดหมายที่ต้องการเห็นมุมมองทั้งหมดของ NavHost (เช่น ปลายทาง <fragment>) จุดหมายก่อนหน้า หยุดวงจรแล้ว ทำให้ไม่มีการเรียกกลับไปยัง LiveData โดย SavedStateHandle

แต่เมื่อไปยัง ปลายทางของกล่องโต้ตอบ ปลายทางก่อนหน้ายังปรากฏบนหน้าจอ ดังนั้นจึง STARTED แม้ว่าจะไม่ใช่ปลายทางปัจจุบัน ซึ่งหมายความว่าการโทรหา getCurrentBackStackEntry() จากภายในวิธีตลอดอายุการใช้งาน เช่น onViewCreated() จะแสดงผล NavBackStackEntry ของปลายทางกล่องโต้ตอบ หลังการเปลี่ยนแปลงการกำหนดค่าหรือประมวลผลการเสียชีวิตและการสันทนาการ (ตั้งแต่กล่องโต้ตอบ จะคืนค่าเหนือปลายทางอื่น) ดังนั้นคุณควรใช้ getBackStackEntry() รหัสปลายทางของคุณเพื่อให้คุณ ใช้ URL ที่ถูกต้อง NavBackStackEntry

และยังหมายความว่า Observer ใดก็ตามที่คุณตั้งค่าไว้ในผลลัพธ์ LiveData จะ ทริกเกอร์แม้ปลายทางของกล่องโต้ตอบจะยังอยู่บนหน้าจอ หากคุณ ต้องการตรวจสอบผลลัพธ์เฉพาะเมื่อปลายทางของกล่องโต้ตอบปิดอยู่และ ปลายทางที่สำคัญจะกลายเป็นปลายทางปัจจุบัน คุณสามารถสังเกต Lifecycle ที่เชื่อมโยงกับ NavBackStackEntry และเรียกดูผลลัพธ์ เมื่อเปลี่ยนเป็น RESUMED เท่านั้น

Kotlin

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val navController = findNavController();
    // After a configuration change or process death, the currentBackStackEntry
    // points to the dialog destination, so you must use getBackStackEntry()
    // with the specific ID of your destination to ensure we always
    // get the right NavBackStackEntry
    val navBackStackEntry = navController.getBackStackEntry(R.id.your_fragment)

    // Create our observer and add it to the NavBackStackEntry's lifecycle
    val observer = LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_RESUME
            && navBackStackEntry.savedStateHandle.contains("key")) {
            val result = navBackStackEntry.savedStateHandle.get<String>("key");
            // Do something with the result
        }
    }
    navBackStackEntry.lifecycle.addObserver(observer)

    // As addObserver() does not automatically remove the observer, we
    // call removeObserver() manually when the view lifecycle is destroyed
    viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_DESTROY) {
            navBackStackEntry.lifecycle.removeObserver(observer)
        }
    })
}

Java

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    NavController navController = NavHostFragment.findNavController(this);
    // After a configuration change or process death, the currentBackStackEntry
    // points to the dialog destination, so you must use getBackStackEntry()
    // with the specific ID of your destination to ensure we always
    // get the right NavBackStackEntry
    final NavBackStackEntry navBackStackEntry = navController.getBackStackEntry(R.id.your_fragment);

    // Create our observer and add it to the NavBackStackEntry's lifecycle
    final LifecycleEventObserver observer = new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
            if (event.equals(Lifecycle.Event.ON_RESUME)
                && navBackStackEntry.getSavedStateHandle().contains("key")) {
                String result = navBackStackEntry.getSavedStateHandle().get("key");
                // Do something with the result
            }
        }
    };
    navBackStackEntry.getLifecycle().addObserver(observer);

    // As addObserver() does not automatically remove the observer, we
    // call removeObserver() manually when the view lifecycle is destroyed
    getViewLifecycleOwner().getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
            if (event.equals(Lifecycle.Event.ON_DESTROY)) {
                navBackStackEntry.getLifecycle().removeObserver(observer)
            }
        }
    });
}

สแต็กย้อนกลับของการนำทางจะจัดเก็บ NavBackStackEntry ไม่เพียงสำหรับปลายทางแต่ละแห่ง แต่สำหรับการนำทางระดับบนสุดแต่ละรายการ ที่มีปลายทางแต่ละแห่ง ซึ่งจะช่วยให้คุณเรียกข้อมูล NavBackStackEntry ที่มีขอบเขตเป็นกราฟการนำทาง การนำทาง NavBackStackEntry ที่มีขอบเขตเป็นกราฟให้วิธีสร้าง ViewModel ที่ ที่กำหนดขอบเขตเป็นกราฟการนำทาง เพื่อช่วยให้คุณแชร์ข้อมูลที่เกี่ยวข้องกับ UI ระหว่าง ปลายทางของกราฟ ออบเจ็กต์ ViewModel ใดๆ ที่สร้างขึ้นในลักษณะนี้จะทำงานจนถึงวันที่ NavHostที่เชื่อมโยงและViewModelStoreจะถูกล้าง หรือจนกว่า กราฟการนำทางถูกดึงมาจากกลุ่มด้านหลัง

ตัวอย่างต่อไปนี้แสดงวิธีเรียกข้อมูล ViewModel ที่กำหนดขอบเขตเป็น กราฟการนำทาง:

Kotlin

val viewModel: MyViewModel
        by navGraphViewModels(R.id.my_graph)

Java

NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph);
MyViewModel viewModel = new ViewModelProvider(backStackEntry).get(MyViewModel.class);

หากคุณใช้การนำทาง 2.2.0 หรือรุ่นก่อนหน้า คุณต้องระบุข้อมูล จากโรงงานที่จะใช้ สถานะที่บันทึกไว้ด้วย ViewModels, ดังที่ปรากฏในตัวอย่างต่อไปนี้

Kotlin

val viewModel: MyViewModel by navGraphViewModels(R.id.my_graph) {
    SavedStateViewModelFactory(requireActivity().application, requireParentFragment())
}

Java

NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph);

ViewModelProvider viewModelProvider = new ViewModelProvider(
        backStackEntry.getViewModelStore(),
        new SavedStateViewModelFactory(
                requireActivity().getApplication(), requireParentFragment()));

MyViewModel myViewModel = provider.get(myViewModel.getClass());

ดูข้อมูลเพิ่มเติมเกี่ยวกับ ViewModel ได้ที่ ดูภาพรวมของโมเดล

การแก้ไขกราฟการนำทางที่พองขึ้น

คุณสามารถแก้ไขกราฟการนำทางที่สูงเกินจริงแบบไดนามิกในระหว่างรันไทม์

ตัวอย่างเช่น หากคุณมี BottomNavigationView ที่เชื่อมโยงกับ NavGraph ซึ่งเป็นปลายทางเริ่มต้นของฟิลด์ NavGraph จะบอกแท็บที่เลือกเมื่อเริ่มต้นแอป อย่างไรก็ตาม คุณอาจ ลบล้างลักษณะการทำงานนี้ เช่น เมื่อค่ากำหนดของผู้ใช้ระบุ แท็บที่ต้องการให้โหลดเมื่อเริ่มต้นแอป อีกวิธีหนึ่งคือ แอปอาจ จำเป็นต้องเปลี่ยนแท็บเริ่มต้นตามพฤติกรรมของผู้ใช้ในอดีต คุณสามารถ รองรับกรณีเหล่านี้ด้วยการระบุปลายทางเริ่มต้นของ NavGraph

ลองพิจารณา NavGraph นี้:

<?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/home">
    <fragment
        android:id="@+id/home"
        android:name="com.example.android.navigation.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" />
    <fragment
        android:id="@+id/location"
        android:name="com.example.android.navigation.LocationFragment"
        android:label="fragment_location"
        tools:layout="@layout/fragment_location" />
    <fragment
        android:id="@+id/shop"
        android:name="com.example.android.navigation.ShopFragment"
        android:label="fragment_shop"
        tools:layout="@layout/fragment_shop" />
    <fragment
        android:id="@+id/settings"
        android:name="com.example.android.navigation.SettingsFragment"
        android:label="fragment_settings"
        tools:layout="@layout/fragment_settings" />
</navigation>

เมื่อโหลดกราฟนี้ แอตทริบิวต์ app:startDestination จะระบุ HomeFragmentก็จะแสดง วิธีลบล้างปลายทางเริ่มต้น แบบไดนามิก ให้ทำดังต่อไปนี้

  1. ก่อนอื่น ให้เพิ่ม NavGraph ด้วยตนเอง
  2. ลบล้างปลายทางเริ่มต้น
  3. สุดท้าย ให้แนบกราฟเข้ากับ NavController ด้วยตนเอง

Kotlin

val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment

val navController = navHostFragment.navController
val navGraph = navController.navInflater.inflate(R.navigation.bottom_nav_graph)
navGraph.startDestination = R.id.shop
navController.graph = navGraph
binding.bottomNavView.setupWithNavController(navController)

Java

NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
        .findFragmentById(R.id.nav_host_fragment);

NavController navController = navHostFragment.getNavController();
NavGraph navGraph = navController.getNavInflater().inflate(R.navigation.bottom_nav_graph);
navGraph.setStartDestination(R.id.shop);
navController.setGraph(navGraph);
NavigationUI.setupWithNavController(binding.bottomNavView, navController);

ตอนนี้เมื่อแอปเริ่มทำงาน ShopFragment จะแสดงแทน HomeFragment

เมื่อใช้ Deep Link NavController จะสร้างแบ็กสแต็ก โดยอัตโนมัติสำหรับปลายทางของ Deep Link หากผู้ใช้ นำทางไปที่ลิงก์ในรายละเอียด แล้วย้อนกลับมาหา เริ่มต้นที่จุดใดจุดหนึ่ง การลบล้างปลายทางเริ่มต้นโดยใช้ ในตัวอย่างก่อนหน้านี้ จะช่วยให้การเริ่ม ปลายทางจะถูกเพิ่มลงในสแต็กด้านหลังที่สร้างขึ้น

โปรดทราบว่าเทคนิคนี้ยังทำให้สามารถลบล้างองค์ประกอบ NavGraph ตามที่จำเป็น ต้องทำการแก้ไขกราฟทั้งหมด ก่อนที่จะเรียกไปยัง setGraph() เพื่อให้แน่ใจว่าโครงสร้างที่ถูกต้อง ใช้เมื่อจัดการ Deep Link, คืนค่าสถานะ และย้ายไปยังจุดเริ่มต้น ปลายทางของกราฟของคุณ