当前位置: 首页 > 知识库问答 >
问题:

即使在onBackPress中,Jetpack Navigation Drawer也总是重新创建第一个片段

阮喜
2023-03-14

标题本身就是我的问题,每当我打开MainActivity,然后导航到汉堡包/抽屉菜单中可用的另一个片段,然后按/刷回以返回它重新创建的主屏幕(第一个片段)。导航组件有没有办法使其不重新创建第一个片段?我正在使用Android Studio生成的Jetpack导航模板,这似乎是默认行为。

这是主要活动

class MainActivity : AppCompatActivity() {

    private lateinit var appBarConfiguration: AppBarConfiguration
    private var _binding: ActivityMainBinding? = null

    // This property is only valid between onCreate and
    // onDestroyView.
    private val binding get() = _binding!!

    private lateinit var drawerLayout: DrawerLayout

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

        _binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        setSupportActionBar(binding.appBarMain.toolbar)

        drawerLayout = binding.drawerLayout
        val navView: NavigationView = binding.navView
        val navController = findNavController(R.id.nav_host_fragment_content_main)
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        appBarConfiguration = AppBarConfiguration(setOf(
                R.id.nav_home, R.id.nav_marketcap, R.id.nav_about), drawerLayout)
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)

    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.main, menu)
        menu.findItem(R.id.action_settings).isChecked = AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES
        return true
    }


    override fun onSupportNavigateUp(): Boolean {
        val navController = findNavController(R.id.nav_host_fragment_content_main)
        return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
    }

    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }

    override fun onBackPressed() {
        if (drawerLayout.isDrawerOpen(GravityCompat.START))
            drawerLayout.closeDrawer(GravityCompat.START)
        else
            super.onBackPressed()
    }

}

这是主片段(主活动中的第一个片段),其中包含子片段Asset片段

class HomeFragment : Fragment() {

    private val homeViewModel: HomeViewModel by activityViewModels()
    private var _binding: FragmentHomeBinding? = null

    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    private lateinit var viewPager : ViewPager2

    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View {

        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        val root: View = binding.root

        viewPager = binding.viewPagerContainer
        val bottomNav = binding.bottomNav
//        val tabLayout = binding.tabLayout

        val fragmentList : MutableList<Pair<String, Fragment>> = mutableListOf()
        fragmentList.add(Pair(getString(R.string.assets), AssetFragment.newInstance()))
        fragmentList.add(Pair(getString(R.string.news), NewsFragment.newInstance()))
        fragmentList.add(Pair(getString(R.string.videos), VideosFragment.newInstance()))

        val adapter = AppFragmentAdapter(fragmentList, this)

        viewPager.adapter = adapter
        viewPager.offscreenPageLimit = 2

        viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {

            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)
                bottomNav.menu.getItem(position).isChecked = true
                homeViewModel.setTitle(adapter.getFragmentTabName(position))
            }

        })

        val bottomNavListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
            when(item.itemId) {
                R.id.page_1 -> {
                    // Respond to navigation item 1 click
                    viewPager.setCurrentItem(0, true)
                    true
                }
                R.id.page_2 -> {
                    // Respond to navigation item 2 click
                    viewPager.setCurrentItem(1, true)
                    true
                }
                R.id.page_3 -> {
                    // Respond to navigation item 3 click
                    viewPager.setCurrentItem(2, true)
                    true
                }
                else -> false
            }
        }

        bottomNav.setOnNavigationItemSelectedListener(bottomNavListener)

//        val layoutInflater : LayoutInflater = LayoutInflater.from(context)
        //Connect TabLayout with ViewPager2
//        TabLayoutMediator(tabLayout, viewPager){ tab, position ->
//            tab.customView = prepareTabView(layoutInflater, tabLayout, adapter.getFragmentTabName(position), tabIcons[position])
//        }.attach()

        return root
    }

