Android Jetpack 导航禁用滚动位置

Posted

技术标签:

【中文标题】Android Jetpack 导航禁用滚动位置【英文标题】:Android Jetpack Navigation Disable Scroll Position 【发布时间】:2020-09-25 10:21:03 【问题描述】:

我有一个包含许多片段的活动(使用喷气背包导航)。在我的第一个片段上,我有一个 recyclerview。如果我在第一个片段上滚动,然后导航到另一个片段,该片段将保留滚动位置,我不希望这样。一个例子如下:

即假设我有两个片段 A 和 B,当我的应用程序启动时,它从 A 开始。假设我开始在 A 上滚动,然后导航到 B。我的应用程序保留 B 上的滚动位置,这不是我想要的。我希望片段 B 从顶部开始。然后当它返回到片段 A 时,我希望它保留之前滚动的滚动位置。

片段 A.xml

<?xml version="1.0" encoding="utf-8"?>

<layout 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">

<data>

    <import type="android.view.View" />

    <variable
        name="ViewModel"
        type="....AccountViewModel" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/Layout_Fragment_Account"
    android:layout_
    android:layout_>


    <!--
       Recyclerview
    -->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/RecyclerView_Account"
        android:layout_
        android:layout_
        android:visibility="@ViewModel.accountListVisibility? View.VISIBLE : View.GONE"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:visibility="gone" />


    <!--
        Empty Views and group
    -->
    <androidx.constraintlayout.widget.Group
        android:id="@+id/Empty_View"
        android:layout_
        android:layout_
        android:visibility="@ViewModel.accountEmptyViewVisibility? 

View.VISIBLE : View.GONE"
            app:constraint_referenced_ids="Empty_View_Illustration,Empty_View_Title,Empty_View_Subtitle" />

    <ImageView
        android:id="@+id/Empty_View_Illustration"
        android:layout_
        android:layout_
        android:layout_gravity="center_horizontal"
        android:scaleType="centerCrop"
        app:layout_constraintBottom_toTopOf="@+id/Empty_View_Title"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="packed"
        app:srcCompat="@drawable/il_account" />

    <TextView
        android:id="@+id/Empty_View_Title"
        style="@style/Locky.Text.Title6"
        android:layout_
        android:layout_
        android:layout_marginStart="32dp"
        android:layout_marginEnd="32dp"
        android:text="@string/text_title_emptyView_accounts"
        android:textAlignment="center"
        app:layout_constraintBottom_toTopOf="@id/Empty_View_Subtitle"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/Empty_View_Illustration"
        app:layout_constraintVertical_chainStyle="packed"
        app:layout_constraintWidth_default="percent"
        app:layout_constraintWidth_percent=".8" />

    <TextView
        android:id="@+id/Empty_View_Subtitle"
        style="@style/Locky.Text.Subtitle"
        android:layout_
        android:layout_
        android:layout_gravity="center_horizontal"
        android:layout_marginStart="32dp"
        android:layout_marginEnd="32dp"
        android:layout_marginBottom="?attr/actionBarSize"
        android:text="@string/text_subtitle_emptyView_accounts"
        android:textAlignment="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.6"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/Empty_View_Title"
        app:layout_constraintWidth_default="percent"
        app:layout_constraintWidth_percent=".8" />

    <!--
        Progress Bar
    -->
    <include
        android:id="@+id/Progress_Bar"
        layout="@layout/custom_view_list_loading"
        android:layout_
        android:layout_
        android:visibility="@ViewModel.loadingStatus? View.VISIBLE : View.GONE"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

</layout>

片段 A.kt:

class AccountFragment : Fragment() 

private var _binding: FragmentAccountBinding? = null
private var _viewModel: AccountViewModel? = null
private var _lastClickTime: Long = 0

private val binding get() = _binding!!
private val viewModel get() = _viewModel!!

companion object 
    const val TAG = "ACCOUNT_FRAGMENT_DEBUG"


