Android共享元素过渡ToolBar重叠。

11

我已经为我的应用实现了共享元素转换,其中转换始于主屏幕上ViewPager中的带有RecyclerView的Fragment中的图像,并扩展为全屏幻灯片浏览器视图,再次在ViewPager中的Fragment中。这一切都正常工作,除了如果图像没有完全可见,它会在扩展到全屏之前覆盖TabBar。以下是发生的情况:

输入图像描述

我的过渡效果如下:

<?xml version="1.0" encoding="utf-8"?>
<fade xmlns:android="http://schemas.android.com/apk/res/android">
    <targets>
        <target android:excludeId="@android:id/statusBarBackground"/>
        <target android:excludeId="@android:id/navigationBarBackground"/>
    </targets>
</fade>

退出:

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="together"
android:duration="500">
    <fade>
        <targets>
            <target android:excludeId="@android:id/statusBarBackground" />
            <target android:excludeId="@android:id/navigationBarBackground" />
        </targets>
    </fade>
</transitionSet>

在调用活动的共享元素回调中,我有这个:

View navigationBar = activity.findViewById(android.R.id.navigationBarBackground);
View statusBar = activity.findViewById(android.R.id.statusBarBackground);
if (navigationBar != null) {
    names.add(navigationBar.getTransitionName());
    sharedElements.put(navigationBar.getTransitionName(), navigationBar);
}
if (statusBar != null) {
    names.add(statusBar.getTransitionName());
    sharedElements.put(statusBar.getTransitionName(), statusBar);
}

最后,在 styles.xml 中为活动主题进行以下设置:

<item name="android:windowContentTransitions">true</item>
<item name="android:windowEnterTransition">@transition/details_window_enter_transition</item>
<item name="android:windowReturnTransition">@transition/details_window_return_transition</item>

我并不完全理解如何在转场时排除工具栏 (或操作栏) 而不出现重叠。也许一种方法是以某种方式强制将图像裁剪在顶部,这样当在工具栏下面时它不会变得完全可见,并且仅从可见矩形扩展。

我尝试将 <target android:excludeId="@id/action_bar_container"/> 添加到动画的目标中,但仍然发生相同的事情。

欢迎任何建议。

4个回答

9

我在我的项目中遇到了类似的问题。请在你的样式中添加以下代码。

<item name="android:windowSharedElementsUseOverlay">false</item>

对我来说可行。


1
这将减少共享元素视图的 alpha 值,这是有意义的,但在我的情况下看起来不好。然而,这是一个有效的解决方案,所以我会标记为正确。 - Georgi
1
如果有人不想禁用windowSharedElementsUseOverlay,那么你可以使用我的解决方案。https://dev59.com/SlcP5IYBdhLWcg3wYpDi#64183340 - Abhinav Chauhan

2

我想到了一个临时解决方案。在执行共享元素转换之前,调用活动会检查目标视图是否在RecyclerView的范围内。如果不是,则RecyclerView将平滑滚动以显示完整视图,并在完成后运行转换。如果视图完全可见,则转换正常运行。

// Scroll to view and run animation when scrolling completes.
recycler.smoothScrollToPosition(adapterPosition);
recycler.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {

    @Override
    public boolean onPreDraw() {
        recycler.getViewTreeObserver().removeOnPreDrawListener(this);
        // Open activity here.
        return true;
    }
});

这是结果,还不错,直到我找到更好的解决方案为止:

在此输入图片描述


我快疯了,我也遇到了同样的问题!你是怎么实现这个滚动的? - user11230
@user11230通过一个示例编辑了我的答案,展示如何滚动并等待滚动完成。 - Georgi
非常感谢,但它不起作用。如果您能看一下,我将万分感激。我从五月份开始尝试修复这个问题。https://pastebin.com/uyEpfjvM http://i.imgur.com/cj3kexs.mp4 - user11230

1
我想我对这个问题有正确的答案。这个问题发生在第二个活动中没有工具栏,所以它不能被添加到共享元素的过渡中。因此,在我的情况下,我在第二个活动中添加了一些“假工具栏”,其高度为0dp。然后,您可以将第一个活动中的工具栏作为共享元素添加,并为其提供变换边界动画,这样工具栏将同时折叠,图像也不再“悬浮”在工具栏上。
我的“假工具栏”视图:
    <View
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:background="@color/colorPrimary"
    android:elevation="10dp"
    android:outlineProvider="none"
    android:transitionName="toolbar_transition" />