//    private fun prepareTabView(
//        layoutInflater: LayoutInflater,
//        tabLayout: TabLayout,
//        fragmentName: String,
//        drawableId: Int
//    ): View {
//
//        val rootView : View = layoutInflater.inflate(R.layout.main_custom_tab_text, tabLayout, false)
//
//        val tabName : AppCompatTextView = rootView.findViewById(R.id.tabName)
//
//        tabName.text = fragmentName
//        tabName.setCompoundDrawablesWithIntrinsicBounds(null, AppCompatResources.getDrawable(requireContext(), drawableId), null, null)
//
//        return tabName
//
//    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    override fun onResume() {
        super.onResume()

        requireView().isFocusableInTouchMode = true
        requireView().requestFocus()
        requireView().setOnKeyListener(object : View.OnKeyListener {

            override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean {
                if (event!!.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
                    onBackPress()
                    return true
                }
                return false
            }

        })
    }

    fun onBackPress() {

        if (viewPager.currentItem != 0)
            viewPager.setCurrentItem(0, true)
        else
            requireActivity().onBackPressed()

    }
}

这是父片段托管的ViewPager中显示的子片段之一

class AssetFragment : Fragment() {

    companion object {
        fun newInstance() = AssetFragment()
    }

    private lateinit var viewModel: AssetViewModel

    private var _binding: FragmentAssetsBinding? = null

    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    private lateinit var logTxt: AppCompatTextView
    private lateinit var recyclerView: RecyclerView
    private lateinit var swipeRefreshLayout: SwipeRefreshLayout

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {

        _binding = FragmentAssetsBinding.inflate(inflater, container, false)
        val root: View = binding.root

        recyclerView = binding.recyclerView
        swipeRefreshLayout = binding.refreshLayout
        logTxt = binding.errorLog

        recyclerView.layoutManager = LinearLayoutManager(context)
        adapter = AssetAdapter(requireContext(), this)
        recyclerView.adapter = adapter

        swipeRefreshLayout.isRefreshing = true
        fetchAssets("30")

        swipeRefreshLayout.setOnRefreshListener {
            swipeRefreshLayout.isRefreshing = true
            fetchAssets("30")
        }

        return root

    }

    private fun fetchAssets(limit: String) {

        //Network stuff
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProvider(this).get(AssetViewModel::class.java)
        // TODO: Use the ViewModel
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

}

导航xml

这是将在抽屉菜单中显示的片段

    <?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/mobile_navigation"
    app:startDestination="@+id/nav_home">

<fragment
    android:id="@+id/nav_home"
    android:name="com.myapp.ui.home.HomeFragment"
    android:label="@string/home"
    tools:layout="@layout/fragment_home" />

<fragment
    android:id="@+id/nav_marketcap"
    android:name="com.myapp.ui.marketcap.MarketCapFragment"
    android:label="@string/marketCap"
    tools:layout="@layout/fragment_marketcap" />

<fragment
    android:id="@+id/nav_about"
    android:name="com.myapp.ui.about.AboutFragment"
    android:label="@string/about"
    tools:layout="@layout/fragment_about" />

</navigation>

menu.xml

 <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:showIn="navigation_view">

    <group android:checkableBehavior="single">

    <item android:title="@string/menu">
        <menu>

            <item
                android:id="@+id/nav_home"
                android:icon="@drawable/ic_assets"
                android:title="@string/home" />

            <item
                android:id="@+id/nav_marketcap"
                android:icon="@drawable/ic_marketcap"
                android:title="@string/marketCap" />

            <item
                android:id="@+id/nav_about"
                android:icon="@drawable/ic_about"
                android:title="@string/about" />

        </menu>
    </item>

</group>



