我正在尝试使用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的解决方案。这甚至可能吗?
另外,我不打算'压扁'层次结构。我故意将View嵌套到更复杂的用例中。
答案 0 :(得分:3)
是的,使用Android转换API可以重新进行修改。 重新定位视图时需要考虑一个主要限制:
通常,每个儿童观点都只允许在其父母范围内绘制。这会导致视图在遇到其初始父级的边框时消失,并在一段时间后重新出现在新的父级中。
通过在所有相关父母上设置android:clipChildren="false"
来关闭相关场景的视图层次结构中的子剪辑。
在更复杂的层次结构中,例如适配器支持的视图,通过TransitionListener
动态切换子剪辑会更高效。
您还必须使用ChangeTransform
重新显示视图,而不是ChangeBounds
或将其添加到TransitionSet
。
在此级别的转换中不需要转换名称或目标,因为框架将自己解决这个问题。如果事情变得更复杂,你会想要
参与过渡的观点。
答案 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;
}
}