web-dev-qa-db-ja.com

Navigation Controllerで共有要素遷移を使用する方法

他のフラグメントに移動するときに、ナビゲーションアーキテクチャコンポーネントを使用して共有要素の遷移を追加したいと思います。しかし、どうすればいいのかわかりません。また、ドキュメントにはそれについて何もありません。誰か助けてくれますか?

29
sativa

この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)

正常に動作しています。

2
Akash Patel

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"
    ...
 />

私はそれをテストしました。うまくいきました。

15
Xzin

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)
13
Bruno Milhan

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
}
4
luckyhandler

(まだ?)サポートされていないようです。トランザクションは実際に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.ActivityNavigatorstartActivity()の呼び出しの前に引数が渡されるため、可能だと思います。

@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)

私はこれをテストしていません。

3
Benoit Duffez

私はついにこれを機能させることができました:フラグメントB:

val transition = TransitionInflater.from(this.activity).inflateTransition(Android.R.transition.move)

sharedElementEnterTransition = ChangeBounds().apply {
            enterTransition = transition
        }

ビューにトランジション名があり、フラグメントBにentertTransitionがないことを確認してください。

2
LuchoG

したがって、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もあります。

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

2
Matteo Pasotti

最新のライブラリバージョンを使用すると、次のように記述できます。

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を指定する必要があります。

0
monkey