如何在不破坏和重新创建片段的情况下在片段之间切换? (以类似静态的方式)
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
是静态的,这样当我在它们之间切换时它们的内容和视图不会消失。我试图在 MainActivity
的 onCreate()
方法中创建它们,但它只有助于在 EditText
小部件中保留文本,其余视图和内容消失。
我看过其他类似的问题,但回答得不好,我是新手。在对类似问题的一些回答之后,我尝试使用 add()
或 attach()
之类的函数而不是 replace()
但我认为我做得不好;事实上,有时我的应用程序会崩溃。
我是否也应该在此处粘贴我的 layout.xml 文件?这些片段被“显示”成一个简单的 FrameLayout
。 先谢谢了:)
replace()
意味着销毁这个并在它的位置添加一个新的,因为你的代码现在是你不能使用 add()
因为根据那个开关情况你将创建一个新的每次导航时片段实例。
它会浪费内存,最终应用程序会因 OutOfMemoryException
而崩溃
你能做什么?
不幸的是,底部导航没有太多选项,但您可以使用以下选项进行改进
1 对每个片段使用 viewmodels
并将所有 viewmodels
附加到主机活动中,这样您就不必加载数据每次创建该片段时,数据都会在视图模型中存活
2 或使用 viewpager 来保存您的片段 viewpager 将能够将所有片段保存在内存中并查看所需的片段,设置当前片段以编程方式
通过从 OnNavigationItemSelected()
在 viewpager 上调用 setCurrentItem()
3 使用 add()
而不是 replace()
并使您的片段 单例 这样你就不会每次都加载数据(如果你不使用视图模型),如果用户转到另一个片段并且在去其他任何地方之前 return 到这个你只需弹出返回使用 getSupportFragmantManager().popBackStack()
删除此片段堆栈,否则弹出返回堆栈并添加另一个片段
4 使用其他形式的导航参见 navigation components 让您的生活更轻松
也许有更好的解决方案,但根据我的经验,这就是我能想到的,不是很多,虽然导航组件也做替换事情,但我认为如果你使用视图模型,这不是很糟糕保存数据。
快乐编码
我是这样成功解决问题的:
- 首先将所有的Fragment声明为
MainActivity
class里面的字段,还有变量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();
}
- 最后,在
MainActivity
的 OnCreate()
方法中以这种方式定义了任何菜单的侦听器:
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();
我的应用使用 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
是静态的,这样当我在它们之间切换时它们的内容和视图不会消失。我试图在 MainActivity
的 onCreate()
方法中创建它们,但它只有助于在 EditText
小部件中保留文本,其余视图和内容消失。
我看过其他类似的问题,但回答得不好,我是新手。在对类似问题的一些回答之后,我尝试使用 add()
或 attach()
之类的函数而不是 replace()
但我认为我做得不好;事实上,有时我的应用程序会崩溃。
我是否也应该在此处粘贴我的 layout.xml 文件?这些片段被“显示”成一个简单的 FrameLayout
。 先谢谢了:)
replace()
意味着销毁这个并在它的位置添加一个新的,因为你的代码现在是你不能使用 add()
因为根据那个开关情况你将创建一个新的每次导航时片段实例。
它会浪费内存,最终应用程序会因 OutOfMemoryException
你能做什么?
不幸的是,底部导航没有太多选项,但您可以使用以下选项进行改进
1 对每个片段使用 viewmodels
并将所有 viewmodels
附加到主机活动中,这样您就不必加载数据每次创建该片段时,数据都会在视图模型中存活
2 或使用 viewpager 来保存您的片段 viewpager 将能够将所有片段保存在内存中并查看所需的片段,设置当前片段以编程方式
通过从 OnNavigationItemSelected()
setCurrentItem()
3 使用 add()
而不是 replace()
并使您的片段 单例 这样你就不会每次都加载数据(如果你不使用视图模型),如果用户转到另一个片段并且在去其他任何地方之前 return 到这个你只需弹出返回使用 getSupportFragmantManager().popBackStack()
删除此片段堆栈,否则弹出返回堆栈并添加另一个片段
4 使用其他形式的导航参见 navigation components 让您的生活更轻松
也许有更好的解决方案,但根据我的经验,这就是我能想到的,不是很多,虽然导航组件也做替换事情,但我认为如果你使用视图模型,这不是很糟糕保存数据。
快乐编码
我是这样成功解决问题的:
- 首先将所有的Fragment声明为
MainActivity
class里面的字段,还有变量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();
}
- 最后,在
MainActivity
的OnCreate()
方法中以这种方式定义了任何菜单的侦听器:
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();