override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? 
    // Inflate the layout for this fragment
    _binding = FragmentAccountBinding.inflate(inflater, container, false)
    // Fetch view model
    _viewModel = ViewModelProvider(this).get(AccountViewModel::class.java)
    //Bind view model to layout
    binding.viewModel = _viewModel
    // Bind lifecycle owner
    binding.lifecycleOwner = this

    return binding.root


override fun onViewCreated(view: View, savedInstanceState: Bundle?) 
    super.onViewCreated(view, savedInstanceState)

    /* Hides the soft keyboard */
    hideSoftKeyboard(binding.root)

    /* Observe snack bar events */
    observeSnackBarEvent()

    /* Observe the account list changes */
    observeAccounts()

    /* Observe back stack entry result after navigating from sort sheet */
    observeBackStackEntryForSortSheet()


override fun onCreate(savedInstanceState: Bundle?) 
    super.onCreate(savedInstanceState)
    setHasOptionsMenu(true)


override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) 
    inflater.inflate(R.menu.menu_toolbar_filter, menu)
    super.onCreateOptionsMenu(menu, inflater)


override fun onOptionsItemSelected(item: MenuItem): Boolean 
    return when (item.itemId) 
        R.id.Toolbar_Filter -> 
            navigateToSort()
            true
        
        else -> false
    


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


/*
* My Functions
*/
private fun observeBackStackEntryForSortSheet() 
    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.Fragment_Account)

    // 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_ACCOUNTS_SORT)
        ) 

            viewModel.sortChange(
                navBackStackEntry.savedStateHandle.get<AccountSort>(
                    KEY_ACCOUNTS_SORT
                )!!
            )

            navBackStackEntry.savedStateHandle.remove<AccountSort>(KEY_ACCOUNTS_SORT)
        
    
    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)
        
    )


private fun observeSnackBarEvent() 
    viewModel.showSnackBarEvent.observe(viewLifecycleOwner, Observer 
        if (it != null) 
            snackBarAction(it)
        
    )


private fun observeAccounts() 
    with(viewModel) 
        accounts.observe(viewLifecycleOwner, Observer 
            if (it != null) 
                //set loading flag to hide loading animation
                doneLoading()

                //Alternate visibility for account list and empty view
                alternateAccountListVisibility(it.size)

                //Submit the cards
                initiateAccounts().submitList(it)
            
        )
    


private fun initiateAccounts(): AccountAdapter 
    val adapter = AccountAdapter(
        AccountClickListener 
            navigateToSelectedAccount(it)
        ,
        AccountOptionsClickListener  view, card ->
            view.apply 
                isEnabled = false
            
            createPopupMenu(view, card)
        )

    binding.RecyclerViewAccount.apply 
        this.adapter = adapter
        setHasFixedSize(true)
    

    return adapter


private fun createPopupMenu(view: View, account: Account) 
    requireContext().createPopUpMenu(
        view,
        R.menu.menu_moreoptions_account,
        PopupMenu.OnMenuItemClickListener 
            when (it.itemId) 
                R.id.Menu_CopyUsername -> copyToClipboardAndToast(account.username)
                R.id.Menu_CopyPass -> copyToClipboardAndToast(account.password)
                R.id.Menu_ShowPass -> triggerSnackBarEvent(account.password)
                else -> false
            
        , PopupMenu.OnDismissListener 
            view.apply 
                isEnabled = true
            
        )


private fun navigateToSort() 
    if (SystemClock.elapsedRealtime() - _lastClickTime >= 800) 
        _lastClickTime = SystemClock.elapsedRealtime()
        navigateTo(AccountFragmentDirections.actionFragmentAccountToBottomSheetFragmentAccountFilter())
    


private fun navigateToSelectedAccount(account: Account) 
    navigateTo(
        AccountFragmentDirections.actionFragmentAccountToFragmentViewAccount(
            account
        )
    )


private fun snackBarAction(message: String) 
    binding.LayoutFragmentAccount.snackbar(message) 
        action(getString(R.string.button_snack_action_close))  dismiss() 
    
    viewModel.doneShowingSnackBar()


