如何避免 FragmentManager 重新创建片段?

Posted

技术标签:

【中文标题】如何避免 FragmentManager 重新创建片段?【英文标题】:How to avoid recreate fragment by FragmentManager? 【发布时间】:2020-07-14 04:52:36 【问题描述】:

我有一个带有 TabLayout 的活动和两个代表选项卡内容的片段。 我在我的活动的 OnCreate 方法中手动管理打开的选项卡的当前状态:

import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;

import com.google.android.material.tabs.TabLayout;

public class LoginActivity extends AppCompatActivity 

    private TabLayout tabLayout;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        tabLayout = findViewById(R.id.tabLayout);

        //initialize or restore opened tab, after activity first started or recreated
        int tabIndex = savedInstanceState == null ? 0 : savedInstanceState.getInt("tabIndex");
        Fragment f;
        switch (tabIndex) 
            case 0:
                f = new SignInFragment();
                break;
            case 1:
                f = new SignUpFragment();
                break;
            default:
                throw new UnsupportedOperationException();
        
        //sync tab indicator
        tabLayout.selectTab(tabLayout.getTabAt(tabIndex));
        //set opened fragment
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.tabContent, f)
                .commit();

        //add listener to handle tab switching
        tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() 
            @Override
            public void onTabSelected(TabLayout.Tab tab) 
                Fragment tabFragment;
                switch (tab.getPosition()) 
                    case 0:
                        tabFragment = new SignInFragment();
                        break;
                    case 1:
                        tabFragment = new SignUpFragment();
                        break;
                    default:
                        throw new UnsupportedOperationException();
                
                getSupportFragmentManager().beginTransaction()
                        .setCustomAnimations(com.google.android.material.R.anim.abc_grow_fade_in_from_bottom, com.google.android.material.R.anim.abc_shrink_fade_out_from_bottom)
                        .replace(R.id.tabContent, tabFragment)
                        .commit();
            

            @Override
            public void onTabUnselected(TabLayout.Tab tab) 

            

            @Override
            public void onTabReselected(TabLayout.Tab tab) 

            
        );
    

    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) 
        super.onSaveInstanceState(outState);
        outState.putInt("tabIndex", tabLayout.getTabAt(tabLayout.getSelectedTabPosition()).getPosition());
    

这只是简单的示例。我的真实代码是用 C# 编写的,还有一些其他逻辑来存储和管理活动重启之间的状态。

配置改变时的问题(例如开关方向):

    打开的片段被破坏 活动被破坏 活动已重新启动 在 onCreate 方法中,当调用 super.onCreate(savedInstanceState) 片段管理器时,使用默认(无参数)构造函数重新创建片段(在步骤 1 中销毁)。 我在 onCreate 方法中的代码恢复被破坏的片段。因此,片段管理器重新创建的片段被销毁并替换为我在此步骤中创建的片段。

如何避免这种行为?我不需要片段管理器恢复的片段。我也不需要片段的默认构造函数(我有一些自定义 ViewModel 通过托管活动的构造函数注入片段)

把 null 放到 super.onCreate() 中?我认为这不是很好的解决方案...

附:我知道 ViewPager 和 ViewPager2 来管理 TabLayout 的选项卡。 ViewPager 已弃用。使用 ViewPager2 我有一个奇怪的错误:第一个选项卡(在 0 索引处)工作正常,但在第二个选项卡中我无法专注于任何输入(单击输入并立即失去焦点,不知道为什么)。

【问题讨论】:

【参考方案1】:

对片段使用全局变量首先创建片段,然后在需要时替换它们,例如:

private Fragment signInFragment = new SignInFragment();
private Fragment signUpFragment = new SignUpFragment();
int tabIndex = savedInstanceState == null ? 0 : 
savedInstanceState.getInt("tabIndex");
    Fragment f;
    switch (tabIndex) 
        case 0:
            f = signInFragment
            break;
        case 1:
            f = signUpFragment
            break;
        default:
            throw new UnsupportedOperationException();
    

【讨论】:

为什么?我想你不明白这个问题。我知道如何在选项卡切换之间存储片段实例。这不是一个问题。问题在于当配置更改(例如方向更改)时,托管活动的片段管理器存储打开片段的类型(而不是实例)。当重新创建活动时,fm 将在调用 super.onCreate() 时尝试自动重新创建存储的片段(使用默认片段构造函数)。我想避免这种情况,因为我手动重新创建我的框架并使用非默认构造函数。 你的问题有点混乱。我建议在其中使用 viewModel 和 savedInstanceState。因此,您可以将所有设置或您想要的任何内容存储在 viewModel 中。您也可以使用导航组件,它更容易并且可以肯定地解决您的问题。 是的,我可以。即使我重写代码以在片段中仅使用默认构造函数,也不能解决问题。片段被重新创建两次。当调用活动的基/父 super.onCreate 时,FragmentManager 自动重新创建第一个片段,当我初始化选项卡切换逻辑时,第二个片段由我的代码重新创建。我发现只有一种方法可以避免这种行为 - 将 null 放入 super.onCreate 调用。这不是 TabLayout 的问题。这是附加到活动的任何片段的问题 - 活动重新启动时,片段管理器会自动保存/恢复片段。 另一种好用且方便的方法是在重新启动活动后或在重新创建片段时清除片段回栈。 这行不通。您总是需要使用 FragmentManager 开始添加、替换或删除片段。

以上是关于如何避免 FragmentManager 重新创建片段?的主要内容,如果未能解决你的问题,请参考以下文章

如何避免使用自动代码优先迁移重新创建现有数据库

BottomNavigationView - 如何避免重新创建片段并重用它们

如何避免重新渲染由 v-for 指令创建的所有子组件

无法从 android.app.FragmentManager 转换为 android.support.v4.app.FragmentManager

java.lang.IllegalStateException: FragmentManager 已被销毁

android.support.v4.app.FragmentManager 还是 android.app.FragmentManager?