首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用视图pager2和嵌套导航正确使用导航组件

使用视图pager2和嵌套导航正确使用导航组件
EN

Stack Overflow用户
提问于 2020-05-06 15:01:48
回答 3查看 7.6K关注 0票数 19

我试图使用ViewPager2 + FragmentStateAdapter +导航组件构建以下视图结构/导航。

先决条件:单一活动体系结构,有一个导航图

1.片段A持有一个视图寻呼机。视图寻呼机使用FragmentStateAdapter。

2.片段B是通过FragmentStateAdapter (视图寻呼机中的“lives”)实例化的。

3.片段C -应该从片段B导航到B->这就是问题所在。

方法1:从片段B声明的ViewPager2 + FragmentStateAdapter +导航

代码语言:javascript
复制
    <fragment
        android:id="@+id/fragmentA"
        android:name="com.abc.FragmentA"
        android:label="FragmentA" />

    <fragment
        android:id="@+id/fragmentB"
        android:name="com.abc.FragmentB"
        android:label="FragmentB">
        <action
            android:id="@+id/to_fragmentC"
            app:destination="@id/fragmentC" />
    </fragment>

    <fragment
        android:id="@+id/fragmentC"
        android:name="com.abc.FragmentC"
        android:label="FragmentC" />

FragmentB执行:

代码语言:javascript
复制
 FragmentBDirections
            .toFragmentC()
            .let { findNavController().navigate(it) }

结果:

代码语言:javascript
复制
App crash
java.lang.IllegalArgumentException: navigation destination com.abc:id/to_fragmentC is unknown to this NavController

方法2:从片段A声明的ViewPager2 + FragmentStateAdapter +导航

代码语言:javascript
复制
    <fragment
        android:id="@+id/fragmentA"
        android:name="com.abc.FragmentA"
        android:label="FragmentA" >
        <action
            android:id="@+id/to_fragmentC"
            app:destination="@id/fragmentC" />
    </fragment>

    <fragment
        android:id="@+id/fragmentB"
        android:name="com.abc.FragmentB"
        android:label="FragmentB">
    </fragment>

    <fragment
        android:id="@+id/fragmentC"
        android:name="com.abc.FragmentC"
        android:label="FragmentC" />

FragmentB执行:

代码语言:javascript
复制
 FragmentADirections
            .toFragmentC()
            .let { findNavController().navigate(it) }

结果:

代码语言:javascript
复制
App navigates to FragmentC, but when i hit the back button , it crashes with :
java.lang.IllegalArgumentException
        at androidx.core.util.Preconditions.checkArgument(Preconditions.java:36)
        at androidx.viewpager2.adapter.FragmentStateAdapter.onAttachedToRecyclerView(FragmentStateAdapter.java:132)
        at androidx.recyclerview.widget.RecyclerView.setAdapterInternal(RecyclerView.java:1209)
        at androidx.recyclerview.widget.RecyclerView.setAdapter(RecyclerView.java:1161)
        at androidx.viewpager2.widget.ViewPager2.setAdapter(ViewPager2.java:461)
        at com.abc.FragmentA.viewCreated(FragmentA.kt:69)

方法3: ViewPager + FragmentStatePagerAdapter (不推荐)+从片段B声明的导航

结果与方法1相同。

方法4:从片段A声明ViewPager + FragmentStatePagerAdapter (不推荐)+导航

这个真的能用。此外,导航回运行良好。

这里的问题是:

必须为不推荐的

  • 导航适配器的FragmentB ->的每个父片段定义可扩展的
  • 用法。

如果有人知道这个问题的一些优雅的解决方案,我会很高兴得到任何提示。

谢谢

EN

回答 3

Stack Overflow用户

发布于 2022-01-04 08:12:46

您不需要在图中将片段B声明为目的地,因为您从不使用NavController导航到它。您可以使用目标id来实现片段B中的导航,比如findNavController().navigate(R.id.fragmentC),而不是使用操作。findNavController方法将找到父片段的navController来执行导航。

票数 2
EN

Stack Overflow用户

发布于 2020-11-03 15:32:19

我不知道您是否找到了解决问题的方法,但我在我的应用程序中就是这样做的(这与您对解决方案4的描述类似)

我有一个应用程序,它有一个主片段(HomeFragment),其中包含一个底部选项卡条,它显示了四个不同的片段。

在使用应用程序的任何时候,用户都可以登录/注销(这是一个包含4个片段的流),回答问卷(2个片段),显示设置(一个片段)或拍照(1个片段)

我已经声明了我的家庭视图模型如下:

代码语言:javascript
复制
class HomeViewModel(app:Application):AndroidViewModel(app), HomeParent {
}