private fun triggerSnackBarEvent(message: String): Boolean 
    viewModel.setSnackBarMessage(message)
    return true


private fun copyToClipboardAndToast(message: String): Boolean 
    copyToClipboard(message)
    toast(getString(R.string.message_copy_successful))
    return true

片段 B.xml

<?xml version="1.0" encoding="utf-8"?>

<layout 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">

<data>

    <variable
        name="Account"
        type="....Account" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/Layout_Credential_View"
    android:layout_
    android:layout_>

    <ImageView
        android:id="@+id/Account_Logo"
        imageUrl="@Account.logoUrl"
        loadingResource="@@drawable/ic_image_loading"
        errorResource="@@drawable/ic_account_placeholder"
        android:layout_
        android:layout_
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:src="@drawable/ic_account_placeholder" />

    <TextView
        android:id="@+id/Account_Name"
        style="@style/Locky.Text.Title5.Name"
        android:layout_
        android:layout_
        android:layout_marginStart="32dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="32dp"
        android:textAlignment="center"
        android:text="@Account.accountName"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/Account_Logo"
        tools:text="This can be a very very very long title toooooo" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/RecyclerView_Credentials_Field"
        android:layout_
        android:layout_
        android:layout_marginStart="8dp"
        android:layout_marginTop="24dp"
        android:layout_marginEnd="8dp"
        android:nestedScrollingEnabled="true"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/Account_Name" />

</androidx.constraintlayout.widget.ConstraintLayout>

</layout>

片段 B.kt

class ViewAccountFragment : Fragment() 

private var _binding: FragmentViewAccountBinding? = null
private var _viewModel: ViewAccountViewModel? = null
private lateinit var _account: Account

private val binding get() = _binding!!
private val viewModel get() = _viewModel!!

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? 
    //Fetch the layout and do the binding
    _binding = FragmentViewAccountBinding.inflate(inflater, container, false)
    //Instantiate view model
    _viewModel = ViewModelProvider(this).get(ViewAccountViewModel::class.java)
    binding.lifecycleOwner = this

    //Fetch the account clicked on the previous screen
    _account = ViewAccountFragmentArgs.fromBundle(requireArguments()).parcelcredaccount

    with(_account) 

        //Bind the account to the layout for displaying
        binding.account = this

        //Submit the account details to the recyclerview
        initiateCredentialsFieldList().submitList(viewModel.fieldList(this))
    

    return binding.root


override fun onViewCreated(view: View, savedInstanceState: Bundle?) 
    super.onViewCreated(view, savedInstanceState)

    /* Hides the soft keyboard */
    hideSoftKeyboard(binding.root)


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


override fun onCreate(savedInstanceState: Bundle?) 
    super.onCreate(savedInstanceState)
    setHasOptionsMenu(true)


override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) 
    inflater.inflate(R.menu.menu_credentials_actions, menu)
    super.onCreateOptionsMenu(menu, inflater)


override fun onOptionsItemSelected(item: MenuItem): Boolean 
    return when (item.itemId) 
        R.id.Action_Duplicate -> 
            /*
            * We set the account id to empty here
            * When the add screen receives it, it wil perceive it as a new account that needs to be
            * added to the database
            */
            navigateToEditScreen(_account.copy(accountID = generateUniqueID()))
            true
        

        R.id.Action_Edit -> 
            navigateToEditScreen(_account)
            true
        

        R.id.Action_Delete -> 
            deleteConfirmationDialog(_account.accountName)
            true
        
        else -> false
    


private fun initiateCredentialsFieldList(): CredentialsViewAdapter 
    val credentialsAdapter =
        CredentialsViewAdapter(
            CopyClickListener  data ->
                copyToClipboardAndToast(data)
            ,
            ViewClickListener  data ->
                snackBarAction(data)
            )

    binding.RecyclerViewCredentialsField.apply 
        adapter = credentialsAdapter
        setHasFixedSize(true)
    

    return credentialsAdapter