重要提示:

- 视图必须具有非透明背景

- 我添加了高程以确保我的视图在图像上方

- 高程会导致阴影,这不是我想要的,因此我将 outilenProvider 设置为 none

接下来,您只需将工具栏添加到共享元素即可。

    sharedElements.add(new Pair<>(toolbar, "toolbar_transition"));

1

我到处搜索,但找不到任何解决方案,所以我自己想出了一个解决方法。这里有一个录制的演示(速度减半),展示了结果(有和没有修复的对比)。

With fix    Without fix

请在此查看完整的演示: https://github.com/me-abhinav/shared-element-overlap-demo

步骤

假设我们有两个活动,即MainActivity,它具有网格/缩略图列表的滚动容器,以及SecondActivity,它在全屏幻灯片中显示图像。

请查看完整代码以完全了解解决方案。

  1. 在托管滚动容器的MainActivity内,在缩略图上设置单击侦听器以打开SecondActivity
ImageView imageView = findViewById(R.id.image_view);
imageView.setOnClickListener(v -> {
    // Set the transition name. We could also do it in the xml layout but this is to demo
    // that we can choose any name generated dynamically.
    String transitionName = getString(R.string.transition_name);
    imageView.setTransitionName(transitionName);

    // This part is important. We first need to clip this view to only its visible part.
    // We will also clip the corresponding view in the SecondActivity using shared element
    // callbacks.
    Rect localVisibleRect = new Rect();
    imageView.getLocalVisibleRect(localVisibleRect);
    imageView.setClipBounds(localVisibleRect);
    mClippedView = imageView;

    Intent intent = new Intent(MainActivity.this, SecondActivity.class);
    intent.putExtra(SecondActivity.EXTRA_TRANSITION_NAME, transitionName);
    intent.putExtra(SecondActivity.EXTRA_CLIP_RECT, localVisibleRect);
    ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(
                    MainActivity.this,
                    Pair.create(imageView, transitionName));
    startActivity(intent, options.toBundle());
});
  1. 在您的MainActivityonResume()中恢复剪辑。
@Override
protected void onResume() {
    super.onResume();

    // This is also important. When we come back to this activity, we need to reset the clip.
    if (mClippedView != null) {
        mClippedView.setClipBounds(null);
    }
}

在你的res文件夹中创建一个过渡资源,例如: app/src/main/res/transition/shared_element_transition.xml 其内容应该类似于此:
<?xml version="1.0" encoding="utf-8"?>
<transitionSet
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="375"
    android:interpolator="@android:interpolator/fast_out_slow_in"
    android:transitionOrdering="together">

    <!-- This is needed to clip the invisible part of the view being transitioned. Otherwise we
         will see weird transitions when the image is partially hidden behind appbar or any other
         view. -->
    <changeClipBounds/>

    <changeTransform/>
    <changeBounds/>

</transitionSet>
  1. 在你的SecondActivity中设置过渡效果。
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);

    // Setup transition
    Transition transition =
            TransitionInflater.from(this)
                    .inflateTransition(R.transition.shared_element_transition);
    getWindow().setSharedElementEnterTransition(transition);

    // Postpone the transition. We will start it when the slideshow is ready.
    ActivityCompat.postponeEnterTransition(this);

    // more code ... 
    // See next step below.
}

现在我们还需要在SecondActivity中剪裁共享视图。
// Setup the clips
String transitionName = getIntent().getStringExtra(EXTRA_TRANSITION_NAME);
Rect clipRect = getIntent().getParcelableExtra(EXTRA_CLIP_RECT);
setEnterSharedElementCallback(new SharedElementCallback() {
    @Override
    public void onSharedElementStart(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
        for (int i = 0; i < sharedElementNames.size(); i++) {
            if (Objects.equals(transitionName, sharedElementNames.get(i))) {
                View view = sharedElements.get(i);
                view.setClipBounds(clipRect);
            }
        }
        super.onSharedElementStart(sharedElementNames, sharedElements, sharedElementSnapshots);
    }

    @Override
    public void onSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements, List<View> sharedElementSnapshots) {
        for (int i = 0; i < sharedElementNames.size(); i++) {
            if (Objects.equals(transitionName, sharedElementNames.get(i))) {
                View view = sharedElements.get(i);
                view.setClipBounds(null);
            }
        }
        super.onSharedElementEnd(sharedElementNames, sharedElements, sharedElementSnapshots);
    }
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接