如何在不破坏和重新创建片段的情况下在片段之间切换? (以类似静态的方式)

How can I switch between Fragments without destroying and re-creating them? (in a static-like way)

我的应用使用 BottomNavigationBar 在 Fragment 之间切换,它是这样做的:

public class MainActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        BottomNavigationView bottomNav = findViewById(R.id.barra);
        bottomNav.setOnNavigationItemSelectedListener(navListener);

        getSupportFragmentManager().beginTransaction().replace(R.id.container, new KeyboardFragment()).commit();
        bottomNav.setSelectedItemId(R.id.keyboard);

    }


    private BottomNavigationView.OnNavigationItemSelectedListener navListener =
            new BottomNavigationView.OnNavigationItemSelectedListener() {
                @Override
                public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                    Fragment selectedFragment = null;

                    switch (item.getItemId()){
                        case R.id.camera:
                            selectedFragment = new CameraFragment();
                            break;
                        case R.id.keyboard:
                            selectedFragment = new KeyboardFragment();
                            break;
                        case R.id.settings:
                            selectedFragment = new SettingsFragment();
                            break;
                    }

                    getSupportFragmentManager().beginTransaction().replace(R.id.container,
                            selectedFragment).commit();

                    return true;

                }
            };
}

我希望那些 Fragments 是静态的,这样当我在它们之间切换时它们的内容和视图不会消失。我试图在 MainActivityonCreate() 方法中创建它们,但它只有助于在 EditText 小部件中保留文本,其余视图和内容消失。

我看过其他类似的问题,但回答得不好,我是新手。在对类似问题的一些回答之后,我尝试使用 add()attach() 之类的函数而不是 replace() 但我认为我做得不好;事实上,有时我的应用程序会崩溃。

我是否也应该在此处粘贴我的 layout.xml 文件?这些片段被“显示”成一个简单的 FrameLayout先谢谢了:)

replace() 意味着销毁这个并在它的位置添加一个新的,因为你的代码现在是你不能使用 add() 因为根据那个开关情况你将创建一个新的每次导航时片段实例。 它会浪费内存,最终应用程序会因 OutOfMemoryException

而崩溃

你能做什么?

不幸的是,底部导航没有太多选项,但您可以使用以下选项进行改进

1 对每个片段使用 viewmodels 并将所有 viewmodels 附加到主机活动中,这样您就不必加载数据每次创建该片段时,数据都会在视图模型中存活

2 或使用 viewpager 来保存您的片段 viewpager 将能够将所有片段保存在内存中并查看所需的片段,设置当前片段以编程方式 通过从 OnNavigationItemSelected()

在 vi​​ewpager 上调用 setCurrentItem()

3 使用 add() 而不是 replace() 并使您的片段 单例 这样你就不会每次都加载数据(如果你不使用视图模型),如果用户转到另一个片段并且在去其他任何地方之前 return 到这个你只需弹出返回使用 getSupportFragmantManager().popBackStack() 删除此片段堆栈,否则弹出返回堆栈并添加另一个片段

4 使用其他形式的导航参见 navigation components 让您的生活更轻松

也许有更好的解决方案,但根据我的经验,这就是我能想到的,不是很多,虽然导航组件也做替换事情,但我认为如果你使用视图模型,这不是很糟糕保存数据。

快乐编码

我是这样成功解决问题的:

  • 首先将所有的Fragment声明为MainActivityclass里面的字段,还有变量selected,后面会用到:
public class MainActivity extends AppCompatActivity {

    public KeyboardFragment keyboard_fragment = new KeyboardFragment();
    public CameraFragment camera_fragment = new CameraFragment();
    public SettingsFragment settings_fragment = new SettingsFragment();
    
    Fragment selected = teclado_fragment;`

    //...
  • 然后,以下方法也在 class 中定义,其中 R.id.container 是 FrameLayout 或用于显示膨胀片段的任何视图:
    private void createFragment(Fragment fragment){
                 getSupportFragmentManager().beginTransaction()
                .add(R.id.container, fragment)
                .hide(fragment)
                .commit();
    }
    private void showFragment(Fragment fragment){
                 getSupportFragmentManager().beginTransaction()
                .show(fragment)
                .commit();
    }
    private void hideFragment(Fragment fragment){
                 getSupportFragmentManager().beginTransaction()
                .hide(fragment)
                .commit();
    }
  • 最后,在 MainActivityOnCreate() 方法中以这种方式定义了任何菜单的侦听器:
private BottomNavigationView.OnNavigationItemSelectedListener navListener =
            new BottomNavigationView.OnNavigationItemSelectedListener() {
                @Override
                public boolean onNavigationItemSelected(@NonNull MenuItem item) {

                    switch (item.getItemId()){
                        case R.id.camera:
                            hideFragment(selected);
                            selected = camera_fragment;
                            showFragment(seleccionado);
                            break;
                        case R.id.keyboard:
                            hideFragment(selected);
                            selected = keyboard_fragment;
                            showFragment(seleccionado);
                            break;
                        case R.id.settings:
                            hideFragment(selected);
                            selected = settings_fragment;
                            showFragment(seleccionado);
                            break;
                    }

                    return true;

                }
            };

这样,菜单只是在视觉上隐藏和显示 Fragments,并且它们只被声明一次,在应用程序关闭之前不会被销毁,从而维护它们的所有字段和视图内存只要app是运行.

第一个检查片段是否存在如果存在则显示片段并隐藏旧片段

显示片段 fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag(标签)).commit();

隐藏片段 fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag(HomeFragment.class.getSimpleName())).commit();

第二个如果不存在则添加片段 fragmentManager.beginTransaction().add(R.id.fragment_activity, fragment, tag).commit();