如果我使用视图绑定,如何找到 NavController?

How can I find NavController if I use view binding?

通常,我在代码A中使用val navController: NavController = findNavController(R.id.nav_host_fragment)来查找NavController,它基于R.id.nav_host_fragment

现在我在应用程序中使用视图绑定就像代码 B,如果我使用视图绑定,我如何使用 NavController?

顺便说一句,在我看来 R.id.nav_host_fragment 在视图绑定中不可用,对吧?

代码A

class TasksActivity : AppCompatActivity() { 

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.tasks_act)

        val navController: NavController = findNavController(R.id.nav_host_fragment)
     }
 
}

代码B

class TasksActivity : AppCompatActivity() {
    
    private lateinit var binding: TasksActBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = TasksActBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)       
      
        //val navController: NavController = findNavController(R.id.nav_host_fragment)       
    }

}

tasks_act.xml

<androidx.drawerlayout.widget.DrawerLayout
    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/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".tasks.TasksActivity"
    tools:openDrawer="start">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <com.google.android.material.appbar.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:minHeight="?attr/actionBarSize"
                android:theme="@style/Toolbar"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
        </com.google.android.material.appbar.AppBarLayout>

        <fragment
            android:id="@+id/nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"

            app:defaultNavHost="true"
            app:navGraph="@navigation/nav_graph" />

    </LinearLayout>

    ..

</androidx.drawerlayout.widget.DrawerLayout>

我们正在使用 ViewBinding 来获取视图本身的引用。如果您在 TextView 中使用视图绑定,您将获得视图的引用。

在 NavCotroller 的情况下,如果您使用 Viewbinding,您将获得对片段的引用,而 findNavController 需要整数类型的 ViewId。

findNavController(@IdRes viewId: int)

代码 B 应该仍然可以正常工作。

我查看了 findNavController() 。这是一个用于简化代码的扩展函数,扩展函数代码为

fun Activity.findNavController(@IdRes viewId: Int): NavController =
    Navigation.findNavController(this, viewId)

现在查看 NavigationfindNavController() 的代码,我们看到如下

 @NonNull
    public static NavController findNavController(@NonNull Activity activity, @IdRes int viewId) {
        View view = ActivityCompat.requireViewById(activity, viewId);
        NavController navController = findViewNavController(view);
        if (navController == null) {
            throw new IllegalStateException("Activity " + activity
                    + " does not have a NavController set on " + viewId);
        }
        return navController;
    }

我们在参数中传递的 viewId 在第一行使用

View view = ActivityCompat.requireViewById(activity, viewId);

现在查看 ActivityCompat 内部的 requireViewById() 我们看到

  @NonNull
    public static <T extends View> T requireViewById(@NonNull Activity activity, @IdRes int id) {
        if (Build.VERSION.SDK_INT >= 28) {
            return activity.requireViewById(id);
        }

        T view = activity.findViewById(id);
        if (view == null) {
            throw new IllegalArgumentException("ID does not reference a View inside this Activity");
        }
        return view;
    }

for api 28 加上被调用的方法是

@NonNull
    public final <T extends View> T requireViewById(@IdRes int id) {
        T view = findViewById(id);
        if (view == null) {
            throw new IllegalArgumentException("ID does not reference a View inside this Activity");
        }
        return view;
    }

因此,只要将视图(其中存在 nav_host_fragment)附加到 activity,您为查找导航控制器而编写的代码就应该可以完全正常工作。

class TasksActivity : AppCompatActivity() {
    
    private lateinit var binding: TasksActBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = TasksActBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)       //you are attaching view to activity here , make sure you always call this before the next line , else you will get IllegalStateException 
      
        val navController: NavController = findNavController(R.id.nav_host_fragment)       
    }

}

我没有亲自测试代码,但据我所知,它应该工作得很好。

正确的工作方式如下:

必须从 supportFragmentManager 访问 FragmentContainerView:

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

val navController = navHostFragment.navController