如何在主/详细窗格布局中将共享元素ImageView从ListView片段动画到详细视图片段



我的应用程序平板布局在左侧包含一个网格视图(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();

"view"变量是用户在网格视图中点击的ImageView。detail片段的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>

所有这些设置都适用于单窗格模式。当用户单击网格视图活动中的图像时,detail片段(在新活动中)取代网格视图,动画在进入和返回场景中都能完美地工作。

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

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

我决定在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);
                }
            });
}

最新更新