private fun deleteAndNavigateBackToAccountList() 
    with(_account) 
        viewModel.delete(accountID)
        toast(getString(R.string.message_credentials_deleted, accountName))
        findNavController().popBackStack()
    

导航.xml

<fragment
    android:id="@+id/Fragment_Account"
    android:name="....AccountFragment"
    android:label="Accounts"
    tools:layout="@layout/fragment_account">
    <action
        android:id="@+id/action_Fragment_Account_to_Fragment_View_Account"
        app:destination="@id/Fragment_View_Account" />
    <action
        android:id="@+id/action_Fragment_Account_to_BottomSheet_Fragment_Account_Filter"
        app:destination="@id/BottomSheet_Fragment_Account_Filter" />
</fragment>

MainActivity.xml

<?xml version="1.0" encoding="utf-8"?>

<layout 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">

<androidx.drawerlayout.widget.DrawerLayout
    android:id="@+id/Drawer_Main"
    android:layout_
    android:layout_
    tools:context=".ui.main.main.MainActivity">

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:id="@+id/Layout_Coordinator_Main"
        android:layout_
        android:layout_
        android:fitsSystemWindows="true">

        <com.google.android.material.appbar.MaterialToolbar
            android:id="@+id/Toolbar_Main"
            android:layout_
            android:layout_
            android:background="@color/colorOnSurface"
            android:outlineAmbientShadowColor="@color/colorShadowColor"
            android:outlineSpotShadowColor="@color/colorShadowColor">

            <TextView
                android:id="@+id/Toolbar_Main_Title"
                style="@style/Locky.Text.Title6.Toolbar"
                android:layout_
                android:layout_
                android:layout_gravity="center"
                android:text="@string/app_name" />

        </com.google.android.material.appbar.MaterialToolbar>

        <androidx.core.widget.NestedScrollView
            android:id="@+id/Nested_Scroll"
            android:layout_
            android:layout_
            android:layout_marginTop="?attr/actionBarSize"
            android:fillViewport="true">

            <fragment
                android:id="@+id/Navigation_Host"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_
                android:layout_
                app:defaultNavHost="true"
                app:navGraph="@navigation/navigation_drawer_main" />

        </androidx.core.widget.NestedScrollView>

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/FAB_Search"
            style="@style/Locky.FloatingActionButton.Mini"
            android:layout_
            android:layout_
            android:layout_marginTop="0dp"
            android:layout_marginBottom="85dp"
            app:layout_anchor="@id/FAB_Add"
            app:layout_anchorGravity="top|center_horizontal"
            app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
            app:srcCompat="@drawable/ic_search" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/FAB_Add"
            style="@style/Locky.FloatingActionButton.Normal"
            android:layout_
            android:layout_
            android:layout_gravity="bottom|end"
            android:layout_margin="@dimen/fab_margin"
            app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
            app:srcCompat="@drawable/ic_add" />

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/Navigation_View"
        style="@style/Locky.Widget.Custom.NavigationView"
        android:layout_
        android:layout_
        android:layout_gravity="start"
        android:clipToPadding="false"
        android:paddingStart="0dp"
        android:paddingEnd="16dp"
        app:headerLayout="@layout/drawer_header"
        app:itemTextAppearance="@style/Locky.Text.Body.Drawer"
        app:menu="@menu/menu_drawer_main" />

</androidx.drawerlayout.widget.DrawerLayout>

MainActivity.kt

class MainActivity : AppCompatActivity() 

private lateinit var _binding: ActivityMainBinding
private lateinit var _viewModel: MainActivityViewModel
private lateinit var _appBarConfiguration: AppBarConfiguration

//Fragments that can navigate with the drawer
private val _navigationFragments = setOf(
    R.id.Fragment_Card,
    R.id.Fragment_Account,
    R.id.Fragment_Device
)

