一. 通过这篇文章你将学到以下内容
- ThreadLocal的使用
- 里氏替换原则
- 自定义Drawable对象
主要涉及这三个主要类
Transition 动画容器的抽象类,一些变化延迟到子类去实现
TransitionManager 对外暴露动画的入口,提供开始动画和结束动画
MaterialContainerTransform 实现了Transition方法创建每个动画animator的对象
二. 如何使用
主要利用系统 TransitionManager.beginDelayedTransition();方法。
如果使用我封装的库 (库地址在文章结尾)
//当执行开始动画
transformationLayout.startTransform()
//当执行结束动画
transformationLayout.finishTransform()
三. 源码解析
3.1 [-> TransitionManager.java]
public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {
//sceneRoot 是执行动画view的父类
//transition 是MaterialContainerTransform对象
//如果Pending动画未添加,才会添加; 当前执行或是销毁时会被移除,保证只执行一次 【见小节3.4】
if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) {
if (Transition.DBG) {
Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " +
sceneRoot + ", " + transition);
}
sPendingTransitions.add(sceneRoot);
if (transition == null) {
transition = sDefaultTransition;
}
final Transition transitionClone = transition.clone();
sceneChangeSetup(sceneRoot, transitionClone);【见小节3.1.1】
Scene.setCurrentScene(sceneRoot, null);
//注册开始运行动画
sceneChangeRunTransition(sceneRoot, transitionClone); 【见小节3.3】
}
}
这里transition参数利用里氏替换原则,它是一个抽象类这里需要传递子类对象,来实现不同的特性。
3.1.1 [-> TransitionManager.java]
private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {
// Capture current values
ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
//如果当前运行和要运行是同一个先暂停它,待可运行时一起执行
if (runningTransitions != null && runningTransitions.size() > 0) {
for (Transition runningTransition : runningTransitions) {
runningTransition.pause(sceneRoot);
}
}
//持久化一些动画的属性
if (transition != null) {
transition.captureValues(sceneRoot, true);
}
// Notify previous scene that it is being exited
Scene previousScene = Scene.getCurrentScene(sceneRoot);
if (previousScene != null) {
previousScene.exit();
}
}
3.1.2 [-> TransitionManager.java]
private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>
sRunningTransitions = new ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>();
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private static ArrayMap<ViewGroup, ArrayList<Transition>> getRunningTransitions() {
WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>> runningTransitions =
sRunningTransitions.get();
//取出当前线程是否有正在运行的动画;如果没有创建当前线程的map容器
if (runningTransitions != null) {
ArrayMap<ViewGroup, ArrayList<Transition>> transitions = runningTransitions.get();
if (transitions != null) {
return transitions;
}
}
ArrayMap<ViewGroup, ArrayList<Transition>> transitions = new ArrayMap<>();
runningTransitions = new WeakReference<>(transitions);
sRunningTransitions.set(runningTransitions);
return transitions;
}
sRunningTransitions是使用ThreadLocal来实现的;ThreadLocal可以实现多线程中数据不共享,从而解决线程安全问题。 ThreadLocal的set方法会把数据保存在当前线程的ThreadLocalMap字段上,每个线程都有自己的ThreadLocalMap,从而实现数据隔离效果。ThreadLocal和同步锁来比较,ThreadLocal通过每个线程处理自己的数据,从而增加内存,但速度快了,利用空间换时间。而同步锁解决多线程访问顺序,数据对象在内存中只有一份,内存小了,但访问速度相对长了。
3.2 [-> MaterialContainerTransform.java]
MaterialContainerTransform().apply {
this.startView = startView //从哪个view开始转换;如果是结束动画一般是startView和endView取反来执行
this.endView = endView
this.pathMotion = this@TransformationLayout.pathMotion.getPathMotion() // 线性动画和曲线动画
this.scrimColor = this@TransformationLayout.scrimColor //蒙版背景色
this.drawingViewId = this@TransformationLayout.zOrder //动画容器的边界限制
this.transitionDirection = this@TransformationLayout.direction.value //
this.fadeMode = this@TransformationLayout.fadeMode.value
this.fitMode = MaterialContainerTransform.FIT_MODE_AUTO
this.startElevation = this@TransformationLayout.ELEVATION_NOT_SET
this.endElevation = this@TransformationLayout.ELEVATION_NOT_SET
this.isElevationShadowEnabled = this@TransformationLayout.elevationShadowEnabled
this.isHoldAtEndEnabled = this@TransformationLayout.isHoldAtEndEnabled
duration = this@TransformationLayout.duration
addTarget(endView)
addListener(object : SimpleTransitionListener() {
override fun onTransitionCancel(transition: Transition) {
onFinishTransformation()
}
override fun onTransitionEnd(transition: Transition) {
onFinishTransformation()
onTransformFinishListener?.onTransformFinish(isTransformed)
}
})
}
3.3 [-> TransitionManager.java]
private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
final Transition transition) {
if (transition != null && sceneRoot != null) {
//创建监听view绘制完成类
MultiListener listener = new MultiListener(transition, sceneRoot);
//监听view离开window我们做资源释放
sceneRoot.addOnAttachStateChangeListener(listener);
//监听view渲染完成
sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener);【见小节3.4】
}
}
3.4 [-> MultiListener.java]
@Override
public void onViewDetachedFromWindow(View v) {
removeListeners();
//移除销毁的view
sPendingTransitions.remove(mSceneRoot);
ArrayList<Transition> runningTransitions = getRunningTransitions().get(mSceneRoot);
if (runningTransitions != null && runningTransitions.size() > 0) {
for (Transition runningTransition : runningTransitions) {
runningTransition.resume(mSceneRoot);
}
}
mTransition.clearValues(true);
}
@Override
public boolean onPreDraw() {
removeListeners();
// Don't start the transition if it's no longer pending.
if (!sPendingTransitions.remove(mSceneRoot)) {//移除它保证只执行一次
return true;
}
// Add to running list, handle end to remove it
final ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions =
getRunningTransitions();
ArrayList<Transition> currentTransitions = runningTransitions.get(mSceneRoot);
ArrayList<Transition> previousRunningTransitions = null;
if (currentTransitions == null) {
currentTransitions = new ArrayList<Transition>();
runningTransitions.put(mSceneRoot, currentTransitions);
} else if (currentTransitions.size() > 0) {
previousRunningTransitions = new ArrayList<Transition>(currentTransitions);
}
//把当前正在运行动画记住
currentTransitions.add(mTransition);
mTransition.addListener(new TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
ArrayList<Transition> currentTransitions =
runningTransitions.get(mSceneRoot);
//执行完移除这个动画
currentTransitions.remove(transition);
transition.removeListener(this);
}
});
mTransition.captureValues(mSceneRoot, false);
//如果有未执行完,上次的遗留动画则恢复它
if (previousRunningTransitions != null) {
for (Transition runningTransition : previousRunningTransitions) {
runningTransition.resume(mSceneRoot);
}
}
mTransition.playTransition(mSceneRoot); //开始运行动画 【见小节3.5】
return true;
}
3.4 [-> Transition.java]
void playTransition(ViewGroup sceneRoot) {
mStartValuesList = new ArrayList<TransitionValues>();
mEndValuesList = new ArrayList<TransitionValues>();
matchStartAndEnd(mStartValues, mEndValues);
ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
int numOldAnims = runningAnimators.size();
WindowId windowId = sceneRoot.getWindowId();
for (int i = numOldAnims - 1; i >= 0; i--) {
Animator anim = runningAnimators.keyAt(i);
if (anim != null) {
AnimationInfo oldInfo = runningAnimators.get(anim);
if (oldInfo != null && oldInfo.view != null && oldInfo.windowId == windowId) {
TransitionValues oldValues = oldInfo.values;
View oldView = oldInfo.view;
TransitionValues startValues = getTransitionValues(oldView, true);
TransitionValues endValues = getMatchedTransitionValues(oldView, true);
if (startValues == null && endValues == null) {
endValues = mEndValues.viewValues.get(oldView);
}
boolean cancel = (startValues != null || endValues != null) &&
oldInfo.transition.isTransitionRequired(oldValues, endValues);
if (cancel) {
if (anim.isRunning() || anim.isStarted()) {
if (DBG) {
Log.d(LOG_TAG, "Canceling anim " + anim);
}
anim.cancel();
} else {
if (DBG) {
Log.d(LOG_TAG, "removing anim from info list: " + anim);
}
runningAnimators.remove(anim);
}
}
}
}
}
//把待运行的动画存到mAnimators
createAnimators(sceneRoot, mStartValues, mEndValues, mStartValuesList, mEndValuesList);【见小节3.5】
//运行mAnimators动画
runAnimators();【见小节3.6】
}
3.5 [-> Transition.java]
protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList,
ArrayList<TransitionValues> endValuesList) {
if (DBG) {
Log.d(LOG_TAG, "createAnimators() for " + this);
}
ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
long minStartDelay = Long.MAX_VALUE;
int minAnimator = mAnimators.size();
SparseLongArray startDelays = new SparseLongArray();
int startValuesListCount = startValuesList.size();
for (int i = 0; i < startValuesListCount; ++i) {
TransitionValues start = startValuesList.get(i);
TransitionValues end = endValuesList.get(i);
if (start != null && !start.targetedTransitions.contains(this)) {
start = null;
}
if (end != null && !end.targetedTransitions.contains(this)) {
end = null;
}
if (start == null && end == null) {
continue;
}
// Only bother trying to animate with values that differ between start/end
boolean isChanged = start == null || end == null || isTransitionRequired(start, end);
if (isChanged) {
if (DBG) {
View view = (end != null) ? end.view : start.view;
Log.d(LOG_TAG, " differing start/end values for view " + view);
if (start == null || end == null) {
Log.d(LOG_TAG, " " + ((start == null) ?
"start null, end non-null" : "start non-null, end null"));
} else {
for (String key : start.values.keySet()) {
Object startValue = start.values.get(key);
Object endValue = end.values.get(key);
if (startValue != endValue && !startValue.equals(endValue)) {
Log.d(LOG_TAG, " " + key + ": start(" + startValue +
"), end(" + endValue + ")");
}
}
}
}
// createAnimator是由子类去实现的,因为动画是可变。【见小节3.5.1】
Animator animator = createAnimator(sceneRoot, start, end);
if (animator != null) {
// Save animation info for future cancellation purposes
View view = null;
TransitionValues infoValues = null;
if (end != null) {
view = end.view;
String[] properties = getTransitionProperties();
if (properties != null && properties.length > 0) {
infoValues = new TransitionValues(view);
TransitionValues newValues = endValues.viewValues.get(view);
if (newValues != null) {
for (int j = 0; j < properties.length; ++j) {
infoValues.values.put(properties[j],
newValues.values.get(properties[j]));
}
}
int numExistingAnims = runningAnimators.size();
for (int j = 0; j < numExistingAnims; ++j) {
Animator anim = runningAnimators.keyAt(j);
AnimationInfo info = runningAnimators.get(anim);
if (info.values != null && info.view == view &&
((info.name == null && getName() == null) ||
info.name.equals(getName()))) {
if (info.values.equals(infoValues)) {
// Favor the old animator
animator = null;
break;
}
}
}
}
} else {
view = (start != null) ? start.view : null;
}
if (animator != null) {
if (mPropagation != null) {
long delay = mPropagation
.getStartDelay(sceneRoot, this, start, end);
startDelays.put(mAnimators.size(), delay);
minStartDelay = Math.min(delay, minStartDelay);
}
//把animator对象存起来
AnimationInfo info = new AnimationInfo(view, getName(), this,
sceneRoot.getWindowId(), infoValues);
runningAnimators.put(animator, info);
mAnimators.add(animator);
}
}
}
}
if (startDelays.size() != 0) {
for (int i = 0; i < startDelays.size(); i++) {
int index = startDelays.keyAt(i);
Animator animator = mAnimators.get(index);
long delay = startDelays.valueAt(i) - minStartDelay + animator.getStartDelay();
animator.setStartDelay(delay);
}
}
}
3.5.1 [-> MaterialContainerTransform.java]
@Nullable
@Override
public Animator createAnimator(
@NonNull ViewGroup sceneRoot,
@Nullable TransitionValues startValues,
@Nullable TransitionValues endValues) {
if (startValues == null || endValues == null) {
return null;
}
RectF startBounds = (RectF) startValues.values.get(PROP_BOUNDS);
ShapeAppearanceModel startShapeAppearanceModel =
(ShapeAppearanceModel) startValues.values.get(PROP_SHAPE_APPEARANCE);
if (startBounds == null || startShapeAppearanceModel == null) {
Log.w(TAG, "Skipping due to null start bounds. Ensure start view is laid out and measured.");
return null;
}
RectF endBounds = (RectF) endValues.values.get(PROP_BOUNDS);
ShapeAppearanceModel endShapeAppearanceModel =
(ShapeAppearanceModel) endValues.values.get(PROP_SHAPE_APPEARANCE);
if (endBounds == null || endShapeAppearanceModel == null) {
Log.w(TAG, "Skipping due to null end bounds. Ensure end view is laid out and measured.");
return null;
}
final View startView = startValues.view;
final View endView = endValues.view;
final View drawingView;
View boundingView;
View drawingBaseView = endView.getParent() != null ? endView : startView;
if (drawingViewId == drawingBaseView.getId()) {
drawingView = (View) drawingBaseView.getParent();
boundingView = drawingBaseView;
} else {
drawingView = findAncestorById(drawingBaseView, drawingViewId);
boundingView = null;
}
// Calculate drawable bounds and offset start/end bounds as needed
RectF drawingViewBounds = getLocationOnScreen(drawingView);
float offsetX = -drawingViewBounds.left;
float offsetY = -drawingViewBounds.top;
RectF drawableBounds = calculateDrawableBounds(drawingView, boundingView, offsetX, offsetY);
startBounds.offset(offsetX, offsetY);
endBounds.offset(offsetX, offsetY);
boolean entering = isEntering(startBounds, endBounds);
if (!appliedThemeValues) {
// Apply theme values if we didn't already apply them up front in the constructor and if they
// haven't already been set by the user.
maybeApplyThemeValues(drawingBaseView.getContext(), entering);
}
//转换主要依靠这个自定义的Drawable来实现的【见小节3.5.2】
final TransitionDrawable transitionDrawable =
new TransitionDrawable(
getPathMotion(),
startView,
startBounds,
startShapeAppearanceModel,
getElevationOrDefault(startElevation, startView),
endView,
endBounds,
endShapeAppearanceModel,
getElevationOrDefault(endElevation, endView),
containerColor,
startContainerColor,
endContainerColor,
scrimColor,
entering,
elevationShadowEnabled,
FadeModeEvaluators.get(fadeMode, entering),
FitModeEvaluators.get(fitMode, entering, startBounds, endBounds),
buildThresholdsGroup(entering),
drawDebugEnabled);
// Set the bounds of the transition drawable to not exceed the bounds of the drawingView.
transitionDrawable.setBounds(
Math.round(drawableBounds.left),
Math.round(drawableBounds.top),
Math.round(drawableBounds.right),
Math.round(drawableBounds.bottom));
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.addUpdateListener(
new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//从0-1的动画百分比同步给drawable去绘制样式
transitionDrawable.setProgress(animation.getAnimatedFraction());
}
});
addListener(
new TransitionListenerAdapter() {
@Override
public void onTransitionStart(@NonNull Transition transition) {
// Add the transition drawable to the root ViewOverlay
// 把转换的背景样式添加到界面上
ViewUtils.getOverlay(drawingView).add(transitionDrawable);
// Hide the actual views at the beginning of the transition
//把真实的布局中开始view和结束view隐藏起来,显示我们转换样式
startView.setAlpha(0);
endView.setAlpha(0);
}
@Override
public void onTransitionEnd(@NonNull Transition transition) {
removeListener(this);
if (holdAtEndEnabled) {
// Keep drawable showing and views hidden (useful for Activity return transitions)
return;
}
// Show the actual views at the end of the transition
//动画结束了把布局中2个转换的view显示出来
startView.setAlpha(1);
endView.setAlpha(1);
// 把转换的背景从界面移除
// Remove the transition drawable from the root ViewOverlay
ViewUtils.getOverlay(drawingView).remove(transitionDrawable);
}
});
return animator;
}
3.5.2 [-> TransitionDrawable.java]
@Override
public void draw(@NonNull Canvas canvas) {
if (scrimPaint.getAlpha() > 0) {
canvas.drawRect(getBounds(), scrimPaint);
}
int debugCanvasSave = drawDebugEnabled ? canvas.save() : -1;
if (elevationShadowEnabled && currentElevation > 0) {
drawElevationShadow(canvas);
}
// Clip the canvas to container's path. Anything drawn to the canvas after this clipping will
// be masked inside the clipped area.
maskEvaluator.clip(canvas);
maybeDrawContainerColor(canvas, containerPaint);
//当MaterialContainerTransform.fadeMode为FADE_MODE_IN时fadeMode.endOnTop是true
//默认走if先绘制startView后endView
if (fadeModeResult.endOnTop) {
drawStartView(canvas);
drawEndView(canvas);
} else {
drawEndView(canvas);
drawStartView(canvas);
}
if (drawDebugEnabled) {
canvas.restoreToCount(debugCanvasSave);
drawDebugCumulativePath(canvas, currentStartBounds, debugPath, Color.MAGENTA);
drawDebugRect(canvas, currentStartBoundsMasked, Color.YELLOW);
drawDebugRect(canvas, currentStartBounds, Color.GREEN);
drawDebugRect(canvas, currentEndBoundsMasked, Color.CYAN);
drawDebugRect(canvas, currentEndBounds, Color.BLUE);
}
}
因为TransitionDrawable是自定义Drawable所以主要看draw方法,
3.6 [-> Transition.java]
protected void runAnimators() {
if (DBG) {
Log.d(LOG_TAG, "runAnimators() on " + this);
}
start();//回调动画的监听 onTransitionStart 方法
ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
// Now start every Animator that was previously created for this transition
for (Animator anim : mAnimators) {
if (DBG) {
Log.d(LOG_TAG, " anim: " + anim);
}
if (runningAnimators.containsKey(anim)) {
start();
runAnimator(anim, runningAnimators);//去执行动画【见小节3.7】
}
}
mAnimators.clear();
end();//回调动画的监听 onTransitionEnd 方法
}
3.7 [-> Transition.java]
private void runAnimator(Animator animator,
final ArrayMap<Animator, AnimationInfo> runningAnimators) {
if (animator != null) {
// TODO: could be a single listener instance for all of them since it uses the param
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mCurrentAnimators.add(animation);
}
@Override
public void onAnimationEnd(Animator animation) {
runningAnimators.remove(animation);
mCurrentAnimators.remove(animation);
}
});
animate(animator);//真正执行动画【见小节3.8】
}
}
3.8 [-> Transition.java]
protected void animate(Animator animator) {
// TODO: maybe pass auto-end as a boolean parameter?
if (animator == null) {
end();
} else {
if (getDuration() >= 0) {
animator.setDuration(getDuration());
}
if (getStartDelay() >= 0) {
animator.setStartDelay(getStartDelay() + animator.getStartDelay());
}
if (getInterpolator() != null) {
animator.setInterpolator(getInterpolator());
}
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
end();
animation.removeListener(this);
}
});
animator.start();//开始执行动画
}
}
四. 小结
可见,View与View之间的动画转换,它本质利用自定义drawable来实现的;通过属性动画0~1的百分比,来不断计算一些属性值,drawable赋值当view上来实现的;
github: github.com/hy-liuyuzhe…