1

我的应用程序平板电脑布局在左侧包含一个网格视图 (RecyclerView),其中包含一个图像网格,右侧是一个详细信息视图,它显示当前所选网格视图图像的较小版本以及描述该图像的附加文本视图。我试图弄清楚如何提供一个共享元素动画过渡,它将任何单击的图像视图(在左侧网格视图片段中)滑动并缩放到其在详细信息视图片段中的匹配位置。我的应用程序只需要一个“进入”转换,因为我不希望每个图像选择都记录在后台堆栈上。

我尝试在我的OnItemClick(View view)处理程序中使用标准共享元素支持调用,如下所示:

DetailFragment detailFragment = detailFragment.newInstance(mFragmentId);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    movieDetailFragment.setSharedElementEnterTransition(
            TransitionInflater.from(this).inflateTransition(
                    R.transition.change_image_transform));
            view.setTransitionName(getString(
                    R.string.image_transition_name));
}

getSupportFragmentManager().beginTransaction()
        .replace(R.id.detail_container,
                 detailFragment,
                 DETAIL_FRAGMENT_TAG)
        .addSharedElement(view, getString(R.string.image_transition_name))
        .commit();

“视图”变量是用户在网格视图中单击的 ImageView。我的细节片段的onCreate()方法也包含条目

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    mImageView.setTransitionName(
            getString(R.string.image_transition_name));
}

我也在使用以下 change_image_transform.xml 转换集:

<transitionSet>
    <changeImageTransform/>
    <changeBounds/>
</transitionSet>

随着

<item name="android:windowContentTransitions">true</item>
<item name="android:windowActivityTransitions">true</item>
<item name="android:windowSharedElementEnterTransition">
    @transition/change_image_transform</item>
<item name="android:windowSharedElementExitTransition">
    @transition/change_image_transform</item>

所有这些设置都适用于单窗格模式。当用户在网格视图活动中单击图像时,详细信息片段(在新活动中)将替换网格视图,并且动画对于进入和返回场景都非常有效。

然而,在 2 窗格模式下,图像不会从网格视图中的源滑动和缩放到新创建的细节片段中的目标。我相信我遇到的问题是由于源 ImageView 存在于永久可见的网格视图片段中,因此没有包含在动画框架的转换处理中。

除了一个模糊的建议使用简单的自定义动画外,我的搜索没有找到任何明确的答案。我尝试了一个简单的 TranslateAnimation(没有缩放)作为测试,但这有两个问题:它不允许图像穿过网格视图片段的边界,并且它具有实际“移动”图像的不良副作用(离开在执行动画时,我的网格视图中有一个临时空白点)。最终,所需的效果是看到图像滑动并从源缩放到目标,而不会导致原始图像被擦除。

任何建议或意见,将不胜感激。

4

1 回答 1

1

我决定让共享元素过渡在 2 窗格模式下的两个片段之间工作的最简单方法是自己简单地制作动画。在我的解决方案中,我不会将细节片段替换为所选网格图像的细节;相反,我只是使用来自所选网格项的新数据刷新布局控件。为了防止父线性布局在目标 ImageView 暂时从详细视图中移除并放置在叠加层(用于动画)时调整大小,ImageView 父级被强制为包含子级的大小,然后在之后恢复到其原始布局宽度和高度动画。这是响应网格项单击事件执行动画的函数:

/**
 * Sets up and runs an animation that translates and scales the
 * selected poster in the grid view to the poster ImageView in the
 * detail fragment.
 * @param srcView
 * @param destView
 */
private void runAnimation(final ImageView srcView, final ImageView destView) {
    final ViewGroup destParentView = (ViewGroup)destView.getParent();

    // Set the destination image view's parent (FrameLayout) to
    // keep the width and height of image view that will be
    // temporarily re-parented during the animation. This will
    // ensure that the enclosing LinearLayout will remain fixed
    // in appearance during the animation.
    final int parentLayoutWidth = destParentView.getLayoutParams().width;
    final int parentLayoutHeight = destParentView.getLayoutParams().height;
    destParentView.getLayoutParams().width = destParentView.getMeasuredWidth();
    destParentView.getLayoutParams().height = destParentView.getMeasuredHeight();

    // Scale the destination image to the size of
    // the source image. The animation will restore
    // the destination image scale when it is run.
    // Note that the scale needs to be set before any
    // translation so that the translation takes into
    // account the change in scale.
    float scaleX = (float)destView.getMeasuredWidth()
            / (float)srcView.getMeasuredWidth();
    float scaleY = (float) destView.getMeasuredHeight()
            / (float) srcView.getMeasuredHeight();

    destView.setScaleX(1f / scaleX);
    destView.setScaleY(1f / scaleY);

    // Now set translation on scaled image so that the
    // destination image begins at the same position as
    // the source image. The destination image translation
    // will be returned to 0 during the animation.
    int[] srcLocation = {0, 0};
    int[] dstLocation = {0, 0};
    srcView.getLocationOnScreen(srcLocation);
    destView.getLocationOnScreen(dstLocation);
    destView.setTranslationX(srcLocation[0] - dstLocation[0]);
    destView.setTranslationY(srcLocation[1] - dstLocation[1]);

    // The overlay must be created from any view whose bounds
    // encompasses both the source and destination views.
    final ViewGroup rootView =
                (ViewGroup)MainActivity.this.findViewById(R.id.main_content);
    rootView.getOverlay().add(destView);

    // Run animation and when it completes, move the destination
    // view from the overlay back to it's original parent. Also,
    // restore the parent's layout params which were changed to
    // act as a placeholder with the image was being animated.
    destView.animate()
            .scaleX(1f)
            .scaleY(1f)
            .translationX(0)
            .translationY(0)
            .setInterpolator(new DecelerateInterpolator(2))
            .setDuration(500)
            .withEndAction(new Runnable() {
                @Override
                public void run() {
                    rootView.getOverlay().remove(destView);
                    destParentView.getLayoutParams().width = parentLayoutWidth;
                    destParentView.getLayoutParams().height = parentLayoutHeight;
                    destParentView.addView(destView);
                }
            });
}
于 2015-10-11T23:14:04.377 回答