【问题标题】:How to start shared element transition using Fragments?如何使用 Fragments 开始共享元素转换?
【发布时间】:2023-04-03 10:14:01
【问题描述】:

我正在尝试在具有“共享元素”的片段之间实现转换,如新材料设计规范中所述。 我能找到的唯一方法是ActivityOptionsCompat.makeSceneTransitionAnimation,我相信它只适用于活动。 我一直在寻找同样的功能,但带有/用于片段。

【问题讨论】:

  • 你检查过 FragmentTransaction.addSharedElement -method ; developer.android.com/reference/android/support/v4/app/… ?
  • 我实际上尝试过使用它,但它似乎不起作用,至少从列表视图项中的图像视图来看。可能有一堆未记录的限制。不过,禁用事务的过渡和动画似乎没有帮助。
  • 我也无法让它与列表项中的 ImageViews 一起使用。我能够将一个非常简单的 Activity 与 2 个全屏片段组合在一起。每个片段都有 2 个不同大小和位置的黑色背景视图,当我点击屏幕时,它会切换片段。在这种情况下,共享元素确实按预期设置了动画。所以它确实有效,只是当您的视图位于列表项中时可能不会。我想知道是不是因为列表项直到运行时才知道?
  • 我现在可以确认将列表项布局内的视图转换为新片段中的视图不起作用。如果我在我的第一个片段布局中放置一个视图,在列表视图之外,它确实有效。
  • @broccoli 我找到了 listview\recyclerview 的解决方案。您需要为每个项目提供唯一的过渡名称。阅读更多:androidauthority.com/…

标签: android fragment material-design


【解决方案1】:

我遇到了同样的问题,但是通过从另一个片段添加新片段来解决问题。 以下链接对开始使用非常有帮助:https://developer.android.com/training/material/animations.html#Transitions

以下是我的有效代码。我正在为ImageView 从一个片段制作动画到另一个片段。 确保您要制作动画的View 在两个片段中具有相同的android:transitionName。 其他内容并不重要。

作为测试,您可以将其复制到您的两个布局 xml 文件中。确保图像存在。

<ImageView
android:transitionName="MyTransition"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/test_image" />

然后我的 res/transition 文件夹中有 1 个文件,名为 change_image_transform.xml

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <changeImageTransform />
</transitionSet>

现在您可以开始了。假设您有包含图像的片段 A,并且想要添加片段 B。

在片段 A 中运行:

@Override
public void onClick(View v) {
    switch(v.getId()) {
        case R.id.product_detail_image_click_area:
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                setSharedElementReturnTransition(TransitionInflater.from(getActivity()).inflateTransition(R.transition.change_image_transform));
                setExitTransition(TransitionInflater.from(getActivity()).inflateTransition(android.R.transition.explode));

                // Create new fragment to add (Fragment B)
                Fragment fragment = new ImageFragment();
                fragment.setSharedElementEnterTransition(TransitionInflater.from(getActivity()).inflateTransition(R.transition.change_image_transform));
                fragment.setEnterTransition(TransitionInflater.from(getActivity()).inflateTransition(android.R.transition.explode));

                // Our shared element (in Fragment A)
                mProductImage   = (ImageView) mLayout.findViewById(R.id.product_detail_image);

                // Add Fragment B
                FragmentTransaction ft = getFragmentManager().beginTransaction()
                        .replace(R.id.container, fragment)
                        .addToBackStack("transaction")
                        .addSharedElement(mProductImage, "MyTransition");
                ft.commit();
            }
            else {
                // Code to run on older devices
            }
            break;
    }
}

【讨论】:

  • 这对我有用,只是它总是在第二个片段的屏幕顶部开始动画。因此,如果我的列表视图(或者在我的情况下为 RecyclerView)的每个元素中都有一个视图,并且您点击列表底部附近的一个,并且第二个片段中该视图的新位置位于屏幕底部,它实际上是从顶部到底部进行动画处理,而不是从视图开始的第一个片段中屏幕上的位置进行动画处理。有谁知道这是为什么?
  • 我认为这是因为列表视图重复包含相同的元素。如果执行 findViewById,第一项将返回。我认为您应该通过在单击项目时动态分配 transitionName 来唯一标识动画视图。
  • 我正在为我的适配器中的每个实例分配唯一的 ID。我正在附加项目位置。话虽如此,我解决了我的问题。事实证明,您需要将 ChangeTransform 包含到您的 TransitionSet 中。这告诉系统保存您刚刚选择的视图的开始位置,并将其用作新片段中动画的开始位置。
  • @brockoli 您能否详细说明“事实证明您需要将 ChangeTransform 包含到您的 TransitionSet 中。” ?你是怎么做到的?
  • @stoefln 查看此链接androidauthority.com/…
