嵌套共享元素

时间:2016-05-29 17:32:15

标签: android android-animation android-transitions

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

enter image description here enter image description here enter image description here

我有以下布局:

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的解决方案。这甚至可能吗?

另外,我不打算'压扁'层次结构。我故意将View嵌套到更复杂的用例中。

3 个答案:

答案 0 :(得分:3)

是的,使用Android转换API可以重新进行修改。 重新定位视图时需要考虑一个主要限制:

通常,每个儿童观点都只允许在其父母范围内绘制。这会导致视图在遇到其初始父级的边框时消失,并在一段时间后重新出现在新的父级中。

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

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

您还必须使用ChangeTransform重新显示视图,而不是ChangeBounds或将其添加到TransitionSet

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

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

参与过渡的观点。

答案 1 :(得分:0)

ChangeBounds类有一个弃用的setReparent(Boolean)方法,它引用ChangeTransform来处理不同父项之间的转换“。此ChangeTransform课程 。。

ChangeBounds source我们can see开始,如果reparent设置为true(加上其他一些条件),则使用场景图层的叠加层来添加叠加层。但由于某些原因,这不起作用:可能是因为它已被弃用。

我已经能够通过扩展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)
}

答案 2 :(得分:0)

这是我对这种情况的解决方法(它只是单独类中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;
    }

}
相关问题