如何打开关闭应用程序后打开的最后一个片段并使用导航抽屉和导航组件重新打开它
How to open the last fragment opened after closed app and reopen it using Navigation drawer and Navigation Component
更新
由于onSaveInstanceState
&onRestoreInstanceState
关闭app后无法使用store/restore值,我尝试用dataStore解决,但是不行,这是我的尝试
DataStoreRepository
@ActivityRetainedScoped
public static class DataStoreRepository {
RxDataStore<Preferences> dataStore;
public static Preferences.Key<Integer> CURRENT_DESTINATION =
PreferencesKeys.intKey("CURRENT_DESTINATION");
public final Flowable<Integer> readCurrentDestination;
@Inject
public DataStoreRepository(@ApplicationContext Context context) {
dataStore =
new RxPreferenceDataStoreBuilder(Objects.requireNonNull(context), /*name=*/ "settings").build();
readCurrentDestination = dataStore.data().map(preferences -> {
if (preferences.get(CURRENT_DESTINATION) != null) {
return preferences.get(CURRENT_DESTINATION);
} else {
return R.id.nav_home;
}
});
}
public void saveCurrentDestination(String keyName, int value){
CURRENT_DESTINATION = PreferencesKeys.intKey(keyName);
dataStore.updateDataAsync(prefsIn -> {
MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
Integer currentKey = prefsIn.get(CURRENT_DESTINATION);
if (currentKey == null) {
saveCurrentDestination(keyName,value);
}
mutablePreferences.set(CURRENT_DESTINATION,
currentKey != null ? value : R.id.nav_home);
return Single.just(mutablePreferences);
}).subscribe();
}
}
在 ViewModel 中读取并保存
public final MutableLiveData<Integer> currentDestination = new MutableLiveData<>();
@Inject
public PostViewModel(Repository repository, Utils.DataStoreRepository dataStoreRepository) {
this.repository = repository;
getAllItemsFromDataBase = repository.localDataSource.getAllItems();
this.dataStoreRepository = dataStoreRepository;
dataStoreRepository.readCurrentDestination
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new FlowableSubscriber<Integer>() {
@Override
public void onSubscribe(@NonNull Subscription s) {
s.request(Long.MAX_VALUE);
}
@Override
public void onNext(Integer integer) {
}
@Override
public void onError(Throwable t) {
Log.e(TAG, "onError: " + t.getMessage());
}
@Override
public void onComplete() {
}
});
}
public void saveCurrentDestination(int currentDestination) {
dataStoreRepository
.saveCurrentDestination("CURRENT_DESTINATION", currentDestination);
}
最后是 MainActivity
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@SuppressWarnings("unused")
private AppBarConfiguration mAppBarConfiguration;
private NavHostFragment navHostFragment;
private NavController navController;
NavGraph navGraph;
private PostViewModel postViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
postViewModel = new ViewModelProvider(this).get(PostViewModel.class);
setSupportActionBar(binding.appBarMain.toolbar);
mAppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_home, R.id.nav_accessory,
R.id.nav_arcade, R.id.nav_fashion,
R.id.nav_food, R.id.nav_heath,
R.id.nav_lifestyle, R.id.nav_sports, R.id.about)
.setOpenableLayout(binding.drawerLayout)
.build();
navHostFragment = (NavHostFragment)
getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
if(navHostFragment !=null) {
navController = navHostFragment.getNavController();
}
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(binding.navView, navController);
navGraph = navController.getNavInflater().inflate(R.navigation.mobile_navigation);
postViewModel.currentDestination.observe(this,currentDestination -> {
Log.d(TAG, "currentDestination: " + currentDestination);
Toast.makeText(this,"currentDestination" + currentDestination,Toast.LENGTH_SHORT).show();
navGraph.setStartDestination(currentDestination);
navController.setGraph(navGraph);
});
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
Log.d(TAG, "addOnDestinationChangedListener: " + destination.getId());
postViewModel.saveCurrentDestination(destination.getId());
});
}
@Override
public boolean onSupportNavigateUp() {
return NavigationUI.navigateUp(navController, mAppBarConfiguration)
|| super.onSupportNavigateUp();
}
}
问题详细
在这个应用程序中,我在导航抽屉中有 9 个菜单项和片段,我想将最后打开的片段保存在 savedInstanceState
或 datastore
中,并在用户关闭应用程序并重新打开它后再次显示最后打开的片段,但我不知道我将使用哪种方法
Navigation.findNavController(activity,nav_graph).navigate();
或
binding.navView.setNavigationItemSelectedListener(item -> false);
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
android:id="@+id/app_bar_main"
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
android:background="@color/color_navigation_list_background"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer" />
</androidx.drawerlayout.widget.DrawerLayout>
activity_main_drawer.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_home"
android:title="@string/home"
android:icon="@drawable/home"
/>
<item
android:id="@+id/nav_accessory"
android:title="@string/accessory"
android:icon="@drawable/necklace"
/>
<item
android:id="@+id/nav_arcade"
android:title="@string/arcade"
android:icon="@drawable/arcade_cabinet"
/>
<item
android:id="@+id/nav_fashion"
android:title="@string/fashion"
android:icon="@drawable/fashion_trend"
/>
<item
android:id="@+id/nav_food"
android:title="@string/food"
android:icon="@drawable/hamburger"
/>
<item
android:id="@+id/nav_heath"
android:title="@string/heath"
android:icon="@drawable/clinic"
/>
<item
android:id="@+id/nav_lifestyle"
android:title="@string/lifestyle"
android:icon="@drawable/yoga"
/>
<item
android:id="@+id/nav_sports"
android:title="@string/sports"
android:icon="@drawable/soccer"
/>
<item
android:id="@+id/nav_favorites"
android:title="@string/favorites_posts"
android:icon="@drawable/ic_favorite"
/>
<item
android:id="@+id/about"
android:title="@string/about"
android:icon="@drawable/about"
/>
</group>
</menu>
nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mobile_navigation"
app:startDestination="@id/nav_home">
<fragment
android:id="@+id/nav_home"
android:name="com.blogspot.abtallaldigital.ui.HomeFragment"
android:label="@string/home"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_nav_home_to_detailsFragment"
app:destination="@id/detailsFragment"
app:popUpTo="@id/nav_home" />
</fragment>
<fragment
android:id="@+id/nav_accessory"
android:name="com.blogspot.abtallaldigital.ui.AccessoryFragment"
android:label="@string/accessory"
tools:layout="@layout/fragment_accessory" >
<action
android:id="@+id/action_nav_Accessory_to_detailsFragment"
app:destination="@id/detailsFragment" />
</fragment>
<fragment
android:id="@+id/nav_arcade"
android:name="com.blogspot.abtallaldigital.ui.ArcadeFragment"
android:label="@string/arcade"
tools:layout="@layout/fragment_arcade" >
<action
android:id="@+id/action_nav_Arcade_to_detailsFragment"
app:destination="@id/detailsFragment" />
</fragment>
<fragment
android:id="@+id/nav_fashion"
android:name="com.blogspot.abtallaldigital.ui.FashionFragment"
android:label="@string/fashion"
tools:layout="@layout/fragment_fashion" >
<action
android:id="@+id/action_nav_Fashion_to_detailsFragment"
app:destination="@id/detailsFragment" />
</fragment>
<fragment
android:id="@+id/nav_food"
android:name="com.blogspot.abtallaldigital.ui.FoodFragment"
android:label="@string/food"
tools:layout="@layout/food_fragment" >
<action
android:id="@+id/action_nav_Food_to_detailsFragment"
app:destination="@id/detailsFragment" />
</fragment>
<fragment
android:id="@+id/nav_heath"
android:name="com.blogspot.abtallaldigital.ui.HeathFragment"
android:label="@string/heath"
tools:layout="@layout/heath_fragment" >
<action
android:id="@+id/action_nav_Heath_to_detailsFragment"
app:destination="@id/detailsFragment" />
</fragment>
<fragment
android:id="@+id/nav_lifestyle"
android:name="com.blogspot.abtallaldigital.ui.LifestyleFragment"
android:label="@string/lifestyle"
tools:layout="@layout/lifestyle_fragment" >
<action
android:id="@+id/action_nav_Lifestyle_to_detailsFragment"
app:destination="@id/detailsFragment" />
</fragment>
<fragment
android:id="@+id/nav_sports"
android:name="com.blogspot.abtallaldigital.ui.SportsFragment"
android:label="@string/sports"
tools:layout="@layout/sports_fragment" >
<action
android:id="@+id/action_nav_Sports_to_detailsFragment"
app:destination="@id/detailsFragment" />
</fragment>
<dialog
android:id="@+id/about"
android:name="com.blogspot.abtallaldigital.ui.AboutFragment"
android:label="about"
tools:layout="@layout/about" />
<fragment
android:id="@+id/detailsFragment"
android:name="com.blogspot.abtallaldigital.ui.DetailsFragment"
android:label="Post details"
tools:layout="@layout/fragment_details" >
<argument
android:name="postItem"
app:argType="com.blogspot.abtallaldigital.pojo.Item" />
</fragment>
<fragment
android:id="@+id/nav_favorites"
android:name="com.blogspot.abtallaldigital.ui.FavoritesFragment"
android:label="Favorites posts"
tools:layout="@layout/fragment_favorites" >
<action
android:id="@+id/action_favoritesFragment_to_detailsFragment"
app:destination="@id/detailsFragment" />
</fragment>
</navigation>
MainActivityclass
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
@SuppressWarnings("unused")
private AppBarConfiguration mAppBarConfiguration;
public static Utils.DataStoreRepository DATA_STORE_REPOSITORY;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.appBarMain.toolbar);
mAppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_home, R.id.nav_accessory,
R.id.nav_arcade, R.id.nav_fashion,
R.id.nav_food, R.id.nav_heath,
R.id.nav_lifestyle, R.id.nav_sports, R.id.about)
.setOpenableLayout(binding.drawerLayout)
.build();
NavHostFragment navHostFragment = (NavHostFragment)
getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
assert navHostFragment != null;
NavController navController = navHostFragment.getNavController();
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(binding.navView, navController);
}
@Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
return NavigationUI.navigateUp(navController, mAppBarConfiguration)
|| super.onSupportNavigateUp();
}
}
免责声明:
由于 SharedPreference
迟早会被弃用,下面有一个使用 DataStore
的更新。
使用SharedPreference
onSaveInstanceState
& onRestoreInstanceState
不能用于 store/restore 应用后的值 closed/shut.
即使不关闭应用程序,也不能依赖它们来存储大对象或长时间存储对象。
您可以使用 SharedPreference
来存储映射到应用程序存在之前最后打开的片段的值。
这里我存储了一些任意值,因为建议不要存储应用程序 ID,因为它们可能因应用程序启动而异。因此,您可以存储任意值并将它们映射到当前应用启动时生成的 ID。
我选择这些值作为数组索引:
// Array of fragments
private Integer[] fragments = {
R.id.nav_home,
R.id.nav_accessory,
R.id.nav_arcade,
R.id.nav_fashion,
R.id.nav_food,
R.id.nav_heath,
R.id.nav_lifestyle,
R.id.nav_sports,
R.id.about
};
然后对于应用程序的每次启动;即在 onCreate()
方法中,您可以从 SharedPreference
中选择当前索引,然后调用 graph.setStartDestination()
:
// Getting the last fragment:
SharedPreferences mSharedPrefs = getSharedPreferences("SHARED_PREFS", MODE_PRIVATE);
int fragIndex = mSharedPrefs.getInt(LAST_FRAGMENT, -1); // The last fragment index
// Check if it's a valid index
if (fragIndex >= 0 && fragIndex < fragments.length) {
// Navigate to this fragment
int currentFragment = fragments[fragIndex];
graph.setStartDestination(currentFragment);
// Change the current navGraph
navController.setGraph(graph);
}
一旦使用 navController
的 OnDestinationChangedListener
更改目标,您就可以将新值注册到 sharedPreference:
// Listener to the change in fragments, so that we can updated the shared preference
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
int fragmentIndex = Arrays.asList(fragments).indexOf(destination.getId());
SharedPreferences.Editor editor = mSharedPrefs.edit();
editor.putInt(LAST_FRAGMENT, fragmentIndex).apply();
});
将其集成到您的代码中:
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@SuppressWarnings("unused")
private AppBarConfiguration mAppBarConfiguration;
private NavHostFragment navHostFragment;
private NavController navController;
NavGraph navGraph;
// Array of fragments
private Integer[] fragments = {
R.id.nav_home,
R.id.nav_accessory,
R.id.nav_arcade,
R.id.nav_fashion,
R.id.nav_food,
R.id.nav_heath,
R.id.nav_lifestyle,
R.id.nav_sports,
R.id.about
};
// Key for saving the last fragment in the Shared Preferences
private static final String LAST_FRAGMENT = "LAST_FRAGMENT";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.appBarMain.toolbar);
mAppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_home, R.id.nav_accessory,
R.id.nav_arcade, R.id.nav_fashion,
R.id.nav_food, R.id.nav_heath,
R.id.nav_lifestyle, R.id.nav_sports, R.id.about)
.setOpenableLayout(binding.drawerLayout)
.build();
navHostFragment = (NavHostFragment)
getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
if(navHostFragment !=null) {
navController = navHostFragment.getNavController();
}
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(binding.navView, navController);
navGraph = navController.getNavInflater().inflate(R.navigation.mobile_navigation);
// Getting the last fragment:
SharedPreferences mSharedPrefs = getSharedPreferences("SHARED_PREFS", MODE_PRIVATE);
int fragIndex = mSharedPrefs.getInt(LAST_FRAGMENT, -1); // The last fragment index
// Check if it's a valid index
if (fragIndex >= 0 && fragIndex < fragments.length) {
// Navigate to this fragment
int currentFragment = fragments[fragIndex];
graph.setStartDestination(currentFragment);
// Change the current navGraph
navController.setGraph(graph);
}
// Listener to the change in fragments, so that we can updated the shared preference
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
int fragmentIndex = Arrays.asList(fragments).indexOf(destination.getId());
SharedPreferences.Editor editor = mSharedPrefs.edit();
editor.putInt(LAST_FRAGMENT, fragmentIndex).apply();
});
}
@Override
public boolean onSupportNavigateUp() {
return NavigationUI.navigateUp(navController, mAppBarConfiguration)
|| super.onSupportNavigateUp();
}
}
使用数据存储
Since I migrated from sharedpreferences to dataStore in this project, I tried to do the same as your solution but it doesn't work, I'll post my try and you can look at it to see what's wrong, then you can edit your answer with dataStore soultion
因此,我将使用相同的方法,但使用 DataStore(相同的片段数组,存储索引而不是片段目标 ID)。
因此,您需要添加片段 ID 数组,以便在 DataStore 进程中读取它:
// Array of fragment IDs
private Integer[] fragments = {
R.id.nav_home,
R.id.nav_accessory,
R.id.nav_arcade,
R.id.nav_fashion,
R.id.nav_food,
R.id.nav_heath,
R.id.nav_lifestyle,
R.id.nav_sports,
R.id.about
};
然后在存储库中更改逻辑以使用索引而不是片段 ID:
@Inject
public DataStoreRepository(@ApplicationContext Context context) {
dataStore =
new RxPreferenceDataStoreBuilder(Objects.requireNonNull(context), /*name=*/ "settings").build();
readCurrentDestination =
dataStore.data().map(preferences -> {
Integer fragIndex = preferences.get(CURRENT_DESTINATION);
if (fragIndex == null) fragIndex = 0;
if (fragIndex >= 0 && fragIndex <= fragments.length) {
// Navigate to the fragIndex
return fragments[fragIndex];
} else {
return R.id.nav_home;
}
});
}
并且在 ViewModel
中,你不应该订阅永久 Observable 到 Flowable
因为这将永久提交对观察到的数据的任何更改,而是你可以转换 Flowable
到 Single
这样你就可以在应用程序启动时只获得片段 ID 的一个(第一个)值一次,并且不再注册观察者。 Check Documentation了解更多详情。
在您的 ViewModel
中应用它:
@Inject
public PostViewModel(Repository repository, Utils.DataStoreRepository dataStoreRepository) {
this.repository = repository;
getAllItemsFromDataBase = repository.localDataSource.getAllItems();
this.dataStoreRepository = dataStoreRepository;
dataStoreRepository.readCurrentDestination.firstOrError().subscribeWith(new DisposableSingleObserver<Integer>() {
@Override
public void onSuccess(@NotNull Integer destination) {
// Must be run at UI/Main Thread
runOnUiThread(() -> {
currentDestination.setValue(destination);
});
}
@Override
public void onError(@NotNull Throwable error) {
error.printStackTrace();
}
}).dispose();
}
然后当您观察 activity 中的 currentDestination
MutableLiveData 时:更改那里的当前目的地(您已经做得很好):
postViewModel.currentDestination.observe(this,currentDestination -> {
Log.d(TAG, "currentDestination: " + currentDestination);
Toast.makeText(this,"currentDestination" + currentDestination,Toast.LENGTH_SHORT).show();
navGraph.setStartDestination(currentDestination);
navController.setGraph(navGraph);
});
每当目标更改时将当前片段保存到 DataStore
:
在ViewModel
:
public void saveCurrentDestination(int value){
int fragmentIndex = Arrays.asList(fragments).indexOf(value);
CURRENT_DESTINATION = PreferencesKeys.intKey(keyName);
dataStore.updateDataAsync(prefsIn -> {
MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
mutablePreferences.set(CURRENT_DESTINATION, fragmentIndex);
return Single.just(mutablePreferences);
}).subscribe();
}
如何在导航图中存储当前位置并不重要(我们实际上是在谈论存储单个 long
值以及最终一些参数值)。
先决条件本身应该已经解释了它是如何工作的:
与此类似,可能会丢失返回堆栈条目,但可以直接导航。如果这还不够,请存储 NavController
后退堆栈条目并播放它们(不建议)。
更新
由于onSaveInstanceState
&onRestoreInstanceState
关闭app后无法使用store/restore值,我尝试用dataStore解决,但是不行,这是我的尝试
DataStoreRepository
@ActivityRetainedScoped
public static class DataStoreRepository {
RxDataStore<Preferences> dataStore;
public static Preferences.Key<Integer> CURRENT_DESTINATION =
PreferencesKeys.intKey("CURRENT_DESTINATION");
public final Flowable<Integer> readCurrentDestination;
@Inject
public DataStoreRepository(@ApplicationContext Context context) {
dataStore =
new RxPreferenceDataStoreBuilder(Objects.requireNonNull(context), /*name=*/ "settings").build();
readCurrentDestination = dataStore.data().map(preferences -> {
if (preferences.get(CURRENT_DESTINATION) != null) {
return preferences.get(CURRENT_DESTINATION);
} else {
return R.id.nav_home;
}
});
}
public void saveCurrentDestination(String keyName, int value){
CURRENT_DESTINATION = PreferencesKeys.intKey(keyName);
dataStore.updateDataAsync(prefsIn -> {
MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
Integer currentKey = prefsIn.get(CURRENT_DESTINATION);
if (currentKey == null) {
saveCurrentDestination(keyName,value);
}
mutablePreferences.set(CURRENT_DESTINATION,
currentKey != null ? value : R.id.nav_home);
return Single.just(mutablePreferences);
}).subscribe();
}
}
在 ViewModel 中读取并保存
public final MutableLiveData<Integer> currentDestination = new MutableLiveData<>();
@Inject
public PostViewModel(Repository repository, Utils.DataStoreRepository dataStoreRepository) {
this.repository = repository;
getAllItemsFromDataBase = repository.localDataSource.getAllItems();
this.dataStoreRepository = dataStoreRepository;
dataStoreRepository.readCurrentDestination
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new FlowableSubscriber<Integer>() {
@Override
public void onSubscribe(@NonNull Subscription s) {
s.request(Long.MAX_VALUE);
}
@Override
public void onNext(Integer integer) {
}
@Override
public void onError(Throwable t) {
Log.e(TAG, "onError: " + t.getMessage());
}
@Override
public void onComplete() {
}
});
}
public void saveCurrentDestination(int currentDestination) {
dataStoreRepository
.saveCurrentDestination("CURRENT_DESTINATION", currentDestination);
}
最后是 MainActivity
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@SuppressWarnings("unused")
private AppBarConfiguration mAppBarConfiguration;
private NavHostFragment navHostFragment;
private NavController navController;
NavGraph navGraph;
private PostViewModel postViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
postViewModel = new ViewModelProvider(this).get(PostViewModel.class);
setSupportActionBar(binding.appBarMain.toolbar);
mAppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_home, R.id.nav_accessory,
R.id.nav_arcade, R.id.nav_fashion,
R.id.nav_food, R.id.nav_heath,
R.id.nav_lifestyle, R.id.nav_sports, R.id.about)
.setOpenableLayout(binding.drawerLayout)
.build();
navHostFragment = (NavHostFragment)
getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
if(navHostFragment !=null) {
navController = navHostFragment.getNavController();
}
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(binding.navView, navController);
navGraph = navController.getNavInflater().inflate(R.navigation.mobile_navigation);
postViewModel.currentDestination.observe(this,currentDestination -> {
Log.d(TAG, "currentDestination: " + currentDestination);
Toast.makeText(this,"currentDestination" + currentDestination,Toast.LENGTH_SHORT).show();
navGraph.setStartDestination(currentDestination);
navController.setGraph(navGraph);
});
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
Log.d(TAG, "addOnDestinationChangedListener: " + destination.getId());
postViewModel.saveCurrentDestination(destination.getId());
});
}
@Override
public boolean onSupportNavigateUp() {
return NavigationUI.navigateUp(navController, mAppBarConfiguration)
|| super.onSupportNavigateUp();
}
}
问题详细
在这个应用程序中,我在导航抽屉中有 9 个菜单项和片段,我想将最后打开的片段保存在 savedInstanceState
或 datastore
中,并在用户关闭应用程序并重新打开它后再次显示最后打开的片段,但我不知道我将使用哪种方法
Navigation.findNavController(activity,nav_graph).navigate();
或
binding.navView.setNavigationItemSelectedListener(item -> false);
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
android:id="@+id/app_bar_main"
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
android:background="@color/color_navigation_list_background"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer" />
</androidx.drawerlayout.widget.DrawerLayout>
activity_main_drawer.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_home"
android:title="@string/home"
android:icon="@drawable/home"
/>
<item
android:id="@+id/nav_accessory"
android:title="@string/accessory"
android:icon="@drawable/necklace"
/>
<item
android:id="@+id/nav_arcade"
android:title="@string/arcade"
android:icon="@drawable/arcade_cabinet"
/>
<item
android:id="@+id/nav_fashion"
android:title="@string/fashion"
android:icon="@drawable/fashion_trend"
/>
<item
android:id="@+id/nav_food"
android:title="@string/food"
android:icon="@drawable/hamburger"
/>
<item
android:id="@+id/nav_heath"
android:title="@string/heath"
android:icon="@drawable/clinic"
/>
<item
android:id="@+id/nav_lifestyle"
android:title="@string/lifestyle"
android:icon="@drawable/yoga"
/>
<item
android:id="@+id/nav_sports"
android:title="@string/sports"
android:icon="@drawable/soccer"
/>
<item
android:id="@+id/nav_favorites"
android:title="@string/favorites_posts"
android:icon="@drawable/ic_favorite"
/>
<item
android:id="@+id/about"
android:title="@string/about"
android:icon="@drawable/about"
/>
</group>
</menu>
nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mobile_navigation"
app:startDestination="@id/nav_home">
<fragment
android:id="@+id/nav_home"
android:name="com.blogspot.abtallaldigital.ui.HomeFragment"
android:label="@string/home"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_nav_home_to_detailsFragment"
app:destination="@id/detailsFragment"
app:popUpTo="@id/nav_home" />
</fragment>
<fragment
android:id="@+id/nav_accessory"
android:name="com.blogspot.abtallaldigital.ui.AccessoryFragment"
android:label="@string/accessory"
tools:layout="@layout/fragment_accessory" >
<action
android:id="@+id/action_nav_Accessory_to_detailsFragment"
app:destination="@id/detailsFragment" />
</fragment>
<fragment
android:id="@+id/nav_arcade"
android:name="com.blogspot.abtallaldigital.ui.ArcadeFragment"
android:label="@string/arcade"
tools:layout="@layout/fragment_arcade" >
<action
android:id="@+id/action_nav_Arcade_to_detailsFragment"
app:destination="@id/detailsFragment" />
</fragment>
<fragment
android:id="@+id/nav_fashion"
android:name="com.blogspot.abtallaldigital.ui.FashionFragment"
android:label="@string/fashion"
tools:layout="@layout/fragment_fashion" >
<action
android:id="@+id/action_nav_Fashion_to_detailsFragment"
app:destination="@id/detailsFragment" />
</fragment>
<fragment
android:id="@+id/nav_food"
android:name="com.blogspot.abtallaldigital.ui.FoodFragment"
android:label="@string/food"
tools:layout="@layout/food_fragment" >
<action
android:id="@+id/action_nav_Food_to_detailsFragment"
app:destination="@id/detailsFragment" />
</fragment>
<fragment
android:id="@+id/nav_heath"
android:name="com.blogspot.abtallaldigital.ui.HeathFragment"
android:label="@string/heath"
tools:layout="@layout/heath_fragment" >
<action
android:id="@+id/action_nav_Heath_to_detailsFragment"
app:destination="@id/detailsFragment" />
</fragment>
<fragment
android:id="@+id/nav_lifestyle"
android:name="com.blogspot.abtallaldigital.ui.LifestyleFragment"
android:label="@string/lifestyle"
tools:layout="@layout/lifestyle_fragment" >
<action
android:id="@+id/action_nav_Lifestyle_to_detailsFragment"
app:destination="@id/detailsFragment" />
</fragment>
<fragment
android:id="@+id/nav_sports"
android:name="com.blogspot.abtallaldigital.ui.SportsFragment"
android:label="@string/sports"
tools:layout="@layout/sports_fragment" >
<action
android:id="@+id/action_nav_Sports_to_detailsFragment"
app:destination="@id/detailsFragment" />
</fragment>
<dialog
android:id="@+id/about"
android:name="com.blogspot.abtallaldigital.ui.AboutFragment"
android:label="about"
tools:layout="@layout/about" />
<fragment
android:id="@+id/detailsFragment"
android:name="com.blogspot.abtallaldigital.ui.DetailsFragment"
android:label="Post details"
tools:layout="@layout/fragment_details" >
<argument
android:name="postItem"
app:argType="com.blogspot.abtallaldigital.pojo.Item" />
</fragment>
<fragment
android:id="@+id/nav_favorites"
android:name="com.blogspot.abtallaldigital.ui.FavoritesFragment"
android:label="Favorites posts"
tools:layout="@layout/fragment_favorites" >
<action
android:id="@+id/action_favoritesFragment_to_detailsFragment"
app:destination="@id/detailsFragment" />
</fragment>
</navigation>
MainActivityclass
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
@SuppressWarnings("unused")
private AppBarConfiguration mAppBarConfiguration;
public static Utils.DataStoreRepository DATA_STORE_REPOSITORY;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.appBarMain.toolbar);
mAppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_home, R.id.nav_accessory,
R.id.nav_arcade, R.id.nav_fashion,
R.id.nav_food, R.id.nav_heath,
R.id.nav_lifestyle, R.id.nav_sports, R.id.about)
.setOpenableLayout(binding.drawerLayout)
.build();
NavHostFragment navHostFragment = (NavHostFragment)
getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
assert navHostFragment != null;
NavController navController = navHostFragment.getNavController();
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(binding.navView, navController);
}
@Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
return NavigationUI.navigateUp(navController, mAppBarConfiguration)
|| super.onSupportNavigateUp();
}
}
免责声明:
由于 SharedPreference
迟早会被弃用,下面有一个使用 DataStore
的更新。
使用SharedPreference
onSaveInstanceState
& onRestoreInstanceState
不能用于 store/restore 应用后的值 closed/shut.
即使不关闭应用程序,也不能依赖它们来存储大对象或长时间存储对象。
您可以使用 SharedPreference
来存储映射到应用程序存在之前最后打开的片段的值。
这里我存储了一些任意值,因为建议不要存储应用程序 ID,因为它们可能因应用程序启动而异。因此,您可以存储任意值并将它们映射到当前应用启动时生成的 ID。
我选择这些值作为数组索引:
// Array of fragments
private Integer[] fragments = {
R.id.nav_home,
R.id.nav_accessory,
R.id.nav_arcade,
R.id.nav_fashion,
R.id.nav_food,
R.id.nav_heath,
R.id.nav_lifestyle,
R.id.nav_sports,
R.id.about
};
然后对于应用程序的每次启动;即在 onCreate()
方法中,您可以从 SharedPreference
中选择当前索引,然后调用 graph.setStartDestination()
:
// Getting the last fragment:
SharedPreferences mSharedPrefs = getSharedPreferences("SHARED_PREFS", MODE_PRIVATE);
int fragIndex = mSharedPrefs.getInt(LAST_FRAGMENT, -1); // The last fragment index
// Check if it's a valid index
if (fragIndex >= 0 && fragIndex < fragments.length) {
// Navigate to this fragment
int currentFragment = fragments[fragIndex];
graph.setStartDestination(currentFragment);
// Change the current navGraph
navController.setGraph(graph);
}
一旦使用 navController
的 OnDestinationChangedListener
更改目标,您就可以将新值注册到 sharedPreference:
// Listener to the change in fragments, so that we can updated the shared preference
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
int fragmentIndex = Arrays.asList(fragments).indexOf(destination.getId());
SharedPreferences.Editor editor = mSharedPrefs.edit();
editor.putInt(LAST_FRAGMENT, fragmentIndex).apply();
});
将其集成到您的代码中:
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@SuppressWarnings("unused")
private AppBarConfiguration mAppBarConfiguration;
private NavHostFragment navHostFragment;
private NavController navController;
NavGraph navGraph;
// Array of fragments
private Integer[] fragments = {
R.id.nav_home,
R.id.nav_accessory,
R.id.nav_arcade,
R.id.nav_fashion,
R.id.nav_food,
R.id.nav_heath,
R.id.nav_lifestyle,
R.id.nav_sports,
R.id.about
};
// Key for saving the last fragment in the Shared Preferences
private static final String LAST_FRAGMENT = "LAST_FRAGMENT";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.appBarMain.toolbar);
mAppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_home, R.id.nav_accessory,
R.id.nav_arcade, R.id.nav_fashion,
R.id.nav_food, R.id.nav_heath,
R.id.nav_lifestyle, R.id.nav_sports, R.id.about)
.setOpenableLayout(binding.drawerLayout)
.build();
navHostFragment = (NavHostFragment)
getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
if(navHostFragment !=null) {
navController = navHostFragment.getNavController();
}
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(binding.navView, navController);
navGraph = navController.getNavInflater().inflate(R.navigation.mobile_navigation);
// Getting the last fragment:
SharedPreferences mSharedPrefs = getSharedPreferences("SHARED_PREFS", MODE_PRIVATE);
int fragIndex = mSharedPrefs.getInt(LAST_FRAGMENT, -1); // The last fragment index
// Check if it's a valid index
if (fragIndex >= 0 && fragIndex < fragments.length) {
// Navigate to this fragment
int currentFragment = fragments[fragIndex];
graph.setStartDestination(currentFragment);
// Change the current navGraph
navController.setGraph(graph);
}
// Listener to the change in fragments, so that we can updated the shared preference
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
int fragmentIndex = Arrays.asList(fragments).indexOf(destination.getId());
SharedPreferences.Editor editor = mSharedPrefs.edit();
editor.putInt(LAST_FRAGMENT, fragmentIndex).apply();
});
}
@Override
public boolean onSupportNavigateUp() {
return NavigationUI.navigateUp(navController, mAppBarConfiguration)
|| super.onSupportNavigateUp();
}
}
使用数据存储
Since I migrated from sharedpreferences to dataStore in this project, I tried to do the same as your solution but it doesn't work, I'll post my try and you can look at it to see what's wrong, then you can edit your answer with dataStore soultion
因此,我将使用相同的方法,但使用 DataStore(相同的片段数组,存储索引而不是片段目标 ID)。
因此,您需要添加片段 ID 数组,以便在 DataStore 进程中读取它:
// Array of fragment IDs
private Integer[] fragments = {
R.id.nav_home,
R.id.nav_accessory,
R.id.nav_arcade,
R.id.nav_fashion,
R.id.nav_food,
R.id.nav_heath,
R.id.nav_lifestyle,
R.id.nav_sports,
R.id.about
};
然后在存储库中更改逻辑以使用索引而不是片段 ID:
@Inject
public DataStoreRepository(@ApplicationContext Context context) {
dataStore =
new RxPreferenceDataStoreBuilder(Objects.requireNonNull(context), /*name=*/ "settings").build();
readCurrentDestination =
dataStore.data().map(preferences -> {
Integer fragIndex = preferences.get(CURRENT_DESTINATION);
if (fragIndex == null) fragIndex = 0;
if (fragIndex >= 0 && fragIndex <= fragments.length) {
// Navigate to the fragIndex
return fragments[fragIndex];
} else {
return R.id.nav_home;
}
});
}
并且在 ViewModel
中,你不应该订阅永久 Observable 到 Flowable
因为这将永久提交对观察到的数据的任何更改,而是你可以转换 Flowable
到 Single
这样你就可以在应用程序启动时只获得片段 ID 的一个(第一个)值一次,并且不再注册观察者。 Check Documentation了解更多详情。
在您的 ViewModel
中应用它:
@Inject
public PostViewModel(Repository repository, Utils.DataStoreRepository dataStoreRepository) {
this.repository = repository;
getAllItemsFromDataBase = repository.localDataSource.getAllItems();
this.dataStoreRepository = dataStoreRepository;
dataStoreRepository.readCurrentDestination.firstOrError().subscribeWith(new DisposableSingleObserver<Integer>() {
@Override
public void onSuccess(@NotNull Integer destination) {
// Must be run at UI/Main Thread
runOnUiThread(() -> {
currentDestination.setValue(destination);
});
}
@Override
public void onError(@NotNull Throwable error) {
error.printStackTrace();
}
}).dispose();
}
然后当您观察 activity 中的 currentDestination
MutableLiveData 时:更改那里的当前目的地(您已经做得很好):
postViewModel.currentDestination.observe(this,currentDestination -> {
Log.d(TAG, "currentDestination: " + currentDestination);
Toast.makeText(this,"currentDestination" + currentDestination,Toast.LENGTH_SHORT).show();
navGraph.setStartDestination(currentDestination);
navController.setGraph(navGraph);
});
每当目标更改时将当前片段保存到 DataStore
:
在ViewModel
:
public void saveCurrentDestination(int value){
int fragmentIndex = Arrays.asList(fragments).indexOf(value);
CURRENT_DESTINATION = PreferencesKeys.intKey(keyName);
dataStore.updateDataAsync(prefsIn -> {
MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
mutablePreferences.set(CURRENT_DESTINATION, fragmentIndex);
return Single.just(mutablePreferences);
}).subscribe();
}
如何在导航图中存储当前位置并不重要(我们实际上是在谈论存储单个 long
值以及最终一些参数值)。
先决条件本身应该已经解释了它是如何工作的:
与此类似,可能会丢失返回堆栈条目,但可以直接导航。如果这还不够,请存储 NavController
后退堆栈条目并播放它们(不建议)。