Androidx BottomNavigationView 不会相应地播放过渡动画
Androidx BottomNavigationView does not play transition animations accordingly
-
android
-
android-architecture-navigation
-
androidx
-
android-jetpack-navigation
-
android-bottomnavigationview
我使用导航组件设置了底部导航视图。片段之间的用户导航工作正常。
问题是通过底部导航视图导航不会播放在导航组件中配置的动画,即当正确触摸卡片时以滑动样式动画,单击底部导航视图中的按钮以淡入淡出动画样式,覆盖导航组件中定义的操作属性。
res/menu/bottom_navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/home_fragment"
android:title="@string/bottom_navigation_home_title"
android:icon="@drawable/ic_home"
app:showAsAction="ifRoom" />
<item android:id="@+id/schedule_fragment"
android:title="@string/bottom_navigation_schedule_title"
android:icon="@drawable/ic_schedule"
app:showAsAction="ifRoom" />
</menu>
res/anim/slide_in_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="-100%"
android:toXDelta="0%"
android:fromYDelta="0%"
android:toYDelta="0%"
android:duration="700" />
</set>
res/anim/slide_in_right.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="100%"
android:toXDelta="0%"
android:fromYDelta="0%"
android:toYDelta="0%"
android:duration="700" />
</set>
res/anim/slide_out_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0%"
android:toXDelta="-100%"
android:fromYDelta="0%"
android:toYDelta="0%"
android:duration="700" />
</set>
res/anim/slide_out_right.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0%"
android:toXDelta="100%"
android:fromYDelta="0%"
android:toYDelta="0%"
android:duration="700" />
</set>
res/navigation/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/nav_graph"
app:startDestination="@id/home_fragment">
<fragment
android:id="@+id/home_fragment"
android:name="com.sslabs.whatsappcleaner.ui.HomeFragment"
android:label="home_fragment"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_home_fragment_to_schedule_fragment"
app:destination="@id/schedule_fragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"
app:popUpTo="@id/home_fragment" />
</fragment>
<fragment
android:id="@+id/schedule_fragment"
android:name="com.sslabs.whatsappcleaner.ui.ScheduleFragment"
android:label="schedule_fragment"
tools:layout="@layout/fragment_schedule">
</fragment>
</navigation>
res/layout/fragment_home.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<com.google.android.material.card.MaterialCardView
android:id="@+id/home_schedule_card"
android:layout_width="344dp"
android:layout_height="148dp"
app:cardBackgroundColor="@android:color/holo_blue_dark"
app:rippleColor="@android:color/holo_orange_dark" />
</layout>
res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.constraintlayout.widget.ConstraintLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_height="0dp"
android:layout_width="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/bottom_navigation"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/bottom_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
com.sslabs.whatsappcleaner.ui.MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val navController: NavController = Navigation.findNavController(this, R.id.nav_host_fragment)
NavigationUI.setupWithNavController(binding.bottomNavigation, navController)
}
}
com.sslabs.whatsappcleaner.ui.HomeFragment
class HomeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: FragmentHomeBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_home, container, false)
binding.homeScheduleCard.setOnClickListener {
findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToScheduleFragment())
}
return binding.root
}
}
解决方案
After trying the first attempt, the default animations still play, instead of the ones specified in the action. (fade-in/fade-out)
显然,action_id
仅用于 destination
,而不是 anims
。
由于默认动画还在播放,我打开了NavigationUI.java
的代码。以下是:
public static boolean onNavDestinationSelected(@NonNull MenuItem item,
@NonNull NavController navController) {
NavOptions.Builder builder = new NavOptions.Builder()
.setLaunchSingleTop(true)
.setEnterAnim(R.anim.nav_default_enter_anim)
.setExitAnim(R.anim.nav_default_exit_anim)
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
.setPopExitAnim(R.anim.nav_default_pop_exit_anim);
if ((item.getOrder() & Menu.CATEGORY_SECONDARY) == 0) {
builder.setPopUpTo(findStartDestination(navController.getGraph()).getId(), false);
}
NavOptions options = builder.build();
try {
//TODO provide proper API instead of using Exceptions as Control-Flow.
navController.navigate(item.getItemId(), null, options);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
如您所知,在 NavOptions.Builder
中,正在设置默认值 anims
。
您使用的解决方法对我来说并不令人满意。因此,我冒昧地创建了一个 BottomNavigationUI
class 来执行 NavigationUI
的功能,但在可用时使用自定义 anims
。
The difference is in the onNavDestinationSelected
. Please note that NavigationUI
is final, so I couldn't override it.
BottomNavigationUI.class
// don't forget your package
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.navigation.NavAction;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
import androidx.navigation.NavGraph;
import androidx.navigation.NavOptions;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.lang.ref.WeakReference;
import java.util.Set;
public class BottomNavigationUI {
private BottomNavigationUI() {
}
public static boolean onNavDestinationSelected(@NonNull MenuItem item,
@NonNull NavController navController) {
int resId = item.getItemId();
Bundle args = null;
NavOptions options;
NavOptions.Builder optionsBuilder = new NavOptions.Builder()
.setLaunchSingleTop(true)
.setEnterAnim(R.anim.nav_default_enter_anim)
.setExitAnim(R.anim.nav_default_exit_anim)
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
.setPopExitAnim(R.anim.nav_default_pop_exit_anim);
if ((item.getOrder() & Menu.CATEGORY_SECONDARY) == 0) {
optionsBuilder.setPopUpTo(findStartDestination(navController.getGraph()).getId(), false);
}
final NavAction navAction = navController.getCurrentDestination().getAction(resId);
if (navAction != null) {
NavOptions navOptions = navAction.getNavOptions();
// Note : You can Add *setLaunchSingleTop* and *setPopUpTo* from *navOptions* to *builder*
if (navOptions.getEnterAnim() != -1) {
optionsBuilder.setEnterAnim(navOptions.getEnterAnim());
}
if (navOptions.getExitAnim() != -1) {
optionsBuilder.setExitAnim(navOptions.getExitAnim());
}
if (navOptions.getPopEnterAnim() != -1) {
optionsBuilder.setPopEnterAnim(navOptions.getPopEnterAnim());
}
if (navOptions.getPopExitAnim() != -1) {
optionsBuilder.setPopExitAnim(navOptions.getPopExitAnim());
}
Bundle navActionArgs = navAction.getDefaultArguments();
if (navActionArgs != null) {
args = new Bundle();
args.putAll(navActionArgs);
}
}
options = optionsBuilder.build();
try {
//TODO provide proper API instead of using Exceptions as Control-Flow.
navController.navigate(resId, args, options);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
public static void setupWithNavController(
@NonNull final BottomNavigationView bottomNavigationView,
@NonNull final NavController navController) {
bottomNavigationView.setOnNavigationItemSelectedListener(
new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
return onNavDestinationSelected(item, navController);
}
});
final WeakReference<BottomNavigationView> weakReference =
new WeakReference<>(bottomNavigationView);
navController.addOnDestinationChangedListener(
new NavController.OnDestinationChangedListener() {
@Override
public void onDestinationChanged(@NonNull NavController controller,
@NonNull NavDestination destination, @Nullable Bundle arguments) {
BottomNavigationView view = weakReference.get();
if (view == null) {
navController.removeOnDestinationChangedListener(this);
return;
}
Menu menu = view.getMenu();
for (int h = 0, size = menu.size(); h < size; h++) {
MenuItem item = menu.getItem(h);
if (matchDestination(destination, item.getItemId())) {
item.setChecked(true);
}
}
}
});
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
static boolean matchDestination(@NonNull NavDestination destination,
@IdRes int destId) {
NavDestination currentDestination = destination;
while (currentDestination.getId() != destId && currentDestination.getParent() != null) {
currentDestination = currentDestination.getParent();
}
return currentDestination.getId() == destId;
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
static boolean matchDestinations(@NonNull NavDestination destination,
@NonNull Set<Integer> destinationIds) {
NavDestination currentDestination = destination;
do {
if (destinationIds.contains(currentDestination.getId())) {
return true;
}
currentDestination = currentDestination.getParent();
} while (currentDestination != null);
return false;
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
static NavDestination findStartDestination(@NonNull NavGraph graph) {
NavDestination startDestination = graph;
while (startDestination instanceof NavGraph) {
NavGraph parent = (NavGraph) startDestination;
startDestination = parent.findNode(parent.getStartDestination());
}
return startDestination;
}
}
现在,你需要做一些改变。
MainActivity:
之后
BottomNavigationUI.setupWithNavController(bottomNavigationView, navController)
bottomNavigationView.setOnNavigationItemReselectedListener { false }
We will be using BottomNavigationUI
instead of NavigationUI
since it will be able to use the custom anims
instead of just the default ones.
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/nav_graph"
app:startDestination="@id/home_fragment">
<fragment
android:id="@+id/home_fragment"
android:name="com.example.android.navbottomsample.HomeFragment"
android:label="HomeFragment"
tools:layout="@layout/fragment_home">
<action
app:launchSingleTop="true"
android:id="@+id/schedule_fragment"
app:destination="@id/schedule_fragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"
app:popUpTo="@+id/home_fragment" />
</fragment>
<fragment
android:id="@+id/schedule_fragment"
android:name="com.example.android.navbottomsample.ScheduleFragment"
android:label="ScheduleFragment"
tools:layout="@layout/fragment_schedule">
<action
android:id="@+id/home_fragment"
app:destination="@id/home_fragment"
app:enterAnim="@anim/slide_in_left"
app:exitAnim="@anim/slide_out_right"
app:popEnterAnim="@anim/slide_in_right"
app:popExitAnim="@anim/slide_out_left"
app:popUpTo="@+id/home_fragment" />
</fragment>
</navigation>
So you don't have to compare the code, the action_id
is flipped now. You also have 2 actions, since we will be needing its action_id
. Now the whole point of the action_id
swap is that we need the action_id
to match the destination_id
, also the fragment_id
and menu_item_id
.
I also changed the app:popUpTo
. This will be most reasonable when you try it out yourself. You want both fragments to popUpTo
the home_fragment
in the backStack
. So, once you are at home_fragment
, you don't want to go any further back. And once you are at schedule_fragment
, you want to go back to home_fragment
. However, I suggest you use BottomNavigationUI
or NavigationUI
as they dynamically specify the popUpTo
(this will be most useful when you have more than 2 bottom navigation tabs).
我已经在您与我分享的项目中尝试了这个解决方案,它完美。享受 :)
其他解决方案
您可以设置自定义 setOnNavigationItemSelectedListener
以执行您需要的操作 (使用 xml 中指定的自定义动画).
现在,在您的代码 (您与我分享的代码) 中,您已经创建了一个解决方法,如下所示:
bottomNavigationView.setOnNavigationItemSelectedListener {
when (it.itemId) {
R.id.schedule_fragment -> navController.navigate(R.id.home_fragment)
else -> navController.popBackStack()
}
true
}
您可以选择保留它,或者执行以下操作(使用更新后的 nav_graph.xml
):
bottomNavigationView.setOnNavigationItemSelectedListener {
navController.navigate(it.itemId)
true
}
But, you should not forget to change the nav_graph.xml
to the new one.
第一次尝试
好的,如果您查看 NavigationUI AndroidDocs,您会注意到以下内容:
setupWithNavController
Sets up a BottomNavigationView
for use with a NavController
. This will call onNavDestinationSelected(MenuItem, NavController)
when a menu item is selected. The selected item in the BottomNavigationView
will automatically be updated when the destination changes.
onNavDestinationSelected
Attempt to navigate to the NavDestination
associated with the given MenuItem
. This MenuItem
should have been added via one of the helper methods in this class.
Importantly, it assumes the menu item id
matches a valid action id
or destination id
to be navigated to.
The first attempt did not solve the issue. The default animations still play, instead of the ones specified in the action.
android
android-architecture-navigation
androidx
android-jetpack-navigation
android-bottomnavigationview
我使用导航组件设置了底部导航视图。片段之间的用户导航工作正常。
问题是通过底部导航视图导航不会播放在导航组件中配置的动画,即当正确触摸卡片时以滑动样式动画,单击底部导航视图中的按钮以淡入淡出动画样式,覆盖导航组件中定义的操作属性。
res/menu/bottom_navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/home_fragment"
android:title="@string/bottom_navigation_home_title"
android:icon="@drawable/ic_home"
app:showAsAction="ifRoom" />
<item android:id="@+id/schedule_fragment"
android:title="@string/bottom_navigation_schedule_title"
android:icon="@drawable/ic_schedule"
app:showAsAction="ifRoom" />
</menu>
res/anim/slide_in_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="-100%"
android:toXDelta="0%"
android:fromYDelta="0%"
android:toYDelta="0%"
android:duration="700" />
</set>
res/anim/slide_in_right.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="100%"
android:toXDelta="0%"
android:fromYDelta="0%"
android:toYDelta="0%"
android:duration="700" />
</set>
res/anim/slide_out_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0%"
android:toXDelta="-100%"
android:fromYDelta="0%"
android:toYDelta="0%"
android:duration="700" />
</set>
res/anim/slide_out_right.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0%"
android:toXDelta="100%"
android:fromYDelta="0%"
android:toYDelta="0%"
android:duration="700" />
</set>
res/navigation/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/nav_graph"
app:startDestination="@id/home_fragment">
<fragment
android:id="@+id/home_fragment"
android:name="com.sslabs.whatsappcleaner.ui.HomeFragment"
android:label="home_fragment"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_home_fragment_to_schedule_fragment"
app:destination="@id/schedule_fragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"
app:popUpTo="@id/home_fragment" />
</fragment>
<fragment
android:id="@+id/schedule_fragment"
android:name="com.sslabs.whatsappcleaner.ui.ScheduleFragment"
android:label="schedule_fragment"
tools:layout="@layout/fragment_schedule">
</fragment>
</navigation>
res/layout/fragment_home.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<com.google.android.material.card.MaterialCardView
android:id="@+id/home_schedule_card"
android:layout_width="344dp"
android:layout_height="148dp"
app:cardBackgroundColor="@android:color/holo_blue_dark"
app:rippleColor="@android:color/holo_orange_dark" />
</layout>
res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.constraintlayout.widget.ConstraintLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_height="0dp"
android:layout_width="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/bottom_navigation"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/bottom_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
com.sslabs.whatsappcleaner.ui.MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val navController: NavController = Navigation.findNavController(this, R.id.nav_host_fragment)
NavigationUI.setupWithNavController(binding.bottomNavigation, navController)
}
}
com.sslabs.whatsappcleaner.ui.HomeFragment
class HomeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding: FragmentHomeBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_home, container, false)
binding.homeScheduleCard.setOnClickListener {
findNavController().navigate(HomeFragmentDirections.actionHomeFragmentToScheduleFragment())
}
return binding.root
}
}
解决方案
After trying the first attempt, the default animations still play, instead of the ones specified in the action. (fade-in/fade-out)
显然,action_id
仅用于 destination
,而不是 anims
。
由于默认动画还在播放,我打开了NavigationUI.java
的代码。以下是:
public static boolean onNavDestinationSelected(@NonNull MenuItem item,
@NonNull NavController navController) {
NavOptions.Builder builder = new NavOptions.Builder()
.setLaunchSingleTop(true)
.setEnterAnim(R.anim.nav_default_enter_anim)
.setExitAnim(R.anim.nav_default_exit_anim)
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
.setPopExitAnim(R.anim.nav_default_pop_exit_anim);
if ((item.getOrder() & Menu.CATEGORY_SECONDARY) == 0) {
builder.setPopUpTo(findStartDestination(navController.getGraph()).getId(), false);
}
NavOptions options = builder.build();
try {
//TODO provide proper API instead of using Exceptions as Control-Flow.
navController.navigate(item.getItemId(), null, options);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
如您所知,在 NavOptions.Builder
中,正在设置默认值 anims
。
您使用的解决方法对我来说并不令人满意。因此,我冒昧地创建了一个 BottomNavigationUI
class 来执行 NavigationUI
的功能,但在可用时使用自定义 anims
。
The difference is in the
onNavDestinationSelected
. Please note thatNavigationUI
is final, so I couldn't override it.
BottomNavigationUI.class
// don't forget your package
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.navigation.NavAction;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
import androidx.navigation.NavGraph;
import androidx.navigation.NavOptions;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.lang.ref.WeakReference;
import java.util.Set;
public class BottomNavigationUI {
private BottomNavigationUI() {
}
public static boolean onNavDestinationSelected(@NonNull MenuItem item,
@NonNull NavController navController) {
int resId = item.getItemId();
Bundle args = null;
NavOptions options;
NavOptions.Builder optionsBuilder = new NavOptions.Builder()
.setLaunchSingleTop(true)
.setEnterAnim(R.anim.nav_default_enter_anim)
.setExitAnim(R.anim.nav_default_exit_anim)
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
.setPopExitAnim(R.anim.nav_default_pop_exit_anim);
if ((item.getOrder() & Menu.CATEGORY_SECONDARY) == 0) {
optionsBuilder.setPopUpTo(findStartDestination(navController.getGraph()).getId(), false);
}
final NavAction navAction = navController.getCurrentDestination().getAction(resId);
if (navAction != null) {
NavOptions navOptions = navAction.getNavOptions();
// Note : You can Add *setLaunchSingleTop* and *setPopUpTo* from *navOptions* to *builder*
if (navOptions.getEnterAnim() != -1) {
optionsBuilder.setEnterAnim(navOptions.getEnterAnim());
}
if (navOptions.getExitAnim() != -1) {
optionsBuilder.setExitAnim(navOptions.getExitAnim());
}
if (navOptions.getPopEnterAnim() != -1) {
optionsBuilder.setPopEnterAnim(navOptions.getPopEnterAnim());
}
if (navOptions.getPopExitAnim() != -1) {
optionsBuilder.setPopExitAnim(navOptions.getPopExitAnim());
}
Bundle navActionArgs = navAction.getDefaultArguments();
if (navActionArgs != null) {
args = new Bundle();
args.putAll(navActionArgs);
}
}
options = optionsBuilder.build();
try {
//TODO provide proper API instead of using Exceptions as Control-Flow.
navController.navigate(resId, args, options);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
public static void setupWithNavController(
@NonNull final BottomNavigationView bottomNavigationView,
@NonNull final NavController navController) {
bottomNavigationView.setOnNavigationItemSelectedListener(
new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
return onNavDestinationSelected(item, navController);
}
});
final WeakReference<BottomNavigationView> weakReference =
new WeakReference<>(bottomNavigationView);
navController.addOnDestinationChangedListener(
new NavController.OnDestinationChangedListener() {
@Override
public void onDestinationChanged(@NonNull NavController controller,
@NonNull NavDestination destination, @Nullable Bundle arguments) {
BottomNavigationView view = weakReference.get();
if (view == null) {
navController.removeOnDestinationChangedListener(this);
return;
}
Menu menu = view.getMenu();
for (int h = 0, size = menu.size(); h < size; h++) {
MenuItem item = menu.getItem(h);
if (matchDestination(destination, item.getItemId())) {
item.setChecked(true);
}
}
}
});
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
static boolean matchDestination(@NonNull NavDestination destination,
@IdRes int destId) {
NavDestination currentDestination = destination;
while (currentDestination.getId() != destId && currentDestination.getParent() != null) {
currentDestination = currentDestination.getParent();
}
return currentDestination.getId() == destId;
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
static boolean matchDestinations(@NonNull NavDestination destination,
@NonNull Set<Integer> destinationIds) {
NavDestination currentDestination = destination;
do {
if (destinationIds.contains(currentDestination.getId())) {
return true;
}
currentDestination = currentDestination.getParent();
} while (currentDestination != null);
return false;
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
static NavDestination findStartDestination(@NonNull NavGraph graph) {
NavDestination startDestination = graph;
while (startDestination instanceof NavGraph) {
NavGraph parent = (NavGraph) startDestination;
startDestination = parent.findNode(parent.getStartDestination());
}
return startDestination;
}
}
现在,你需要做一些改变。
MainActivity:
之后BottomNavigationUI.setupWithNavController(bottomNavigationView, navController)
bottomNavigationView.setOnNavigationItemReselectedListener { false }
We will be using
BottomNavigationUI
instead ofNavigationUI
since it will be able to use the customanims
instead of just the default ones.
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/nav_graph"
app:startDestination="@id/home_fragment">
<fragment
android:id="@+id/home_fragment"
android:name="com.example.android.navbottomsample.HomeFragment"
android:label="HomeFragment"
tools:layout="@layout/fragment_home">
<action
app:launchSingleTop="true"
android:id="@+id/schedule_fragment"
app:destination="@id/schedule_fragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"
app:popUpTo="@+id/home_fragment" />
</fragment>
<fragment
android:id="@+id/schedule_fragment"
android:name="com.example.android.navbottomsample.ScheduleFragment"
android:label="ScheduleFragment"
tools:layout="@layout/fragment_schedule">
<action
android:id="@+id/home_fragment"
app:destination="@id/home_fragment"
app:enterAnim="@anim/slide_in_left"
app:exitAnim="@anim/slide_out_right"
app:popEnterAnim="@anim/slide_in_right"
app:popExitAnim="@anim/slide_out_left"
app:popUpTo="@+id/home_fragment" />
</fragment>
</navigation>
So you don't have to compare the code, the
action_id
is flipped now. You also have 2 actions, since we will be needing itsaction_id
. Now the whole point of theaction_id
swap is that we need theaction_id
to match thedestination_id
, also thefragment_id
andmenu_item_id
.I also changed the
app:popUpTo
. This will be most reasonable when you try it out yourself. You want both fragments topopUpTo
thehome_fragment
in thebackStack
. So, once you are athome_fragment
, you don't want to go any further back. And once you are atschedule_fragment
, you want to go back tohome_fragment
. However, I suggest you useBottomNavigationUI
orNavigationUI
as they dynamically specify thepopUpTo
(this will be most useful when you have more than 2 bottom navigation tabs).
我已经在您与我分享的项目中尝试了这个解决方案,它完美。享受 :)
其他解决方案
您可以设置自定义 setOnNavigationItemSelectedListener
以执行您需要的操作 (使用 xml 中指定的自定义动画).
现在,在您的代码 (您与我分享的代码) 中,您已经创建了一个解决方法,如下所示:
bottomNavigationView.setOnNavigationItemSelectedListener {
when (it.itemId) {
R.id.schedule_fragment -> navController.navigate(R.id.home_fragment)
else -> navController.popBackStack()
}
true
}
您可以选择保留它,或者执行以下操作(使用更新后的 nav_graph.xml
):
bottomNavigationView.setOnNavigationItemSelectedListener {
navController.navigate(it.itemId)
true
}
But, you should not forget to change the
nav_graph.xml
to the new one.
第一次尝试
好的,如果您查看 NavigationUI AndroidDocs,您会注意到以下内容:
setupWithNavController
Sets up a
BottomNavigationView
for use with aNavController
. This will callonNavDestinationSelected(MenuItem, NavController)
when a menu item is selected. The selected item in theBottomNavigationView
will automatically be updated when the destination changes.onNavDestinationSelected
Attempt to navigate to the
NavDestination
associated with the givenMenuItem
. ThisMenuItem
should have been added via one of the helper methods in this class. Importantly, it assumes themenu item id
matches a validaction id
ordestination id
to be navigated to.
The first attempt did not solve the issue. The default animations still play, instead of the ones specified in the action.