【问题标题】:Scene transition with nested shared element具有嵌套共享元素的场景过渡
【发布时间】:2016-05-29 17:32:14
【问题描述】:

我正在尝试使用 Transitions API 为两个 ViewGroup 之间的共享元素设置动画。目标是绿色视图“超出其父级边界”向新位置移动。

我有以下布局:

first.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <FrameLayout
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:background="#f00" />

  <FrameLayout
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:layout_alignParentBottom="true"
    android:layout_alignParentRight="true"
    android:background="#00f">

    <View
      android:id="@+id/myview"
      android:layout_width="100dp"
      android:layout_height="100dp"
      android:layout_gravity="center"
      android:background="#0f0" />

  </FrameLayout>
</RelativeLayout>

second.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <FrameLayout
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:background="#f00">

    <View
      android:id="@+id/myview"
      android:layout_width="100dp"
      android:layout_height="100dp"
      android:layout_gravity="center"
      android:background="#0f0" />

  </FrameLayout>

  <FrameLayout
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:layout_alignParentBottom="true"
    android:layout_alignParentRight="true"
    android:background="#00f" />

</RelativeLayout>

但是,我无法让它正常工作。默认过渡只是淡入淡出,ChangeBounds 过渡什么也不做,ChangeTransform 看起来也不正确。

我正在使用的代码:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);

    setContentView(R.layout.first);
    View myView1 = findViewById(R.id.myview);
    myView1.setTransitionName("MYVIEW");

    new Handler().postDelayed(new Runnable() {
      @Override
      public void run() {
        View second = LayoutInflater.from(MainActivity.this).inflate(R.layout.second, root, false);
        View myView2 = second.findViewById(R.id.myview);
        myView2.setTransitionName("MYVIEW");

        Scene scene = new Scene(root, second);
        Transition transition = new ChangeTransform();
        transition.addTarget("MYVIEW");
        transition.setDuration(3000);

        TransitionManager.go(scene, transition);
      }
    }, 2000);
  }

现在我可以通过使用ViewOverlay 自己创建动画来手动执行此操作。但是,我正在寻找使用 Transitions API 的解决方案。这甚至可能吗?

另外,我并不是要“扁平化”层次结构。我有意嵌套视图以解决更复杂的用例。

