navegacion - transiciones material design android studio
Cómo usar transiciones de elementos compartidos en el controlador de navegación (5)
Me gustaría agregar una transición de elementos compartidos usando los componentes de la arquitectura de navegación, al navegar a otro fragmento. Pero no tengo idea de cómo. También en las documentaciones no hay nada al respecto. ¿Alguien me puede ayudar?
Así que digamos que tienes dos Fragmentos, Fragmento Segundo y Fragmento Tercero. Ambos tienen ImageView con el mismo nombre de transición, digamos: "imageView"
android:transitionName="imageView"
Solo define una acción normal entre estos fragmentos.
En FragmentSecond, vamos a añadir nuestros extras.
val extras = FragmentNavigatorExtras( binding.image to "imageView")
findNavController().navigate(R.id.action_secondFragment_to_thirdFragment , null, null , extras)
Así que estamos diciendo que queremos compartir esa ImageView, con ese nombre de transición, con ThirdFragment
Y luego en ThirdFragment:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
setHasOptionsMenu(true)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Glide.with(this).load(IMAGE_URI).into(binding.headerImage)
}
Lo único que tenemos que hacer es cargar la imagen en los dos fragmentos de la misma URL. La URL se puede pasar entre fragmentos usando un objeto de paquete y pasarla en la llamada de navegación o como un argumento de destino en el gráfico de navegación.
Si lo necesita, estoy preparando una muestra sobre la navegación y también hay SharedElementTransition:
Desde 1.0.0-alpha06, el componente de navegación admite la adición de transiciones de elementos compartidos entre destinos. Simplemente agregue FragmentNavigatorExtras a la llamada de navegación (). Más detalles: https://developer.android.com/guide/navigation/navigation-animate-transitions#shared-element
val extras = FragmentNavigatorExtras(
imageView to "header_image",
titleView to "header_title")
view.findNavController().navigate(R.id.confirmationAction,
null, // Bundle of args
null, // NavOptions
extras)
Finalmente pude hacer que esto funcionara: En el Fragmento B:
val transition = TransitionInflater.from(this.activity).inflateTransition(android.R.transition.move)
sharedElementEnterTransition = ChangeBounds().apply {
enterTransition = transition
}
Solo asegúrese de tener sus nombres de transición en sus puntos de vista y NO tiene entertTransition en el Fragmento B
FirstFragment
val extras = FragmentNavigatorExtras(
imageView to "secondTransitionName")
view.findNavController().navigate(R.id.confirmationAction,
null, // Bundle of args
null, // NavOptions
extras)
primer_fragmento.xml
<ImageView
android:id="@+id/imageView"
android:transitionName="firstTransitionName"
...
/>
SecondFragment
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
sharedElementEnterTransition = ChangeBounds().apply {
duration = 750
}
sharedElementReturnTransition= ChangeBounds().apply {
duration = 750
}
return inflater.inflate(R.layout.second_fragment, container, false)
}
second_fragment.xml
<ImageView
android:transitionName="secondTransitionName"
...
/>
Lo he probado. Se trabaja
Parece que no está (¿todavía?) Soportado. La transacción se construye realmente en androidx.navigation.fragment.FragmentNavigator
:
@Override
public void navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions) {
final Fragment frag = destination.createFragment(args);
final FragmentTransaction ft = mFragmentManager.beginTransaction();
int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
enterAnim = enterAnim != -1 ? enterAnim : 0;
exitAnim = exitAnim != -1 ? exitAnim : 0;
popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
}
ft.replace(mContainerId, frag);
final StateFragment oldState = getState();
if (oldState != null) {
ft.remove(oldState);
}
final @IdRes int destId = destination.getId();
final StateFragment newState = new StateFragment();
newState.mCurrentDestId = destId;
ft.add(newState, StateFragment.FRAGMENT_TAG);
final boolean initialNavigation = mFragmentManager.getFragments().isEmpty();
final boolean isClearTask = navOptions != null && navOptions.shouldClearTask();
// TODO Build first class singleTop behavior for fragments
final boolean isSingleTopReplacement = navOptions != null && oldState != null
&& navOptions.shouldLaunchSingleTop()
&& oldState.mCurrentDestId == destId;
if (!initialNavigation && !isClearTask && !isSingleTopReplacement) {
ft.addToBackStack(getBackStackName(destId));
} else {
ft.runOnCommit(new Runnable() {
@Override
public void run() {
dispatchOnNavigatorNavigated(destId, isSingleTopReplacement
? BACK_STACK_UNCHANGED
: BACK_STACK_DESTINATION_ADDED);
}
});
}
ft.commit();
mFragmentManager.executePendingTransactions();
}
Las animaciones están aquí (agregadas desde la navegación XML), pero en ninguna parte podemos cambiar el comportamiento de esto y llamar a addSharedElement()
en la transacción.
Sin embargo, creo que podemos hacer esto a partir de las transiciones de elementos compartidos de actividad.
Esto no se recomienda, ya que solo se realiza entre actividades, y va en contra de las recomendaciones de Google más recientes para aplicaciones de actividad única.
Creo que es posible, ya que los argumentos se pasan antes de la llamada a startActivity()
en androidx.navigation.fragment.ActivityNavigator
:
@Override
public void navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions) {
if (destination.getIntent() == null) {
throw new IllegalStateException("Destination " + destination.getId()
+ " does not have an Intent set.");
}
Intent intent = new Intent(destination.getIntent());
if (args != null) {
intent.putExtras(args);
String dataPattern = destination.getDataPattern();
if (!TextUtils.isEmpty(dataPattern)) {
// Fill in the data pattern with the args to build a valid URI
StringBuffer data = new StringBuffer();
Pattern fillInPattern = Pattern.compile("//{(.+?)//}");
Matcher matcher = fillInPattern.matcher(dataPattern);
while (matcher.find()) {
String argName = matcher.group(1);
if (args.containsKey(argName)) {
matcher.appendReplacement(data, "");
data.append(Uri.encode(args.getString(argName)));
} else {
throw new IllegalArgumentException("Could not find " + argName + " in "
+ args + " to fill data pattern " + dataPattern);
}
}
matcher.appendTail(data);
intent.setData(Uri.parse(data.toString()));
}
}
if (navOptions != null && navOptions.shouldClearTask()) {
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
}
if (navOptions != null && navOptions.shouldLaunchDocument()
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
} else if (!(mContext instanceof Activity)) {
// If we''re not launching from an Activity context we have to launch in a new task.
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
if (navOptions != null && navOptions.shouldLaunchSingleTop()) {
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
}
if (mHostActivity != null) {
final Intent hostIntent = mHostActivity.getIntent();
if (hostIntent != null) {
final int hostCurrentId = hostIntent.getIntExtra(EXTRA_NAV_CURRENT, 0);
if (hostCurrentId != 0) {
intent.putExtra(EXTRA_NAV_SOURCE, hostCurrentId);
}
}
}
final int destId = destination.getId();
intent.putExtra(EXTRA_NAV_CURRENT, destId);
NavOptions.addPopAnimationsToIntent(intent, navOptions);
mContext.startActivity(intent);
if (navOptions != null && mHostActivity != null) {
int enterAnim = navOptions.getEnterAnim();
int exitAnim = navOptions.getExitAnim();
if (enterAnim != -1 || exitAnim != -1) {
enterAnim = enterAnim != -1 ? enterAnim : 0;
exitAnim = exitAnim != -1 ? exitAnim : 0;
mHostActivity.overridePendingTransition(enterAnim, exitAnim);
}
}
// You can''t pop the back stack from the caller of a new Activity,
// so we don''t add this navigator to the controller''s back stack
dispatchOnNavigatorNavigated(destId, BACK_STACK_UNCHANGED);
}
Tendríamos que rellenar los argumentos de esta manera:
val args = Bundle()
// If there''s a shared view and the device supports it, animate the transition
if (sharedView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val transitionName = "my_transition_name"
args.putAll(ActivityOptions.makeSceneTransitionAnimation(this, sharedView, transitionName).toBundle())
}
navController.navigate(R.id.myDestination, args)
No he probado esto.