     <item android:title="@string/connect">
            <menu>
                <item
                    android:id="@+id/email_connect"
                    android:icon="@drawable/ic_email"
                    android:title="@string/fui_email_hint" />
            </menu>
        </item>

</menu>

流量:

打开应用程序

启动MainActivity

显示HomeFragment(AssetFragment)

打开抽屉菜单

选择项目,例如About(AboutFragment)

按/向后滑动

这里的问题是HomeFragment onCreateView再次被触发

预期的行为家庭片段将不再需要膨胀视图,因为我们只是让用户回到第一个目的地。除非用户自己在我们的抽屉菜单中按下Home项,否则将重新创建家庭片段。

共有1个答案

洪建茗
2023-03-14

根据《使用片段保存状态指南》,当片段位于后堆栈上时,预期会销毁并重新创建片段的视图(而不是片段本身)。

根据该指南,状态类型之一是非配置状态:

NonConfig:从外部源(例如服务器或本地存储库)提取的数据,或提交后发送到服务器的用户创建的数据。

NonConfig数据应该放在片段之外,例如在ViewModel中。ViewModel类本质上允许数据在配置更改(例如屏幕旋转)中生存,并在片段放在后堆栈时保留在内存中。

因此,您的片段不应该在onCreateView()中调用fetchAsset("30")。相反,此逻辑应该发生在ViewModel中,以便当片段从后堆栈返回时,它立即可用。根据ViewModel指南,您的fetchAsset应该在ViewModel中完成,并且您的片段将观察该数据。

 类似资料:
  • 所以我用FragmentStatePagerAdapter实现了一个ViewPager,我有两个片段。我用参数1和2调用了setOffscreenPageLimit(),希望片段不会被重新创建,但是第一个片段总是被重新创建,而第二个片段被创建。 第一个片段总是经过这些步骤,只要它离开视野(向右滑动到另一个片段,或者点击主页按钮,等等...) on暂停 onStop onAttach 创建 onCr

  • 问题内容: 在我的.xhtml页面中,我具有以下形式: CustomerTemplate.xhtml是: 这是我的ManagedBean: 如您所见,我的MrBean是ViewScoped ManagedBean。我希望@PostContruct函数只会在页面第1次呈现时被调用一次。但是,当我单击该按钮时,即使我仍在同一视图上,我也遇到了该行中的null异常。 如果有人能给我有关如何解决此问题的建

  • 因此,我有一个DatePicker ChildComponent,我需要从父级通过访问它的。基本上一切正常。 但有一个问题。 每次我更新与DatePickerChildComponents没有任何关系的父其他儿童组件时,DatePicker仍然在渲染,即使它不应该渲染。 我试图删除从父级到子级的ref传递,然后每当我更新我想要解决的其他组件时,组件不再重新提交。 我已经开始使用回调备忘录,通过使用

  • configure脚本创建一个名为'config.status'的文件,用它描述在包最后一次进行配置时 给出的配置选项。该文件是一个shell脚本文件,如果运行它,将重新创建相同的配置。 你可以用'--recheck'选项调用'config.status'以更新它自身。如果你修改了configure, 该选项是有用的,这是因为某些测试的结果可能会与上一次运行的结果不同。选项'--recheck'以

  • http://prntscr.com/9jhrwa“GUI看起来怎么样” 公共类Okno1扩展javax.swing.jFrame{ 在这里,我显示了按下这个按钮时的jScrollPanel,我还显示了如果我想在显示的JList中获得选定元素的索引时必须按下的按钮 在这里,我按下一个按钮,它应该为我提供所选项的索引,但它一直给我-1,无论JList上的项是否被选中都无关紧要

  • 问题内容: 我最近在SSRS-2008标记中回答了这个问题,要求将日期中的天数更改为序数(即“ 1st”,“ 2nd”而不是“ 1”,“2”)。该解决方案涉及VB.Net功能。我很好奇如何在SQL中执行此任务(特别是t-sql和SQL Server),或者是否有内置的支持。 因此,这是一个场景:假设您为1000个跑步者组织了一场比赛,并将结果存储在带有“名称”和“位置”(以正常数字表示)列的表格中