【问题标题】:How to change AppBarLayout content based on Navigation library destination如何根据导航库目标更改 AppBarLayout 内容
【发布时间】:2019-01-29 01:03:15
【问题描述】:

我需要根据导航组件目标更改我的 AppBarLayout,例如,当用户转到 FragmentA 时,我想要将 TabLayout 添加到 AppBarLayout,例如 YouTube 应用程序

我尝试将 TabLayout 添加到 Activity 并根据导航目的地隐藏/显示它,但我发现它没用,因为我需要访问 Activity,之后我需要 findViewById 并设置 ViewPager 但我需要这样做新的 android Navigation 组件和干净的代码

这就是我实现导航组件的方式

    bnvActivityMainNavigation.setupWithNavController(navController)
    setupWithNavController(vnActivityMain, navController)
    setupActionBarWithNavController(navController)
    setupActionBarWithNavController(navController, dlActivityMainRoot)
    toolbar?.setNavigationOnClickListener { onSupportNavigateUp() }

有什么建议吗??

【问题讨论】:

  • 你能不能再请教一下,“我想当用户去 FragmentA 时添加 TabLayout 到 AppBarLayout”是什么意思
  • 我的意思是我有主要片段 FragmentA 这个片段包含一个按钮,并且当用户单击按钮时我需要的 AppBarLayout 中没有 TabLayout 将目的地从 FragmentA 移动到 FragmentB 并在 AppBarLayout 中显示 TabLayout 就像 YouTube 应用程序@图片
  • 尝试在 Main Activity 中创建一个 TabLayout,并在切换到 FragmentB 时将其隐藏在 FragmentA 中
  • @pic 这只是一个示例,我还需要基于片段添加 CollapsingToolbarLayout 和其他视图,因此我无法添加多个视图并显示/隐藏,我需要以干净的方式执行此操作

标签: android android-fragments kotlin android-jetpack android-architecture-navigation


【解决方案1】:

这是一个建议的解决方案,旨在将片段和活动的耦合限制在最低限度。

TabLayout 根据布尔 LiveData 对象显示或隐藏,该对象托管在 Activity 和每个片段目标共享的 ViewModel 中。
每个目的地负责为 LiveData 对象设置适当的布尔值,Activity 观察 LiveData 对象以显示或隐藏 TabLayout。

为了能够使用 TabLayout,片段声明了一个 Activity 必须实现的接口,其中包含一个用于获取 TabLayout 引用的方法。

这里是对应的代码示例,在 Kotlin 中。

MainActivityViewModel

class MainActivityViewModel internal constructor() : ViewModel() {

    val tabLayoutDestination = MutableLiveData<Boolean>()

    fun setTabLayoutDestination(newValue : Boolean) {
        //If the new value is the same, do not trigger an update
        if (Objects.equals(tabLayoutDestination.value, newValue)) return
        tabLayoutDestination.value = newValue
    }
}

示例片段

class ExampleFragment : Fragment() {

    private lateinit var viewPager: ViewPager
    private lateinit var activityViewModel : MainActivityViewModel
    private var tabLayout: TabLayout? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        ...
        val binding = FragmentExampleBinding.inflate(inflater, container, false)
        val context = context ?: return binding.root

        //Get the activity's ViewModel and specify that this destination requires a TabLayout
        val mainViewModelFactory = InjectorUtils.provideMainActivityViewModelFactory()
        activityViewModel = ViewModelProviders.of(activity!!, mainViewModelFactory)
                .get(MainActivityViewModel::class.java)
        activityViewModel.setTabLayoutDestination(true)

        viewPager = binding.viewpager

        return binding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        //If the parent activity implements the interface, get a reference to its TabLayout
        val parentActivity = if(activity is TabLayoutHost) activity as TabLayoutHost else return
        tabLayout = parentActivity.getTabLayoutReference()

        //Setup the TabLayout and ViewPager
        tabLayout?.setupWithViewPager(viewPager)
        viewPager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabLayout))
    }

    //The interface which must be implemented by the parent activity
    interface TabLayoutHost {
        fun getTabLayoutReference() : TabLayout
    }
}

MainActivity

class MainActivity : AppCompatActivity(), ExampleFragment.TabLayoutHost {

    private lateinit var activityViewModel : MainActivityViewModel
    private lateinit var tabLayout: TabLayout

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityMainBinding = DataBindingUtil.setContentView(this,
                R.layout.activity_main)

        //Get the viewmodel
        val mainViewModelFactory = InjectorUtils.provideMainActivityViewModelFactory()
        activityViewModel = ViewModelProviders.of(this, mainViewModelFactory)
                .get(MainActivityViewModel::class.java)

        //Get a reference to the TabLayout
        TabLayout = binding.tabLayout

        //Subscribe to the boolean livedata, to be able to make 
        //the appropriate UI changes according to the fragment displayed
        activityViewModel.tabLayoutDestination.observe(this, Observer { 
                if(it == true ) updateUiForTabLayoutDestination() 
                else updateUiForOtherDestination() 
        })
    }

    override fun getTabLayoutReference() = tabLayout

    private fun updateUiForTabLayoutDestination() {
        tabLayout.visibility = View.VISIBLE
    }

    private fun updateUiForOtherDestination() {
        tabLayout.visibility = View.GONE
    }
}

这些示例使用了几个 jetpack 组件(LiveData、ViewModel 和 Binding),并且源自 Google 提供的 sunflower sample app

LiveData 解决方案的灵感来自this answer

这里给出的示例是针对 TabLayout 的,但我通过获取对整个 AppBarLayout 的引用并调用 findViewById 来检索每个视图,成功地将其应用于其他视图(进度条和固定表头)。

【讨论】:

  • 这只是我的第二个 SO 答案,它可能会得到改进,感谢 cmets 和更正。具体来说,我不确定 onActivityCreated 是调用接口的最佳生命周期步骤,或者 Object.equals 是否对 Kotlin 中的布尔比较有用。
猜你喜欢
  • 2013-05-05
  • 2020-03-09
  • 1970-01-01
  • 2022-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多