我正在玩原生 Android 应用程序导航,使用 BottomNavigationBar 在 Fragments 之间切换:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
}
private BottomNavigationView.OnNavigationItemSelectedListener navListener =
new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
Fragment selectedFragment = null;
switch (item.getItemId()) {
case R.id.first:
selectedFragment = new FirstFragment();
break;
case R.id.second:
selectedFragment = new SecondFragment();
break;
case R.id.third:
selectedFragment = new ThirdFragment();
break;
}
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, selectedFragment)
.commit();
return true;
}
};
}
我希望这些
Fragments
在导航之间持续存在。有什么建议吗?
MainActivity
类中的字段,以及变量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;`
//...
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;
}
};
这样,菜单只是在视觉上隐藏和显示片段,并且它们仅声明一次,在应用程序关闭之前不会被销毁,因此只要应用程序正在运行,它们的所有字段和视图都会保留在内存中。
replace()
意味着销毁这个并在其位置添加新的,并且由于您的代码现在您也不能使用 add()
,因为根据该开关情况,您每次导航时都会创建一个新的片段实例。
它会浪费内存,最终应用程序会崩溃,并显示 OutOfMemoryException
你能做什么?
不幸的是,底部导航没有太多选项,但您可以使用以下选项进行改进
1 为每个片段使用
viewmodels
,并将所有viewmodels
附加到主机活动中,这样您就不必在每次创建片段时加载数据,数据将保留在视图模型中
2 或使用 viewpager 来保存片段,viewpager 将能够将所有片段保存在内存中并查看所需的片段,以编程方式设置当前片段 通过从
setCurrentItem()
在 viewpager 上调用 OnNavigationItemSelected()
3使用
add()
代替replace()
并使你的片段是singleton这样你就不会每次都加载数据(如果你不使用视图模型)现在也可以跟踪最后一个片段,如果用户转到另一个片段,在转到其他任何地方之前返回到这个片段时,您只需使用 getSupportFragmantManager().popBackStack()
弹出后退以删除此片段堆栈,否则弹出后退堆栈并添加其他片段
4 使用其他形式的导航请参阅导航组件让您的生活变得更轻松
也许有更好的解决方案,但这就是我根据我的经验所能想到的,虽然导航组件也可以做替换的事情,但我认为如果你使用视图模型来保存数据,这并不是很糟糕.
快乐编码
第一个检查片段是否存在,如果存在则显示片段并隐藏旧片段
显示片段 fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag(tag)).commit();
隐藏片段fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag(HomeFragment.class.getSimpleName())).commit();
第二个如果不存在则添加片段 fragmentManager.beginTransaction().add(R.id.fragment_activity,fragment,tag).commit();