Transition 是布局容器发生变化(包括其内部的控件发生变化:如添加控件,某个控件改变颜色)时应用的一种动画。
Transition 的简单调用如下
1 TransitionManager.beginDelayedTransition(sceneRoot);
也可以使用 Scene
1 2 Scene scene = new Scene(sceneRoot, textView); TransitionManager.go(scene, new Fade(Fade.IN));
此时实际并不能执行动画,只有当布局发生变化时才会执行,且一次设置只能执行一次 。动画发生的准确时间是在onPreDraw方法中。
Scene 首先来看Scene类,Scene 定义如下,它需要一个已经存在的容器(mSceneRoot),和一个新的控件或布局(mLayoutId或mLayout)。
1 2 3 4 5 6 7 public final class Scene { private Context mContext; private int mLayoutId = -1 ; private ViewGroup mSceneRoot; private View mLayout; Runnable mEnterAction, mExitAction; }
进入场景(Scene )的执行方法 enter 如下,实际是将容器的控件清空,添加新控件并将Scene对象自身存储进容器中。
1 2 3 4 5 6 7 8 9 public void enter () { getSceneRoot().removeAllViews(); mSceneRoot.addView(mLayout); LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot); mEnterAction.run(); setCurrentScene(mSceneRoot, this ); }
根布局对象 sceneRoot 中的 Tag 对象是一个SparseArray集合,用 layoutId 作为键 新创建的 Scene 对象存储进去,反过来也能从中获取Scene对象。
1 2 3 static Scene getCurrentScene (View view) { return (Scene) view.getTag(com.android.internal.R.id.current_scene); }
这样原本的根布局就埋下了所需的条件,当时机成熟时将被调用,当Transition动画播放完成后将被清除
TransitionManager TransitionManager 是使用 Transition 的调度器,它将 Scene 和 Transition 匹配到一起,这样当某个 Scene 进入时,可以执行对应的 Transition。
1 2 3 4 5 6 7 public class TransitionManager { private static Transition sDefaultTransition = new AutoTransition(); ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>(); ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions = new ArrayMap<Scene, ArrayMap<Scene, Transition>>(); private static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<ViewGroup>(); }
也可以指定离开某Scene时的 Transition 集合,由集合mScenePairTransitions保存,其 key 是进入场景 toScene。
1 2 3 4 public void setTransition (Scene fromScene, Scene toScene, Transition transition) { ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(toScene); sceneTransitionMap.put(fromScene, transition); }
虽然 TransitionManager 保存了 Scene 和 Transition 的对应关系,但如何根据Scene获取Transition 还与所配置的 ViewGroup有关。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private Transition getTransition (Scene scene) { Transition transition = null ; ViewGroup sceneRoot = scene.getSceneRoot(); if (sceneRoot != null ) { Scene currScene = Scene.getCurrentScene(sceneRoot); if (currScene != null ) { ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(scene); if (sceneTransitionMap != null ) { transition = sceneTransitionMap.get(currScene); if (transition != null ) { return transition; } } } } transition = mSceneTransitions.get(scene); return (transition != null ) ? transition : sDefaultTransition; }
TransitionManager 调用动画的执行过程如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot); for (Transition runningTransition : runningTransitions) { runningTransition.pause(sceneRoot); } Scene previousScene = Scene.getCurrentScene(sceneRoot); previousScene.exit(); transition.captureValues(sceneRoot, true ); Scene.setCurrentScene(sceneRoot, null ); sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener); mTransition.captureValues(mSceneRoot, false ); mTransition.playTransition(mSceneRoot);
当视图树发生变化时将执行 OnPreDraw 方法,此时就是执行动画的时机
1 2 3 4 5 6 7 removeListeners(); sPendingTransitions.remove(mSceneRoot); mTransition.captureValues(mSceneRoot, false ); mTransition.playTransition(mSceneRoot);
这里 Transition 的执行实际是放在ViewTreeObserver.OnPreDrawListener接口中进行的,以便容器 mSceneRoot 重绘前调用 ,在播放完成后还要进行对应清理工作。
Transition Transition 动画的数据结构是 TransitionValues,包含了待变化的属性字典
1 2 3 4 5 public class TransitionValues { public View view; public final Map<String, Object> values = new ArrayMap<String, Object>(); final ArrayList<Transition> targetedTransitions = new ArrayList<Transition>(); }
Transition 中的核心就是两个 TransitionValues 集合,表示动画的起始值。
1 2 ArrayList<TransitionValues> mStartValuesList; ArrayList<TransitionValues> mEndValuesList;
在 playTransition 方法中,首先要构建这两个集合
1 2 public abstract void captureStartValues (TransitionValues transitionValues) ;public abstract void captureEndValues (TransitionValues transitionValues) ;
而后是遍历 sceneRoot,为每个 TransitionValues 对创建属性动画。
1 public Animator createAnimator (ViewGroup sceneRoot, TransitionValues startValues,TransitionValues endValues)
Transition 的子类要定义如何获取起始值以及如何创建属性动画上,这都与具体的属性有关。
下面通过子类实例说说如何定义 Transition。
Visibility Visibility 类能够响应控件可见性的变化, 这与他定义的三个属性有关 ,这些属性的数值完全由 View 提供,其名称和类型为
1 2 3 4 5 6 static final String PROPNAME_VISIBILITY = "android:visibility:visibility" ;static final String PROPNAME_PARENT = "android:visibility:parent" ;static final String PROPNAME_SCREEN_LOCATION = "android:visibility:screenLocation" ;
这样在获取属性时,就可以将这些属性值写入属性字典中去
1 2 3 4 5 6 7 8 private void captureValues (TransitionValues transitionValues, int forcedVisibility) { int visibility = transitionValues.view.getVisibility(); transitionValues.values.put(PROPNAME_VISIBILITY, visibility); transitionValues.values.put(PROPNAME_PARENT, transitionValues.view.getParent()); int [] loc = new int [2 ]; transitionValues.view.getLocationOnScreen(loc); transitionValues.values.put(PROPNAME_SCREEN_LOCATION, loc); }
创建动画的方法委托给了子类,以 Fade 为例,它添加了一个透明度属性(由view.getAlpha()提供)
1 static final String PROPNAME_ALPHA = "fade:alpha" ;
创建关于 View透明度的属性动画,变化区间从当前透明度到 1。
1 2 3 4 5 6 @Override public Animator onAppear (ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues) { float startAlpha = getStartAlpha(startValues, 0 ); return ObjectAnimator.ofFloat(view, View.ALPHA, startAlpha, 1 ); }
同理子类 Scale 创建了关于 scaleX 和 scaleY 的属性动画,子类 Slide 创建了关于 TRANSLATION_X 和 TRANSLATION_Y的属性动画,在确立起始点时要考虑屏幕位置,它使用的属性动画要建立 Path,通过改变Path可以改变滑动的轨迹 。
1 ObjectAnimator.ofFloat(view, View.TRANSLATION_X, View.TRANSLATION_Y, path);
其他Transition 类 ChangeBounds 将改变 View 的 Bound 属性,同时也要处理父容器的 Bound 改变。它有如下属性
1 2 3 4 5 private static final String PROPNAME_BOUNDS = "android:changeBounds:bounds" ;private static final String PROPNAME_CLIP = "android:changeBounds:clip" ;private static final String PROPNAME_PARENT = "android:changeBounds:parent" ; private static final String PROPNAME_WINDOW_X = "android:changeBounds:windowX" ; private static final String PROPNAME_WINDOW_Y = "android:changeBounds:windowY" ;
ChangeScroll : scrollX/scrollY 属性
ChangeTransform : AnimationMatrix 属性
ChangeClipBounds : clipBounds 属性
ChangeImageTransform :ImageView 的 animateTransform 属性
TransitionSet 装饰器 TransitionSet 代表着 Transition 集合,并能够为其指定播放顺序。默认实现 AutoTransition 采用顺序播放,包括三个 Transition,即 Fade.OUT,ChangeBounds,Fade.IN。
单个 Transition 可以添加或排除某个 View,排除的规则可以使用 id,transitionname字符串,class等手段。
二维路径插值器(PathMotion) PathMotion 负责在起始点之间进行插值,它返回的是一个路径。
1 public abstract Path getPath (float startX, float startY, float endX, float endY) ;
某些改变位置的 Transition 可以使用,如ChangeBounds
1 new ChangeBounds().setPathMotion(new ArcMotion())
Transition 的实践 活动间的转场动画 使用 ActivityOptions可以得到活动间的转场动画,这里要通过 transitionName 属性来指定采用动画的View。
1 2 3 String transitionName = context.getResources().getString(R.string.transition_app_icon); ActivityOptions transitionActivityOptions = ActivityOptions.makeSceneTransitionAnimation(activity, appIcon, transitionName); context.startActivity(intent, transitionActivityOptions.toBundle());
1.执行这个动画需要设置 Window.FEATURE_ACTIVITY_TRANSITIONS。
ActivityOptions 创建的各种动画只是记录参数到 Bundle 中去,对于 SceneTransition 主要记录 View 和 String(transitionName) 的对应关系,以便于选取 Transition。
2.使用转场动画时,可以使用下列的一对方法来控制转场动画。
1 2 3 void postponeEnterTransition () ; void startPostponedEnterTransition () ;
3.更简单的使用场景动画的方式是
1 2 startActivity(intent); overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right);
overridePendingTransition 分别决定了起始页和跳转页使用的属性动画。
这一点同样可以用在离开时
1 2 3 4 5 @Override public void onBackPressed () { super .onBackPressed(); overridePendingTransition(R.anim.slide_out_right, R.anim.slide_in_left); }
4.使用 Transition 监听器
活动的 Transition 是设置在 Window 上的,采用转场动画应该尽量在动画播放完成后再加载数据,避免处理控件(如RecyclerView)的滑动等。
1 getWindow().getEnterTransition().addListener(this );
自定义Transition 自定义Transition的核心是要改变容器布局中哪些 View 的哪些属性 ,为此需要以下步骤来确定(以改变TextView的字体大小为例)
1.确定属性名称
1 public static final String PRO_NAME_TEXT_SIZE = "text:size" ;
2.如何获取起始值 TransitionValues
1 2 3 4 5 6 7 @Override public void captureStartValues (TransitionValues transitionValues) { if (transitionValues.view instanceof TextView){ TextView tv = (TextView) transitionValues.view; transitionValues.values.put(PRO_NAME_TEXT_SIZE, tv.getTextSize()); } }
3.创建关于字体大小的动画
1 2 3 4 5 6 7 8 9 10 11 @Override public Animator createAnimator (ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { if (endValues.view instanceof TextView){ TextView tv = (TextView) endValues.view; float start = ((TextView) startValues.view).getTextSize(); float end = ((TextView) endValues.view).getTextSize(); PropertyValuesHolder holder = PropertyValuesHolder.ofFloat(PROPERTY_TEXT_SIZE, start, end); return ObjectAnimator.ofPropertyValuesHolder(tv, holder); } return null ; }
这样当设置字体时就会播放动画。
TransitionPlayer 中有一个精彩的引导页示例, 也是 Transition 动画的使用范例。