【解决方案2】:

共享元素片段过渡确实可以与 ListView 一起使用,只要源视图和目标视图具有相同(且唯一)的过渡名称。

如果您让列表视图适配器为您想要的视图设置唯一的 transitionNames(例如一些常量 + 特定项目 id)并且还更改您的详细片段以将相同的 transitionNames 设置为目标视图在运行时(onCreateView),转换确实有效!

【讨论】:

  • 这为我解决了问题。我现在可以在同一个 Activity 中将共享的 ImageView 从一个片段动画化到另一个片段。现在我的问题是我的 ImageViews 没有转换到它们的新位置,它们只是出现在它们的新位置但运行放大动画。我正在使用 setSharedElementEnterTransition(new ChangeBounds());当我创建我的片段实例时。 ChangeImageTransform() 的类似行为
  • 是否有可能得到一段工作代码来测试这个?我已经尝试了一段时间,但没有任何效果。
  • 对我来说,即使每个图像视图共享相同的转换名称,它也有点工作。如果图像仍在内存中并且仅在第一次出现动画之后,它确实可以正常工作。问题是,当我使用下载的图像时,您可以看到图像视图在加载正确的图像之前先交换为备用图像。仍然,我必须在片段 B 中强制加载相同的图像,将图像 url 作为来自片段 A 的参数传递。
  • @Dimitris 可以帮我解决这个问题stackoverflow.com/q/59431465/4291272
【解决方案3】:

共享元素确实适用于 Fragments,但有一些事项需要牢记:

  1. 不要尝试在 Fragment 的 onCreateView 中设置 sharedElementsTransition。您必须在创建 Fragment 实例时或在 onCreate 中定义它们。

  2. 请注意有关进入/退出过渡和 sharedElementTransition 的可能动画的官方文档。它们不一样。

  3. 试错:)

【讨论】:

【解决方案4】:

这应该是对已接受答案的评论,因为我无法对此发表评论。

接受的答案(由 WindsurferOak 和 ar34z 提供)有效,但在使用 backStack 向上导航时导致空指针异常的“小”问题除外。似乎应该在目标片段而不是原始片段上调用setSharedElementReturnTransition()

所以而不是:

setSharedElementReturnTransition(TransitionInflater.from(getActivity()).inflateTransition(R.transition.change_image_transform));

应该是

fragment.setSharedElementReturnTransition(TransitionInflater.from(getActivity()).inflateTransition(R.transition.change_image_transform));

https://github.com/tevjef/Rutgers-Course-Tracker/issues/8

【讨论】:

【解决方案5】:
【解决方案6】:

关键是使用自定义事务

transaction.addSharedElement(sharedElement, "sharedImage");

两个片段之间的共享元素过渡

在本例中,应将两个不同的ImageViews 之一从ChooserFragment 转换为DetailFragment

ChooserFragment 布局中,我们需要唯一的transitionName 属性:

<ImageView
    android:id="@+id/image_first"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/ic_first"
    android:transitionName="fistImage" />

<ImageView
    android:id="@+id/image_second"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/ic_second"
    android:transitionName="secondImage" />

ChooserFragments 类中,我们需要将被点击的View 和一个ID 传递给处理片段替换的父Activity(我们需要ID 来知道要显示哪个图像资源在DetailFragment)。如何将信息传递给父活动的详细信息肯定会在另一个文档中介绍。

view.findViewById(R.id.image_first).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        if (mCallback != null) {
            mCallback.showDetailFragment(view, 1);
        }
    }
});

view.findViewById(R.id.image_second).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        if (mCallback != null) {
            mCallback.showDetailFragment(view, 2);
        }
     }
});

