【问题标题】:How to use shared element transitions in Navigation Controller如何在导航控制器中使用共享元素转换
【发布时间】:2018-11-08 23:52:24
【问题描述】:

当导航到其他片段时,我想使用导航架构组件添加共享元素转换。但我不知道怎么做。同样在文档中也没有任何内容。有人可以帮我吗?

【问题讨论】:

标签: android android-architecture-components navigationcontroller


【解决方案1】:

第一个片段

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

第二个片段

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

我测试过了。成功了。

【讨论】:

  • 它适用于 Fragments A 中的元素。我想在 recyclerView(在 Fragment A 中)和 Fragment B 中的项目之间进行转换,这种方法不起作用。如何适应我的情况?
  • @AlexandrSushkov 我有同样的问题。你找到解决办法了吗?
  • @Xzin 我在代码中设置了 transitionName 但仍然无法正常工作,您能举个例子吗?
  • @Xzin 尝试为 recyclerview 中的每个图像视图设置一个唯一的过渡名称。
  • 我可以让这个在进入转换中工作,(片段 A 有一个项目的回收器视图,片段 B 有一个细节视图)但是退出转换不起作用
【解决方案2】:

我参考了这个 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.1.0-beta02
  • 我应该如何在java中使用它?
  • 退出 transton 是否也为此工作?
【解决方案3】:

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)

【讨论】:

  • 确实有 FragmentNavigatorExtras,但它们似乎对共享动画没有任何影响,尽管它们确实得到了处理,因为当源视图中没有过渡名称时我遇到了异常。
  • 这对任何人都有效吗?我的似乎什么也没做。
  • 也不适合我。请让我们知道它是否适用于任何人。
  • @ssuukk - 例外。您只需将转换名称添加到您的 XML 中。或者你可以使用 ViewCompat#setTransitionName
  • 我有这个工作,但退出转换似乎不起作用
【解决方案4】:

要从 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。

在第二个 Fragment 中,您将转换名称设置为 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
}

【讨论】:

  • 返回过渡仍然不起作用。即当点击详细页面中的向上按钮并返回recyclerView时。有什么线索可以让它发挥作用吗?
  • 这个工作的原因是因为你确保recyclerview 项目的transitionName 在该片段中是唯一的(这很重要!),只需在单击后设置它。我发现这也很有帮助:medium.com/@rajnishsuryavanshi223/…
【解决方案5】:

对于 Java

要让共享元素创建一个类似的方法:

void sharedNavigation(int id, View... views) {
        FragmentNavigator.Extras.Builder extras = new FragmentNavigator.Extras.Builder();
        for (View view : views)
            extras.addSharedElement(view, view.getTransitionName());
        FragmentNavigator.Extras build = extras.build();
        Navigation.findNavController(getView()).navigate(id,
                null,
                null,
                build);
    }

在目标类或基类中,您必须在 onCreate() 中添加以下代码。

@Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setSharedElementEnterTransition(TransitionInflater.from(getContext())
                .inflateTransition(android.R.transition.move));
    }

为了制作过渡动画,将 id 和视图提供给 sharedNavigation(),如下所示:

sharedNavigation(R.id.action_splashFragment_to_loginFragment,
                        getView().findViewById(R.id.logo));

【讨论】:

    【解决方案6】:

    假设您有两个 Fragment,FragmentSecond 和 FragmentThird。两者都有具有相同转换名称的 ImageView,比如说:“imageView”

    android:transitionName="imageView"
    

    只需在这些片段之间定义一个正常的动作。

    在 FragmentSecond 中,让我们添加我们的附加功能

    val extras = FragmentNavigatorExtras( binding.image to "imageView")
    
    findNavController().navigate(R.id.action_secondFragment_to_thirdFragment , null, null , extras)
    

    所以我们说我们想与 ThirdFragment 共享 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 加载两个片段中的图像。 URL 可以使用 Bundle 对象在片段之间传递,并在导航调用中传递或作为导航图中的目标参数传递。

    如果你需要它,我正在准备一个关于 Navigation 的示例,还有 SharedElementTransition:

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

    【讨论】:

      【解决方案7】:

      似乎(还没有?)支持。事务其实是建在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()


      但是,我相信我们可以通过活动共享元素转换来做到这一点。

      不建议这样做,因为它只在活动之间进行,这违背了最新的谷歌建议,以配合单活动应用程序。

      我认为这是可能的,因为参数是在调用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)
      

      我还没有测试过。

      【讨论】:

        【解决方案8】:

        我终于可以让它工作了: 在片段 B 上:

        val transition = TransitionInflater.from(this.activity).inflateTransition(android.R.transition.move)
        
        sharedElementEnterTransition = ChangeBounds().apply {
                    enterTransition = transition
                }
        

        只需确保您的视图中的转换名称正确,并且片段 B 上没有 entertTransition

        【讨论】:

          【解决方案9】:

          使用最新的库版本,您只需编写以下内容:

          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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2022-06-25
            • 1970-01-01
            • 2023-03-26
            • 2023-04-03
            • 2017-11-02
            • 2010-10-09
            • 2017-05-15
            相关资源
            最近更新 更多