能否在onNavigationItemSelected()等其他方法中访问savedInstance?
这问题我之前也碰到过!配置变更(比如旋转屏幕)的时候,系统会重建你的Activity,抽屉的选中状态能保留是因为系统默认帮你存了MenuItem的选中标记,但Fragment的显示状态得咱们自己手动处理才行。给你几个实用的解决方案:
方案1:通过savedInstanceState保存当前Fragment标记
Activity重建时,系统会调用onSaveInstanceState方法,我们可以在这里把当前显示的Fragment的tag或者对应的MenuItem ID存起来,等重建完成后再恢复。
步骤1:保存状态
在你的DrawerLayout Activity中重写onSaveInstanceState:
@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // 获取当前显示的Fragment实例 Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.content_frame); if (currentFragment != null) { // 保存Fragment的唯一tag(加载Fragment时要设置这个tag) outState.putString("ACTIVE_FRAGMENT_TAG", currentFragment.getTag()); // 或者也可以保存对应的MenuItem ID,看你哪种方式更方便 // outState.putInt("SELECTED_MENU_ID", mCurrentSelectedMenuId); } }
步骤2:恢复状态
在onCreate方法中判断savedInstanceState是否存在,如果存在就取出保存的标记,重新加载对应的Fragment:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化DrawerLayout、NavigationView等组件... if (savedInstanceState == null) { // 第一次启动App,加载默认Fragment loadDefaultFragment(); } else { // 从保存的状态中取出Fragment tag String activeTag = savedInstanceState.getString("ACTIVE_FRAGMENT_TAG"); if (activeTag != null) { Fragment savedFragment = getSupportFragmentManager().findFragmentByTag(activeTag); if (savedFragment != null) { // 重新显示这个Fragment getSupportFragmentManager().beginTransaction() .replace(R.id.content_frame, savedFragment, activeTag) .commit(); // 同步更新抽屉的选中状态(保险起见) int menuId = getMenuIdFromFragmentTag(activeTag); // 自己实现这个映射方法 mNavigationView.setCheckedItem(menuId); } } } }
注意: 加载Fragment的时候一定要给每个Fragment设置唯一的tag,比如getSupportFragmentManager().beginTransaction().replace(R.id.content_frame, new HomeFragment(), "HOME_TAG").commit();,这样才能通过tag找到对应的实例。
方案2:用ViewModel保存选中状态(更推荐)
ViewModel在配置变更时不会被销毁,是保存状态的绝佳选择。我们可以用它来记录当前选中的MenuItem ID,Activity重建后直接根据这个ID加载对应的Fragment。
步骤1:创建ViewModel类
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; public class MainActivityViewModel extends ViewModel { // 保存当前选中的MenuItem ID private final MutableLiveData<Integer> selectedMenuId = new MutableLiveData<>(); public void setSelectedMenuId(int id) { selectedMenuId.setValue(id); } public MutableLiveData<Integer> getSelectedMenuId() { return selectedMenuId; } }
步骤2:在Activity中使用ViewModel
import androidx.lifecycle.ViewModelProvider; public class MainActivity extends AppCompatActivity { private MainActivityViewModel mViewModel; private NavigationView mNavigationView; private DrawerLayout mDrawerLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化组件 mDrawerLayout = findViewById(R.id.drawer_layout); mNavigationView = findViewById(R.id.nav_view); // 获取ViewModel实例 mViewModel = new ViewModelProvider(this).get(MainActivityViewModel.class); // 设置抽屉菜单的点击事件 mNavigationView.setNavigationItemSelectedListener(item -> { int menuId = item.getItemId(); // 把选中的ID保存到ViewModel mViewModel.setSelectedMenuId(menuId); // 加载对应的Fragment loadFragmentByMenuId(menuId); // 关闭抽屉 mDrawerLayout.closeDrawer(GravityCompat.START); return true; }); // 观察ViewModel中的选中ID,自动恢复Fragment mViewModel.getSelectedMenuId().observe(this, menuId -> { if (menuId != 0) { loadFragmentByMenuId(menuId); mNavigationView.setCheckedItem(menuId); } else { // 第一次启动,加载默认Fragment loadDefaultFragment(); } }); } // 根据MenuItem ID加载对应的Fragment private void loadFragmentByMenuId(int menuId) { Fragment fragment = null; String tag = null; switch (menuId) { case R.id.nav_home: fragment = new HomeFragment(); tag = "HOME_TAG"; break; case R.id.nav_profile: fragment = new ProfileFragment(); tag = "PROFILE_TAG"; break; // 其他菜单对应的Fragment } if (fragment != null) { getSupportFragmentManager().beginTransaction() .replace(R.id.content_frame, fragment, tag) .commit(); } } // 加载默认Fragment private void loadDefaultFragment() { loadFragmentByMenuId(R.id.nav_home); mNavigationView.setCheckedItem(R.id.nav_home); } }
方案3:禁止Activity重建(应急用,不推荐)
如果你只是临时解决问题,可以在AndroidManifest.xml中给Activity添加configChanges属性,让系统在配置变更时不重建Activity:
<activity android:name=".MainActivity" android:configChanges="orientation|screenSize|keyboardHidden"> <!-- 其他属性 --> </activity>
注意: 这个方法不推荐,因为系统无法自动适配不同屏幕的资源(比如横屏布局),长期来看会增加维护成本。
额外注意点
- 你的Fragment必须要有无参构造函数,系统重建Fragment时会用无参构造实例化它。
- 如果Fragment自己有需要保存的状态(比如输入框内容、列表滚动位置),要在Fragment的
onSaveInstanceState方法中保存,然后在onViewCreated或者onActivityCreated中恢复。
内容的提问来源于stack exchange,提问作者najm

