transiciones studio navegacion imagenes fragments entre diseño animaciones activity activities android android-architecture-components navigationcontroller

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:

https://github.com/matteopasotti/navigation-sample


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.