intefrace HomeParent { //this contains all the actions that can be executed, like showLogin, showQuestions etc etc
    fun ShowLogin()
 
}

在HomeFragment中,我初始化寻呼机,如下所示:

代码语言:javascript
复制
class HomeFragment: Fragment(), KoinComponent {
    private val vm by sharedViewModel<HomeViewModel>()

    private val pager by lazy { binding.pager }  //I just use view bindings you can do it with findviewbyid
    private val adapter by lazy { HomePageAdapter(this) }

    
    override fun initView(state:Bundle?) { //this is run before oncreate finishes
         pager.setPageTransformer(this::pageAnimation) //this is just to animate transitions
    }

    override fun setUpListeners() {  //this is run after oncreate finishes
        bottomNav.setOnNavigationItemSelectedListener(this::navigationItemSelected) //this does stuff and sends it to vm
        pager.registerOnPageChangeCallback(pagerChangeCallback)
    }

    override fun onResume() {
        super.onResume()
        pager.adapter = adapter
        (vm.state.value as? HomeState.Default)?.let { //I'm using mvi, this basically holds the current position in the pager
            pager.setCurrentItem(it.position.position, false)
        }
    }

    override fun onPause() {
        super.onPause()
        pager.adapter = null //I don't remember why I did this, I gues
    }

    override fun onDestroy() {
        super.onDestroy()
        pager.unregisterOnPageChangeCallback(pagerChangeCallback)
    }

}

实际适配器如下所示:

代码语言:javascript
复制
class HomePageAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
    override fun getItemCount() = 4

    override fun createFragment(position: Int): Fragment {
        return when (position) {
            0 -> Fragment1()
            1 -> Fragment2()
            2 -> Fragment3()
            3 -> Fragment4()
            else -> throw Throwable("Invalid position $position")
        }
    }
}

每一个片段都是这样创建的:

代码语言:javascript
复制
class Fragment1 :Fragment(),
    private val home : HomeParent by sharedViewModel<HomeViewModel>()
    override val vm: Fragment1ViewModel by viewModel { parametersOf(home) }

如您所见,我将home视图模型作为参数传递给home内的每个选项卡。

那么每个片段都可以做一些类似的事情

代码语言:javascript
复制
home.ShowLogin()

这将将消息发送到HomeViewModel,而后者又将消息发送到HomeFragment,它将调用以下内容

代码语言:javascript
复制
findNavController().navigate(HomeFragmentDirections.logIn())

因此,如您所见,Fragment1/2/3/4在整个导航过程之外,HomeFragment包含将要执行的所有操作

我希望我的解释是正确的,如果有人有任何问题或需要澄清,请告诉我。

票数 0
EN

Stack Overflow用户

发布于 2021-03-11 06:25:19

通用思想

您可以在您的Channel中定义一个ViewModel来处理UI事件。

让我们看看如何才能做到这一点:

定义viewModel

代码语言:javascript
复制
@HiltViewModel
class OrderViewModel @Inject constructor(
    private val repository: Repository
) : ViewModel() {
    
    private val orderEventChannel = Channel<OrderEvent>()
    val orderEvent = orderEventChannel.receiveAsFlow()

    ...

    fun onInvoiceClicked(invoice: Invoice) = viewModelScope.launch {
        orderEventChannel.send(OrderEvent.NavigateToOrderDetailsFragment(invoice))
    }

    sealed class OrderEvent{
        data class NavigateToOrderDetailsFragment(val invoice: Invoice) : OrderEvent()
    }
}

  1. viewModel by activityViewModels并侦听包含viewPager2视图的Channel in片段:

代码语言:javascript
复制
@AndroidEntryPoint
class OrdersFragment : Fragment(R.layout.fragment_orders) {
    private val viewModel: OrderViewModel by activityViewModels()
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        ...
        
        viewLifecycleOwner.lifecycleScope.launchWhenStarted {
            viewModel.orderEvent.collect { event ->
                when(event){
                    is OrderViewModel.OrderEvent.NavigateToOrderDetailsFragment -> {
                        findNavController().navigate(
                            OrdersFragmentDirections.actionOrdersFragmentToOrderDetailsFragment(event.invoice)
                        )
                    }
                }
            }
        }
    }

  1. 现在在viewPager初始viewModel中绑定activityViewModels并调用onInvoiceClicked()函数:

的所有Fragment

代码语言:javascript
复制
class InProgressViewPagerFragment : Fragment(R.layout.fragment_in_progress_view_pager){
    private val viewModel: OrderViewModel by activityViewModels()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        ...
        binding.invoice.setOnClickListener{
           viewModel.onInvoiceClicked(invoice)
        }
    }
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61638513

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档