1 概述
Android 4.4.2 (API level 19)引入Transition框架,之后很多APP上都使用该框架做出很酷炫的效果。
关系图.png
图中有三个核心的类,分别是Scene、Transition和TransitionManager
2 Scene
- Scene场景,用于保存布局中所有View的属性值,创建Scene的方式可以通过getSceneForLayout方法getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context)
mScene0 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene0, getContext());
mScene1 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene1, getContext());
也可以直接new Scene(ViewGroup sceneRoot, View layout)
View view0 = inflater.inflate(R.layout.scene0, container, false);
View view1 = inflater.inflate(R.layout.scene1, container, false);
mScene0 = new Scene(mSceneRoot, view0);
mScene1 = new Scene(mSceneRoot, view1);
两种方式都需要传SceneRoot,即该场景的根节点。
3 Transition
TIM截图20180409170238.png
Transition过渡动画,前面创建了两个场景,分别保存了视图的一些属性,比如Visibility、position等,Transition就是对于这些属性值的改变定义过渡的效果。
系统内置了一些常用的Transition,Transition的创建可以通过加载xml,如:
res/transition/fade_transition.xml
<fade xmlns:android="http://schemas.android.com/apk/res/android" />
然后在代码中:
Transition mFadeTransition =
TransitionInflater.from(this).
inflateTransition(R.transition.fade_transition);
或者直接在代码中:
Transition mFadeTransition = new Fade();
4 TransitionManager
- TransitionManeger用于将Scene和Transition联系起来,
- 它提供了一系列的方法如
- setTransition(Scene fromScene, Scene toScene, Transition transition)指明起始场景和结束场景、他们的过渡动画是什么,
- go(Scene scene, Transition transition),到指定的场景所使用的过渡动画是什么,
- beginDelayedTransition(ViewGroup sceneRoot, Transition transition),在当前场景到下一帧的过渡效果是什么。比如这里使用> * 我们可以通过Transition.addTarget和removeTarget方法选择性添加或移除执行动画的View。
5 常用API
5.1 AutoTransition
- 用于创建默认过渡的实用工具类,可在场景更改期间自动淡入淡出,移动视图并调整视图大小
- tag为autoTransition
其实是以下几个动画组合顺序执行:
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="sequential">
<fade android:fadingMode="fade_out" />
<changeBounds />
<fade android:fadingMode="fade_in" />
</transitionSet>
在代码中使用:
TransitionManager.beginDelayedTransition(mRoot, new AutoTransition());
if (mTextView.getVisibility() != View.VISIBLE) {
mTextView.setVisibility(View.VISIBLE);
} else {
mTextView.setVisibility(View.GONE);
}
5.2 ChangeBounds
- 捕捉场景更改前后目标视图的布局边界,并在转换期间为这些更改提供动画
- tag changeBounds
//mRoot父view(这里为FrameLayout) mTarget子view
TransitionManager.beginDelayedTransition(mRoot, new ChangeBounds());
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mTarget.getLayoutParams();
if ((lp.gravity & Gravity.START) == Gravity.START) {
lp.gravity = Gravity.BOTTOM | Gravity.END;
} else {
lp.gravity = Gravity.TOP | Gravity.START;
}
mTarget.setLayoutParams(lp);
5.3 ChangeClipBounds
- ChangeClipBounds捕捉场景变化前后的getClipBounds(),并在变换过程中为这些变化提供动画。
- 作用对象:View的getClipBounds()值
- tag为changeClipBounds
Rect BOUNDS = new Rect(20, 20, 100, 100);
TransitionManager.beginDelayedTransition(mRoot, new ChangeClipBounds());
if (BOUNDS.equals(ViewCompat.getClipBounds(mImageView))) {
ViewCompat.setClipBounds(mImageView, null);
} else {
ViewCompat.setClipBounds(mImageView, BOUNDS);
}
5.4 ChangeImageTransform
- 这个Transition在场景变化之前和之后捕获一个ImageView的矩阵,并在转换过程中对其进行动画处理。
- 与ChangeBounds结合使用时,ChangeImageTransform允许更改大小,形状或ImageView.ScaleType的ImageView来平滑地动画内容。
- tag为changeImageTransform
TransitionManager.beginDelayedTransition(mRoot, new ChangeImageTransform());
mImageView.setScaleType(ImageView.ScaleType.XXX);
5.5 ChangeScroll
- 此转换捕获场景更改前后的目标的滚动属性,并为所有更改生成动画。
- 作用对象:View的scroll属性值
- tag为changeScroll
TransitionManager.beginDelayedTransition(mRoot, new ChangeScroll());
mTarget.scrollBy(-100, -100);
5.6 ChangeTransform
- 此过渡在场景更改之前和之后为视图捕获缩放和旋转,并在过渡期间为这些更改制作动画。 通过在场景变化之前和之后捕捉父变换,并在变换过程中为那些动画制作动画,处理父变化。
- 作用对象:View的scale和rotation
- tag 为changeTransform
TransitionManager.beginDelayedTransition(mRoot, new ChangeTransform());
if (mContainer2.getChildCount() > 0) {
mContainer2.removeAllViews();
showRedSquare(mContainer1);
} else {
mContainer1.removeAllViews();
showRedSquare(mContainer2);
mContainer2.getChildAt(0).setRotation(45);
}
private void showRedSquare(FrameLayout container) {
final View view = LayoutInflater.from(getContext())
.inflate(R.layout.red_square, container, false);
container.addView(view);
}
5.7 Explode
- 此转换跟踪对开始和结束场景中目标视图的可见性的更改,并将视图从场景的边缘移入或移出。 可见性由视图的setVisibility(int)状态以及它是否在当前视图层次结构中确定。 消失视图是有限的,如onDisappear(android.view.ViewGroup,TransitionValues,int,TransitionValues,int)中所述。
- 作用对象:View的Visibility
- tag为explode
TransitionManager.beginDelayedTransition(mRoot, new Explode());
int vis = mViews.get(0).getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE;
for (View view : mViews) {
view.setVisibility(vis);
}
5.8 Fade
- 此转换跟踪更改为开始和结束场景中目标视图的可见性,并在视图变为可见或不可见时淡入视图。可见性由视图的setVisibility(int)状态以及它是否在当前视图层次结构中确定。
- 这种转换淡化特定视图的能力以及淡化操作发生的方式是基于视图层次结构中视图的情况。例如,如果一个视图被简单地从其父视图中移除,那么该视图将在淡入淡出时被添加到ViewGroupOverlay中。如果可见视图更改为GONE或INVISIBLE,则在动画期间可见性将更改为VISIBLE。但是,如果某个视图处于也正在改变其可见性的层次结构中,则情况会更加复杂。一般来说,如果结束场景中不再处于层次结构中的视图仍然有父项(因此其父层次结构已被删除,但未从其父项中移除),则它将被单独放置以避免副作用不恰当地将其从其父母移除。唯一的例外是,如果前一场景是从布局资源文件创建的,那么取消父场起始场景视图以淡化它是安全的。
- 作用对象:View的Visibility
- tag为fade
TransitionManager.beginDelayedTransition(mRoot, new Fade());
int vis = mViews.get(0).getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE;
for (View view : mViews) {
view.setVisibility(vis);
}
5.9 Slide
- 该转换跟踪对开始和结束场景中目标视图的可见性的更改,并将视图从场景的一个边缘移入或移出。 可见性由视图的setVisibility(int)状态以及它是否在当前视图层次结构中确定。 消失视图是有限的,如onDisappear(android.view.ViewGroup,TransitionValues,int,TransitionValues,int)中所述。
- 作用对象:View的Visibility
- tag为slide
TransitionManager.beginDelayedTransition(mRoot, new Slide());
int vis = mViews.get(0).getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE;
for (View view : mViews) {
view.setVisibility(vis);
}
5.10 TransitionSet
- TransitionSet是子过渡的父代(包括其他TransitionSet)。 使用TransitionSets可以实现更复杂的转换编排,其中一些组播放ORDERING_TOGETHER和其他播放ORDERING_SEQUENTIAL。
- 通过transitionOrdering属性设置动画执行的顺序,together表示同时执行,sequential表示顺序执行,在代码中可以调用TransitionSet的setOrdering(int)方法,属性值传ORDERING_SEQUENTIAL或者ORDERING_TOGETHER
- 通过使用标签transitionSet以及TransitionSet和Transition的标准属性,可以在资源文件中描述TransitionSet。 可以通过在封闭的transitionSet标签内添加这些子标签来加载TransitionSet对象的子过渡。
代码中创建transitionSet
mTransition = new TransitionSet();
mTransition.addTransition(new ChangeImageTransform());
mTransition.addTransition(new ChangeTransform());
TransitionManager.beginDelayedTransition(mOuterFrame, mTransition);
if (mInnerFrame.getChildCount() > 0) {
mInnerFrame.removeAllViews();
addImageView(mOuterFrame, ImageView.ScaleType.CENTER_CROP, mPhotoSize);
} else {
mOuterFrame.removeViewAt(1);
addImageView(mInnerFrame, ImageView.ScaleType.FIT_XY,
FrameLayout.LayoutParams.MATCH_PARENT);
}
加载xml布局创建transitionSet
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="together">
<changeImageTransform/>
<changeTransform/>
</transitionSet>
mTransition = (TransitionSet) TransitionInflater.from(getContext()).inflateTransition(R.transition.transition);
TransitionManager.beginDelayedTransition(mOuterFrame, mTransition);
if (mInnerFrame.getChildCount() > 0) {
mInnerFrame.removeAllViews();
addImageView(mOuterFrame, ImageView.ScaleType.CENTER_CROP, mPhotoSize);
} else {
mOuterFrame.removeViewAt(1);
addImageView(mInnerFrame, ImageView.ScaleType.FIT_XY,
FrameLayout.LayoutParams.MATCH_PARENT);
}
5.11 PathMotion
- 这个基类可以被扩展来提供沿着转换路径的运动。
- ChangeBounds之类的转场移动视图,通常位于开始位置和结束位置之间的直线路径中。 希望让这些运动在曲线中移动的应用程序可以通过扩展PathMotion并实现getPath(float,float,float,float)来更改View在二维中的内插方式。
- 两个具体实现类ArcMotion和PatternPathMotion
mTransition = new AutoTransition();
mTransition.setPathMotion(new ArcMotion());
TransitionManager.beginDelayedTransition(mRoot, mTransition);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mTarget.getLayoutParams();
if ((lp.gravity & Gravity.START) == Gravity.START) {
lp.gravity = Gravity.END | Gravity.BOTTOM;
} else {
lp.gravity = Gravity.START | Gravity.TOP;
}
mTarget.setLayoutParams(lp);
6 自定义Transition
除了系统内置的Transition,我们还可以自定义Transition效果,需要继承Transition
public class CustomTransition extends Transition {
@Override
public void captureStartValues(TransitionValues values) {}
@Override
public void captureEndValues(TransitionValues values) {}
@Override
public Animator createAnimator(ViewGroup sceneRoot,
TransitionValues startValues,
TransitionValues endValues) {}
}
- 其工作原理是在captureStartValues和captureEndValues中分别记录View的属性值,官网建议确保属性值不冲突,属性值的命名格式参考:package_name:transition_name:property_name
在createAnimator中创建动画,对比属性值的改变执行动画效果,如自定义修改颜色动画效果:
/**
* A sample implementation of support {@link Transition}.
*/
public class ChangeColor extends Transition {
/** Key to store a color value in TransitionValues object */
private static final String PROPNAME_BACKGROUND = "kai.ling";
/**
* Convenience method: Add the background Drawable property value
* to the TransitionsValues.value Map for a target.
*/
private void captureValues(TransitionValues transitionValues) {
// Capture the property values of views for later use
transitionValues.values.put(PROPNAME_BACKGROUND, ((ColorDrawable) transitionValues.view.getBackground()).getColor());
}
@Override
public void captureEndValues(@NonNull TransitionValues transitionValues) {
if (transitionValues.view.getBackground() instanceof ColorDrawable) {
captureValues(transitionValues);
}
}
@Override
public void captureStartValues(@NonNull TransitionValues transitionValues) {
if (transitionValues.view.getBackground() instanceof ColorDrawable) {
captureValues(transitionValues);
}
}
// Create an animation for each target that is in both the starting and ending Scene. For each
// pair of targets, if their background property value is a color (rather than a graphic),
// create a ValueAnimator based on an ArgbEvaluator that interpolates between the starting and
// ending color. Also create an update listener that sets the View background color for each
// animation frame
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
if (null == startValues || null == endValues) {
return null;
}
final View view = endValues.view;
int startBackground = (Integer) startValues.values.get(PROPNAME_BACKGROUND);
int endBackground = (Integer) endValues.values.get(PROPNAME_BACKGROUND);
if (startBackground != endBackground) {
ValueAnimator animator = ValueAnimator.ofObject(new ArgbEvaluator(),
startBackground, endBackground);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Object value = animation.getAnimatedValue();
if (null != value) {
view.setBackgroundColor((Integer) value);
}
}
});
return animator;
}
return null;
}
@Override
public String[] getTransitionProperties() {
return new String[]{
PROPNAME_BACKGROUND
};
}
}
mTransition = new ChangeColor();
mTransition.setDuration(1000);
TransitionManager.beginDelayedTransition(mRoot, mTransition);
mView1.setBackgroundColor(Color.GREEN);
mView2.setBackgroundColor(Color.BLUE);
mView3.setBackgroundColor(Color.RED);
Note
- 1.Android 版本在4.0(API Level 14)到4.4.2(API Level 19)使用Android Support Library's
- 2.对于 SurfaceView可能不起效果,因为SurfaceView的实例是在非UI线程更新的,因此会造成和其他视图动画不同步。
- 3.某些特定的转换类型在应用到TextureView时可能不会产生所需的动画效果。
- 4.继承自AdapterView的如ListView,与该框架不兼容。
- 5.不要对包含文本的视图的大小进行动画