override fun onCreate(savedInstanceState: Bundle?) 
    super.onCreate(savedInstanceState)
    _binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
    _viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)

    _binding.lifecycleOwner = this

    //Set the support action bar to the toolbar
    setSupportActionBar(_binding.ToolbarMain)
    //Remove the default actionbar title
    supportActionBar?.setDisplayShowTitleEnabled(false)

    /* Updates the app settings*/
    updateAppSettings()

    //Setup the navigation components
    navigationUISetup()

    //Load FABs
    listenerForAddFab()

    listenerForSearchFab()

    //Scroll changes to adjust toolbar elevation accordingly
    setUpNestedScrollChangeListener()


override fun onOptionsItemSelected(item: MenuItem) =
    item.onNavDestinationSelected(findNavController(R.id.Navigation_Host)) || super.onOptionsItemSelected(
        item
    )

override fun onSupportNavigateUp() =
    findNavController(R.id.Navigation_Host).navigateUp(_appBarConfiguration)

override fun finish() 
    super.finish()

    ActivityNavigator.applyPopAnimationsToPendingTransition(this)


private fun navigationUISetup() 
    //Fetch the Nav Controller
    val navController = findNavController(R.id.Navigation_Host)
    //Setup the App Bar Configuration
    _appBarConfiguration = AppBarConfiguration(_navigationFragments, _binding.DrawerMain)

    //Use Navigation UI to setup the app bar config and navigation view
    NavigationUI.setupActionBarWithNavController(this, navController, _appBarConfiguration)
    NavigationUI.setupWithNavController(_binding.NavigationView, navController)

    //Set the mini FABs with navigation to navigate to fragments accordingly.
    Navigation.setViewNavController(_binding.FABAdd, navController)
    Navigation.setViewNavController(_binding.FABSearch, navController)

    //Add on change destination listener to navigation controller to handle fab visibility
    navigationDestinationChangeListener_FAB(navController)

    //Add on change destination listener to navigation controller to handle screen title visibility
    navigationDestinationChangeListener_ToolbarTitle(navController)


private fun setUpNestedScrollChangeListener() =
    _binding.NestedScroll.setOnScrollChangeListener  _, _, scrollY, _, _ ->
        if (scrollY > 0) 
            _binding.ToolbarMain.elevation = 12F
         else 
            _binding.ToolbarMain.elevation = 0F
        
    

private fun navigationDestinationChangeListener_ToolbarTitle(navController: NavController) 
    navController.addOnDestinationChangedListener  _, nd, _ ->
        when (nd.id) 
            R.id.Fragment_Account -> updateToolbar(getString(R.string.text_title_screen_accounts))
            R.id.Fragment_Card -> updateToolbar(getString(R.string.text_title_screen_cards))
            R.id.Fragment_Device -> updateToolbar(getString(R.string.text_title_screen_devices))
            R.id.Fragment_Settings -> updateToolbar(getString(R.string.text_title_screen_settings))
            R.id.Fragment_Profile -> updateToolbar(getString(R.string.text_title_screen_profile))
            R.id.Fragment_About -> updateToolbar(getString(R.string.text_title_screen_about))
            R.id.Fragment_Donate -> updateToolbar(getString(R.string.text_title_screen_donate))
            else -> 
                //Show the toolbar
                updateToolbar(null)
            
        
    


private fun navigationDestinationChangeListener_FAB(navController: NavController) 
    navController.addOnDestinationChangedListener  nc, nd, _ ->
        when (nd.id) 
            nc.graph.startDestination,
            R.id.Fragment_Card,
            R.id.Fragment_Device -> 
                _binding.DrawerMain.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)

                //Show all the FABs
                showFABs()
            
            else -> 
                _binding.DrawerMain.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)

                //Hide all the FABs
                hideFABs()
            
        
    


private fun getFadeNavOptions(): NavOptions? 
    return NavOptions.Builder()
        .setEnterAnim(R.anim.anim_fade_in)
        .setExitAnim(R.anim.anim_fade_out)
        .build()


private fun hideFABs() 
    _binding.FABSearch.hide()
    _binding.FABAdd.hide()


