我想在导航到其他片段时使用导航架构组件添加共享元素转换。但我不知道怎么做。在文件中也没有任何关于它的内容。有人能帮我吗?
我参考了这个github样本https://github.com/serbelga/android_navigation_shared_elements
cardView.setOnClickListener{
val extras = FragmentNavigatorExtras(
imageView to "imageView"
)
findNavController().navigate(R.id.detailAction, null, null, extras)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
它运作正常。
FirstFragment
val extras = FragmentNavigatorExtras(
imageView to "secondTransitionName")
view.findNavController().navigate(R.id.confirmationAction,
null, // Bundle of args
null, // NavOptions
extras)
first_fragment.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"
...
/>
我测试了它。这是有效的。
从1.0.0-alpha06开始,导航组件支持在目标之间添加共享元素转换。只需将FragmentNavigatorExtras添加到navigate()调用即可。更多细节: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)
它似乎不支持(但是?)。该交易实际上是在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();
}
动画在这里(从XML导航中添加),但我们无处可改变它的行为,并在事务上调用addSharedElement()
。
但是,我相信我们可以从活动共享元素转换中执行此操作。
建议不要这样做,因为它只是在活动之间,这与单一活动应用程序的最新Google建议相悖。
我认为这是有可能的,因为在startActivity()
调用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);
}
我们需要像这样填充参数:
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)
我没有测试过这个。
为了使这个工作从recyclerView的ImageView设置,所有设置如下:
val adapter = PostAdapter() { transitionView, post ->
findNavController().navigate(
R.id.action_postsFragment_to_postsDetailFragment,
null,
null,
FragmentNavigatorExtras(transitionView to getString(R.string.transition_image)))
}
在适配器中,这可以解决问题:
itemView.setOnClickListener {
ViewCompat.setTransitionName(imageView, itemView.context.getString(R.string.transition_image))
onClickedAction?.invoke(imageView, post)
}
您不必在适配器的项目的xml中指定转换名称,只需在单击项目后立即从代码中设置它。
onClickedAction看起来像:
private val onClickedAction: ((transitionView: View, post: Post) -> Unit)?
然后将它传递给ViewHolder。
在第二个片段中,您将转换名称设置为xml中的ImageView:
android:transitionName="@string/transition_image"
并指定转换
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val transition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
sharedElementEnterTransition = transition
sharedElementReturnTransition = transition
}
我终于能够让它工作了:在片段B上:
val transition = TransitionInflater.from(this.activity).inflateTransition(android.R.transition.move)
sharedElementEnterTransition = ChangeBounds().apply {
enterTransition = transition
}
只需确保您的视图中有您的过渡名称,并且您在片段B上没有任何entertTransition
所以我们假设您有两个片段,FragmentSecond和FragmentThird。两者都有ImageView具有相同的transitionName,让我们说:“imageView”
android:transitionName="imageView"
只需定义这些片段之间的正常操作即可。
在FragmentSecond中,让我们添加我们的额外内容
val extras = FragmentNavigatorExtras( binding.image to "imageView")
findNavController().navigate(R.id.action_secondFragment_to_thirdFragment , null, null , extras)
所以我们说我们希望与ThirdFragment共享具有该transitionName的ImageView
然后在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)
}
我们唯一要做的就是将图像加载到同一URL中的两个片段中。可以使用Bundle Object在片段之间传递URL,并在导航调用中将其传递或在导航图中作为目标参数传递。
如果您需要它,我正在准备一个关于导航的示例,并且还有SharedElementTransition:
使用最新的库版本,您只需编写以下内容:
view.findNavController().navigate(
R.id.action_firstFragment_to_secondFragment,
null,
null,
FragmentNavigator.Extras.Builder().addSharedElements(
mapOf(
firstSharedElementView to "firstSharedElementName",
secondSharedElementView to "secondSharedElementName"
)
).build()
)
为了过渡到工作,您还必须在目标片段onCreateView方法中指定sharedElementEnterTransition
和/或sharedElementReturnTransition
,就像Xzin在他的回答中所解释的那样。