DetailFragment中,共享元素的ImageView也需要唯一的transitionName属性。

<ImageView
    android:id="@+id/image_shared"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:transitionName="sharedImage" />

DetailFragmentonCreateView() 方法中,我们必须决定应该显示哪个图像资源(如果我们不这样做,共享元素将在过渡后消失)。

public static DetailFragment newInstance(Bundle args) {
    DetailFragment fragment = new DetailFragment();
    fragment.setArguments(args);
    return fragment;
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);
    View view = inflater.inflate(R.layout.fragment_detail, container, false);

    ImageView sharedImage = (ImageView) view.findViewById(R.id.image_shared);

    // Check which resource should be shown.
    int type = getArguments().getInt("type");

    // Show image based on the type.
    switch (type) {
        case 1:
            sharedImage.setBackgroundResource(R.drawable.ic_first);
            break;

        case 2:
            sharedImage.setBackgroundResource(R.drawable.ic_second);
            break;
    }

    return view;
}

Activity 正在接收回调并处理片段的替换。

@Override
public void showDetailFragment(View sharedElement, int type) {
    // Get the chooser fragment, which is shown in the moment.
    Fragment chooserFragment = getFragmentManager().findFragmentById(R.id.fragment_container);

    // Set up the DetailFragment and put the type as argument.
    Bundle args = new Bundle();
    args.putInt("type", type);
    Fragment fragment = DetailFragment.newInstance(args);

    // Set up the transaction.
    FragmentTransaction transaction = getFragmentManager().beginTransaction();

    // Define the shared element transition.
    fragment.setSharedElementEnterTransition(new DetailsTransition());
    fragment.setSharedElementReturnTransition(new DetailsTransition());

    // The rest of the views are just fading in/out.
    fragment.setEnterTransition(new Fade());
    chooserFragment.setExitTransition(new Fade());

    // Now use the image's view and the target transitionName to define the shared element.
    transaction.addSharedElement(sharedElement, "sharedImage");

    // Replace the fragment.
    transaction.replace(R.id.fragment_container, fragment, fragment.getClass().getSimpleName());

    // Enable back navigation with shared element transitions.
    transaction.addToBackStack(fragment.getClass().getSimpleName());

    // Finally press play.
    transaction.commit();
}

不要忘记 - Transition 本身。此示例移动和缩放共享元素。

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class DetailsTransition extends TransitionSet {

    public DetailsTransition() {
        setOrdering(ORDERING_TOGETHER);
        addTransition(new ChangeBounds()).
            addTransition(new ChangeTransform()).
            addTransition(new ChangeImageTransform());
    }

}

【讨论】:

【解决方案7】:

我在片段中搜索了 SharedElement,我在 GitHub 上找到了非常有用的源代码。

1.首先你应该在两个片段布局中为你的对象(像ImageView)定义transitionName(我们在片段A中添加一个按钮来处理点击事件):

片段A

  <ImageView
    android:id="@+id/fragment_a_imageView"
    android:layout_width="128dp"
    android:layout_height="96dp"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true"
    android:layout_marginBottom="80dp"
    android:scaleType="centerCrop"
    android:src="@drawable/gorilla"
    android:transitionName="@string/simple_fragment_transition />

<Button
    android:id="@+id/fragment_a_btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true"
    android:layout_marginBottom="24dp"
    android:text="@string/gorilla" />

片段 B:

    <ImageView
    android:id="@+id/fragment_b_image"
    android:layout_width="match_parent"
    android:layout_height="250dp"
    android:scaleType="centerCrop"
    android:src="@drawable/gorilla"
    android:transitionName="@string/simple_fragment_transition" />
  1. 那么你应该在你的transition文件中写下这段代码transition Directory(如果你没有这个Directory那么创建一个:res > new > Android Resource Directory > Resource Type = transition > name = change_image_transform):

change_image_transform.xml:

 <?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
  <changeBounds/>
  <changeTransform/>
  <changeClipBounds/>
  <changeImageTransform/>
</transitionSet>
  1. 在最后一步你应该用java完成代码:

片段 A:

public class FragmentA extends Fragment {