private fun showFABs() 
    _binding.FABSearch.show()
    _binding.FABAdd.show()

    showFABFromSlidingBehavior(_binding.FABSearch, _binding.FABSearch.isVisible)
    showFABFromSlidingBehavior(_binding.FABAdd, _binding.FABAdd.isVisible)


private fun showFABFromSlidingBehavior(fab: FloatingActionButton, isVisible: Boolean) 
    val layoutParams: ViewGroup.LayoutParams = fab.layoutParams
    if (layoutParams is CoordinatorLayout.LayoutParams) 
        val behavior = layoutParams.behavior
        if (behavior is HideBottomViewOnScrollBehavior) 
            if (isVisible) 
                behavior.slideUp(fab)
             else 
                behavior.slideDown(fab)
            
        
    

我附上了一个 gif 来演示这里的问题:

在 GIF 中,我从 3 个片段导航(片段 A > 片段 B > 片段 C) 我在这里做错了什么吗?

【问题讨论】:

这是因为即使您转到另一个片段,您的片段仍然存在。您可能需要将片段从导航堆栈中弹出。或者添加一个 backstack 更改监听器并在返回片段时滚动到 0。 实际上不是什么时候回来的。假设我有两个片段 A 和 B,当我的应用程序启动时,它从 A 开始。假设我开始在 A 上滚动,然后导航到 B。我的应用程序保留 B 上的滚动位置,这不是我想要的。我希望片段 B 从顶部开始。 有人可以帮我解决这个问题吗?我被困住了…… 滚动位置不会保留在另一个片段上。也许您的代码中有一些泄漏。正确检查您的代码。 嗯,这很方便。我有完全相反的问题。稍后我将查看您的代码并将其与我的代码进行比较。希望我能为我们俩找到原因! 【参考方案1】:

当您填充不同的片段时,两个片段的layoutmanager 相同;调用相同的layoutmanager。然后它会尝试恢复相同的位置,并考虑其相同的回收视图,当您考虑它时,这是一种功能。

来自文档:

RecyclerView 准备好恢复状态时调用 以前的RecyclerView。请注意,这可能发生在实际 布局,基于适配器更喜欢如何恢复状态。看 RecyclerView.Adapter.getStateRestorationPolicy()

这意味着我们需要的不是恢复可以通过传递完成的状态 PREVENTRecyclerView.Adapter.StateRestorationPolicy

解决方案 1:在您的片段 B 适配器中只需调用 adapter.stateRestorationPolicy = PREVENT

解决方案 2:为片段 B 创建一个不同的layoutmanager,以防您想为其他东西恢复位置。

编辑 :: QA :: 我如何将视图设置在顶部(靠近状态栏):

好吧,既然您要在 NestedScrollView 中填充片段,当您导航到所需片段时,您应该调用 NestedScrollView.scrollTo(0, 0);,可能是通过在您的 MainActivity.kt 中等待来自 addOnDestinationChangedListenercallback

【讨论】:

我试过了,它从recyclerview的顶部开始,而不是在屏幕的顶部。我在recyclerview的顶部有其他的看法。正常它应该加载在屏幕顶部.... 这发生在应用程序中的所有片段中,而不仅仅是有回收站视图的地方。 你不应该使用单例 LayoutManager 上面是什么意思?你的意思是靠近状态栏?还是在其他片段之上(覆盖)? 好吧,实际上有很多解决方案,您可以DispatchNestedPreScroll 和/或DispatchNestedScroll,如果您想在调用该特定片段时传递一个新的SavedInstanceBundle,或者您可以添加NestedScrollView 到片段而不是 MainActivity.kt 类。为什么要禁用嵌套滚动视图的滚动?

以上是关于Android Jetpack 导航禁用滚动位置的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Jetpack Compose 的 LazyColumn/LazyRow 中禁用和启用滚动?

使用Android Jetpack导航时如何防止导航

关闭画布导航滚动问题

android jetpack 导航仪器测试在返回导航上失败

使用 Jetpack 的 Android 导航组件销毁/重新创建的片段

是否可以使用 Android 导航架构组件(Android Jetpack)有条件地设置 startDestination?