他のフラグメントに移動するときに、ナビゲーションアーキテクチャコンポーネントを使用して共有要素の遷移を追加したいと思います。しかし、どうすればいいのかわかりません。また、ドキュメントにはそれについて何もありません。誰か助けてくれますか?
この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)
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に渡します。
2番目のフラグメントでは、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
}
(まだ?)サポートされていないようです。トランザクションは実際に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の推奨事項に反します。
androidx.navigation.fragment.ActivityNavigator
のstartActivity()
の呼び出しの前に引数が渡されるため、可能だと思います。
@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)
私はこれをテストしていません。
私はついにこれを機能させることができました:フラグメントB:
val transition = TransitionInflater.from(this.activity).inflateTransition(Android.R.transition.move)
sharedElementEnterTransition = ChangeBounds().apply {
enterTransition = transition
}
ビューにトランジション名があり、フラグメントBにentertTransitionがないことを確認してください。
したがって、2つのフラグメント、FragmentSecondとFragmentThirdがあるとします。両方とも同じtransitionNameのImageViewがあります。たとえば、「imageView」
Android:transitionName="imageView"
これらのフラグメント間の通常のアクションを定義するだけです。
FragmentSecondでは、エクストラを追加しましょう
val extras = FragmentNavigatorExtras( binding.image to "imageView")
findNavController().navigate(R.id.action_secondFragment_to_thirdFragment , null, null , extras)
そのため、そのImageViewを、transitionName、ThirdFragmentと共有したいと言っています。
そして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から2つのフラグメントに画像を読み込むことだけです。 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()
)
移行を機能させるには、Xzinが答えで説明したように、宛先のFragments onCreateViewメソッドでsharedElementEnterTransition
および/またはsharedElementReturnTransition
を指定する必要があります。