【问题讨论】:

    标签: android android-animation android-transitions


    【解决方案1】:

    是的,可以使用 Android 转换 API 重新设置父级。 重设视图时需要考虑一个主要限制:

    通常,每个子视图只允许在其父视图范围内绘制。这将导致视图在到达其初始父级的边框时消失,并在片刻后重新出现在新父级中。

    通过在所有相关父级上设置android:clipChildren="false",在相关场景的视图层次结构中一直关闭子级剪辑。

    在更复杂的层次结构中,例如支持适配器的视图,通过TransitionListener 动态切换子剪辑的性能更高。

    您还必须使用 ChangeTransform 来重新设置视图的父级,而不是 ChangeBounds 或将其添加到 TransitionSet

    在这个级别的转换中不需要转换名称或目标,因为框架会自己解决。如果事情变得更复杂,你会想要有一个

    • 匹配资源 ID 或
    • 匹配的转换名称

    对于参与转换的视图。

    【讨论】:

    • 啊,谢谢,clipChildren 在这种情况下可以解决问题。为什么它不使用覆盖?来自ChangeTransform.setReparentWithOverlay 文档:设置对父级的更改是否应使用覆盖。当父项更改不使用覆盖时,它会影响子项的变换。 *默认值为真*。
    • 在进行转换之前和之后查看 Android 设备监视器。转换后的层次结构中应该有一个ViewOverlay
    【解决方案2】:

    ChangeBounds 类有一个已弃用的setReparent(Boolean) 方法,它引用ChangeTransform“处理不同父级之间的转换”。这个ChangeTransform根本没有产生预期的效果

    ChangeBounds source我们can see得知,如果reparent设置为true(加上其他一些条件),sceneroot的overlay就是用来添加overlay的。但由于某种原因,这不起作用:可能是因为它已被弃用。

    我已经能够通过扩展ChangeBounds 来解决这个问题,以便在我想(在 Kotlin 中)使用覆盖时:

    class OverlayChangeBounds : ChangeBounds() {
    
        override fun createAnimator(sceneRoot: ViewGroup, startValues: TransitionValues?, endValues: TransitionValues?): Animator? {
            if (startValues == null || endValues == null) return super.createAnimator(sceneRoot, startValues, endValues)
    
            val startView = startValues.view
            val endView = endValues.view
    
            if (endView.id in targetIds || targetNames?.contains(endView.transitionName) == true) {
                startView.visibility = View.INVISIBLE
                endView.visibility = View.INVISIBLE
    
                endValues.view = startValues.view.toImageView()
                sceneRoot.overlay.add(endValues.view)
    
                return super.createAnimator(sceneRoot, startValues, endValues)?.apply {
                    addListener(object : AnimatorListenerAdapter() {
                        override fun onAnimationEnd(animation: Animator) {
                            endView.visibility = View.VISIBLE
                            sceneRoot.overlay.remove(endValues.view)
                        }
                    })
                }
            }
    
            return super.createAnimator(sceneRoot, startValues, endValues)
        }
    }
    
    private fun View.toImageView(): ImageView {
        val v = this
        val drawable = toBitmapDrawable()
        return ImageView(context).apply {
            setImageDrawable(drawable)
            scaleType = ImageView.ScaleType.CENTER_CROP
            layout(v.left, v.top, v.right, v.bottom)
        }
    }
    
    private fun View.toBitmapDrawable(): BitmapDrawable {
        val b = Bitmap.createBitmap(layoutParams.width, layoutParams.height, Bitmap.Config.ARGB_8888);
        draw(Canvas(b));
        return BitmapDrawable(resources, b)
    }
    

    【讨论】:

    • 虽然这可能是适合您的解决方案,但我建议您查看我的答案并考虑采用 Android 转换 API 的预期方式。
    【解决方案3】:

    这是我针对这种情况的解决方法(它只是单独类中 ChangeBounds 的一部分)

    import android.animation.Animator;
    import android.animation.AnimatorListenerAdapter;
    import android.animation.ObjectAnimator;
    import android.animation.PropertyValuesHolder;
    import android.annotation.TargetApi;
    import android.graphics.Bitmap;
    import android.graphics.Canvas;
    import android.graphics.Path;
    import android.graphics.PointF;
    import android.graphics.Rect;
    import android.graphics.drawable.BitmapDrawable;
    import android.graphics.drawable.Drawable;
    import android.os.Build;
    import android.transition.Transition;
    import android.transition.TransitionValues;
    import android.util.Property;
    import android.view.View;
    import android.view.ViewGroup;
    
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public class AbsoluteChangeBounds extends Transition {
    
        private static final String PROPNAME_WINDOW_X = "android:changeBounds:windowX";
        private static final String PROPNAME_WINDOW_Y = "android:changeBounds:windowY";
    
        private int[] tempLocation = new int[2];
    
        private static final Property<Drawable, PointF> DRAWABLE_ORIGIN_PROPERTY =
                new Property<Drawable, PointF>(PointF.class, "boundsOrigin") {
                    private Rect mBounds = new Rect();
    
                    @Override
                    public void set(Drawable object, PointF value) {
                        object.copyBounds(mBounds);
                        mBounds.offsetTo(Math.round(value.x), Math.round(value.y));
                        object.setBounds(mBounds);
                    }
    
                    @Override
                    public PointF get(Drawable object) {
                        object.copyBounds(mBounds);
                        return new PointF(mBounds.left, mBounds.top);
                    }
                };
    
        @Override
        public void captureStartValues(TransitionValues transitionValues) {
            captureValues(transitionValues);
        }
    
        @Override
        public void captureEndValues(TransitionValues transitionValues) {
            captureValues(transitionValues);
        }
    
        private void captureValues(TransitionValues values) {
            View view = values.view;
            if (view.isLaidOut() || view.getWidth() != 0 || view.getHeight() != 0) {
                values.view.getLocationInWindow(tempLocation);
                values.values.put(PROPNAME_WINDOW_X, tempLocation[0]);
                values.values.put(PROPNAME_WINDOW_Y, tempLocation[1]);
            }
        }
    
        @Override
        public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
            if (startValues == null || endValues == null) {
                return null;
            }
    
            final View view = endValues.view;
            sceneRoot.getLocationInWindow(tempLocation);
            int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X) - tempLocation[0];
            int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y) - tempLocation[1];
            int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X) - tempLocation[0];
            int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y) - tempLocation[1];
            // TODO: also handle size changes: check bounds and animate size changes
            if (startX != endX || startY != endY) {
                final int width = view.getWidth();
                final int height = view.getHeight();
                Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                Canvas canvas = new Canvas(bitmap);
                view.draw(canvas);
                final BitmapDrawable drawable = new BitmapDrawable(bitmap);
                view.setAlpha(0);
                drawable.setBounds(startX, startY, startX + width, startY + height);
                sceneRoot.getOverlay().add(drawable);
                Path topLeftPath = getPathMotion().getPath(startX, startY, endX, endY);
                PropertyValuesHolder origin = PropertyValuesHolder.ofObject(
                        DRAWABLE_ORIGIN_PROPERTY, null, topLeftPath);
                ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, origin);
                anim.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        sceneRoot.getOverlay().remove(drawable);
                        view.setAlpha(1);
                    }
                });
                return anim;
            }
            return null;
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 2016-04-23
      • 2016-10-24
      • 1970-01-01
      • 2016-02-04
      • 2020-04-29
      • 1970-01-01
      • 2018-07-14
      • 2019-08-07
      • 2018-03-01
      相关资源
      最近更新 更多