    public static final String TAG = FragmentA.class.getSimpleName();


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_a, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        final ImageView imageView = (ImageView) view.findViewById(R.id.fragment_a_imageView);
        Button button = (Button) view.findViewById(R.id.fragment_a_btn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                getFragmentManager()
                        .beginTransaction()
                        .addSharedElement(imageView, ViewCompat.getTransitionName(imageView))
                        .addToBackStack(TAG)
                        .replace(R.id.content, new FragmentB())
                        .commit();
            }
        });
    }
}

片段 B:

public class FragmentB extends Fragment {

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

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_b, container, false);
    }
}

别忘了在你的活动中显示你的“A”片段:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.content, new SimpleFragmentA())
                .commit();
    }

来源:https://github.com/mikescamell/shared-element-transitions

【讨论】:

【解决方案8】:

如何使用 Fragments 启动共享元素过渡?

我假设您想使用 Fragment(而不是 Activity)转换您的图像

  • 如果你已经设置了 AppTheme 将无法正常工作

  • 保持源和目标的转换名称相同

你必须为过渡做三件事:

1.在调用makeFragmentTransition之前将transitionName设置为源视图(xml或程序)->

private void setImageZoom(boolean isImageZoom) {
    ImageView imageView = this.findViewById(R.id.image);

    if (isImageZoom) {
        imageView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                ViewCompat.setTransitionName(imageView, "imageTransition");
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    makeFragmentTransition(imageView);
            }
            }
        });
    }
}

2.片段过渡

  • 为特定的Transition动画设置TransitionSet

  • 将它们应用于片段

  • 在 fragmentTransition 时调用 addSharedElement(View, transitionName)

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 公共无效makeFragmentTransition(ImageView sourceTransitionView){ //源视图的转换名称
    //必须在调用此方法之前设置transitionName(以编程方式或将->transitionName赋予xml中的视图) 字符串 sourceTransitionName = ViewCompat.getTransitionName(sourceTransitionView); 过渡集过渡集 = 新过渡集(); transitionSet.setDuration(500); transitionSet.addTransition(new ChangeBounds()); //扩大边界 transitionSet.addTransition(new ChangeTransform()); //垂直转换 transitionSet.addTransition(new ChangeImageTransform()); // 图像变换工作 transitionSet.setOrdering(TransitionSet.ORDERING_TOGETHER);

      ImageTransitionFragment fragment = new ImageTransitionFragment();
      fragment.setSharedElementEnterTransition(transitionSet);
      fragment.setSharedElementReturnTransition(transitionSet);
      fragment.setAllowReturnTransitionOverlap(false);
    
      try {
    
          getHostActivity().getSupportFragmentManager()
                  .beginTransaction()
                  //sharedElement is set here for fragment 
                  //it will throw exception if transitionName is not same for source and destionationView
                  .addSharedElement(sourceTransitionView, sourceTransitionName)
                  //R.id.fragmentView is the View in activity on which fragment will load...
                  .replace(R.id.fragmentView, fragment)
                  .addToBackStack(null)
                  .commit();
      } catch (Exception e) {
          //
          String string = e.toString();
      }
    

    }

3.在ImageView中设置destinationNation transitionName

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/destionationTransitionPage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:transitionName="@string/pageTransition"
android:background="@color/black_color">


<com.android.foundation.ui.component.FNImageView
    android:id="@+id/destinationImageView"
    android:layout_width="@dimen/_400dp"
    android:layout_gravity="center"

    android:transitionName="imageTransition"
    android:layout_height="@dimen/_400dp" />
</FrameLayout>

如有不清楚或需要改进的地方请回复

【讨论】:

  • 我正面临过渡问题,它不适用于我的项目,相同的代码在核心项目中工作。
  • 感谢您提供此代码 sn-p,它可能会提供一些有限的短期帮助。一个正确的解释would greatly improve 其长期价值,通过展示为什么这是解决问题的好方法,并将使其对未来有其他类似问题的读者更有用。请edit您的回答添加一些解释,包括您所做的假设。
  • 谢谢托比,你的反馈真的很棒,我会尽快改进我的答案,因为我已经解决了这个问题。
猜你喜欢
  • 2015-04-26
  • 2017-11-02
  • 2017-05-15
  • 1970-01-01
  • 1970-01-01
  • 2015-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多