Android源码设计模式(一) -- 面向对象的六大原则
Android源码设计模式(二)-- 应用最广的模式:单例模式
Android源码设计模式(三)-- 自由扩展你的项目:Builder 模式
Android源码设计模式(四)-- 时势造英雄:策略模式
Android源码设计模式(五)-- 使编程更有灵活性:责任链模式
Android源码设计模式(六)— 编程好帮手:代理模式
Android源码设计模式(七)— 解决、解耦的钥匙 — 观察者模式
简书 MD 语法不识别 [TOC] ,也不会根据标题行(#) 来插入目录,作为每次看资料喜欢先看目录把握总体的我来说,很不习惯,查找跳转也不方便,所以,如果看到文章没有目录预览的,请安装脚本:简书目录脚本地址
一、策略模式的定义
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
二、策略模式的使用场景
- 针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。
- 需要安全的封装多种同一类型的操作时。
- 出现同一抽象多个子类,而又需要使用 if-else 或者 switch-case 来选择时。
三、策略模式的 UML 类图
策略模式 UML图角色介绍
- Context:用来操作策略的上下文环境。
- Strategy : 策略的抽象。
- ConcreteStrategyA、ConcreteStrategyB : 具体的策略实现。
四、策略模式的简单实现
通常如果一个问题有多个解决方案时,最简单的方式是用 if-else 或者 switch-case 方式选择不同的解决方案,但是会带来一系列问题,例如耦合性高、代码臃肿、难以维护等。如果解决方案中包括大量的处理逻辑需要封装,或者处理方式变动较大的时候则就显得混乱、复杂,当需要增加一种方案时还需要修改类中的代码。这就没有遵循开闭原则。
下面我们以在北京坐公共交通工具的费用计算来演示一个简单示例。
public class PriceCalculator {
/**
* 公交车类型
*/
private static final int BUS = 1;
/**
* 地铁类型
*/
private static final int SUBWAY = 2;
public static void main(String[] args) {
PriceCalculator priceCalculator = new PriceCalculator();
System.out.println("坐16公里的公交车票价为:" + priceCalculator.calculatePrice(16, BUS));
System.out.println("坐16公里的地铁票价为:" + priceCalculator.calculatePrice(16, SUBWAY));
}
/**
* 坐公共交通的费用
* @param km 距离
* @param type 乘坐类型
* @return 价格
*/
private int calculatePrice(int km, int type) {
if (type == BUS) {
return busPrice(km);
} else if (type == SUBWAY) {
return subwayPrice(km);
} else {
return 0;
}
}
/**
* 公交车价格:10公里(含)内2元,10公里以上部分,每增加1元可乘坐5公里
* @param km 距离
* @return 价格
*/
private int busPrice(int km) {
// 超过10公里的距离
int extraTotal = km - 10;
// 超过的距离是5公里的倍数
int extraFactor = extraTotal / 5;
// 超过的距离对5公里取余
int fraction = extraTotal % 5;
// 价格计算
int price = 2 + extraFactor * 1;
return fraction > 0 ? ++price : price;
}
/**
* 地铁价格:6公里(含)内3元;6公里至12公里(含)4元;12公里至22公里(含)5元;
* 22公里至32公里(含)6元;32公里以上部分,每增加1元可乘坐20公里。
* @param km 距离
* @return 价格
*/
private int subwayPrice(int km) {
if (km <= 6) {
return 3;
} else if (km > 6 && km <= 12) {
return 4;
} else if (km > 12 && km <= 22) {
return 5;
} else if (km > 22 && km <= 32) {
return 6;
} else {
// 其他距离简化为7元
return 7;
}
}
}
PriceCalculator 类很明显的问题就是并不是单一职责,首先它承担了计算公交车和地铁乘坐价格的职责;另一个问题就是通过 if-else 的形式来判断使用哪种计算形式。
当我们增加一种出行方式时,如出租车,那么我们就需要在 PriceCalculator 中增加一个方法来计算出租车出行的价格,并且在 calculatePrice(int km, int type) 函数中增加一个判断来计算出租车的价格,这时代码就比较混乱了,当价格的计算方式变化时,需要直接修改这个类中的代码,那么很可能有一段代码是其他几个计算方法所共同使用的,就容易引起错误。
另外,在增加出行方式时,我们还需要继续添加 if-else,它会使得代码变得越来越臃肿,难以维护。为了解决这个问题,可以使用策略模式(当然,也可以把每个计算方法独立成一个函数,让外部调用对应的方法即可,但是这也是另一种耦合的形式,对于可变性较大的算法族来说还是不合适使用这种方式。)
下面我们对于上述示例用策略模式进行重构。
首先我们需要定义一个抽象的价格计算接口,这里命名为 CalculateStrategy,具体代码如下:
public interface CalculateStrategy {
/**
* 按距离来计算价格
* @param km 距离
* @return 价格
*/
int calculatePrice(int km);
}
对于每一种出行方式我们都有哦一个独立的计算策略类,这些策略类都实现了 CalculateStrategy 接口,例如下面是公交车和地铁的计算策略类:
/**
* 公交车价格计算策略
*/
public class BusStrategy implements CalculateStrategy {
/**
* 公交车价格:10公里(含)内2元,10公里以上部分,每增加1元可乘坐5公里
* @param km 距离
* @return 价格
*/
@Override
public int calculatePrice(int km) {
// 超过10公里的总距离
int extraTotal = km - 10;
// 超过的距离是5公里的倍数
int extraFactor = extraTotal / 5;
// 超过的距离对5公里取余
int fraction = extraTotal % 5;
// 计算价格
int price = 2 + extraFactor * 1;
return fraction > 0 ? ++price : price;
}
}
/**
* 地铁价格计算策略
*/
public class SubwayStrategy implements CalculateStrategy {
/**
* 地铁价格:6公里(含)内3元;6公里至12公里(含)4元;12公里至22公里(含)5元;
* 22公里至32公里(含)6元;32公里以上部分,每增加1元可乘坐20公里。
*
* @param km 距离
* @return 价格
*/
@Override
public int calculatePrice(int km) {
if (km <= 6) {
return 3;
} else if (km > 6 && km <= 12) {
return 4;
} else if (km > 12 && km <= 22) {
return 5;
} else if (km > 22 && km <= 32) {
return 6;
} else {
// 其他距离简化为7元
return 7;
}
}
}
我们再创建一个扮演 Context 角色的类,这里将它命名为 TranficCalculator,代码如下:
/**
* 公交出行价格计算器
*/
public class TranficCalculator {
CalculateStrategy strategy;
public static void main(String[] args) {
TranficCalculator calculator = new TranficCalculator();
// 设置计算策略
calculator.setStrategy(new BusStrategy());
// 计算价格
System.out.println("公交车乘16公里的价格:" + calculator.strategy(16));
}
public int strategy(int km) {
return strategy.calculatePrice(km);
}
public void setStrategy(CalculateStrategy strategy) {
this.strategy = strategy;
}
}
这种方案在隐藏实现的同时,可扩展性变得很强,例如,当我们需要增加出租车的计算策略时,只需要添加一个出租车计算策略类,然后将该策略设置给 TranficCalculator,最好直接通过 TranficCalculator 对象的计算方法即可。示例代码如下:
/**
* 出租车计算策略
*/
public class TaxiStrategy implements CalculateStrategy {
@Override
public int calculatePrice(int km) {
// 价格我们简单计算为公里数*2
return km * 2;
}
}
将策略注入到 TranficCalculator 中。
/**
* 出租出行价格计算器
*/
public class TranficCalculator {
CalculateStrategy strategy;
public static void main(String[] args) {
TranficCalculator calculator = new TranficCalculator();
// 设置计算策略
calculator.setStrategy(new TaxiStrategy());
// 计算价格
System.out.println("出租车乘16公里的价格:" + calculator.strategy(16));
}
public int strategy(int km) {
return strategy.calculatePrice(km);
}
public void setStrategy(CalculateStrategy strategy) {
this.strategy = strategy;
}
}
通过上述示例我们可以清晰地看出二者的区别所在。前者通过 if-else 来解决问题,虽实现简单,类型层级单一,但暴漏的问题也很明显,即代码臃肿,逻辑复杂,难以升级和维护,没有结构可言;
后者则是通过建立抽象,将不同的策略构建成一个具体的策略实现,通过不同的策略实现算法替换。在简化逻辑、结构的同时,增强了系统的可读性、稳定性、可扩展性,这对于较为复杂的业务逻辑显得更为直观,扩展也更为方便。
五、Android 中策略模式实现
日常的 Android 开发中经常会用到动画,Android 中最简单的动画就是 Tween Animation 了,当然帧动画和属性动画也挺方便的,但是基本原理都类似,毕竟动画的本质都是一帧一帧的展现给用户的,只不要当 fps 小于 60 的时候,人眼基本看不出间隔,也就成了所谓的流畅动画。(注:属性动画是3.0以后才有的,低版本可采用NineOldAndroids来兼容。而动画的动态效果往往也取决于插值器 Interpolator 不同,我们只需要对 Animation 对象设置不同的 Interpolator 就可以实现不同的效果。这些插值器就是策略模式的典型应用。
5.1 时间插值器
在开始之前我们需要先了解动画中的 TimeInterPolator,也就是时间插值器。它的作用是根据时间流逝的百分比计算出当前属性值改变的百分比,系统预置的有:
AccelerateDecelerateInterpolator 在动画开始与介绍的地方速率改变比较慢,在中间的时候加速
AccelerateInterpolator 在动画开始的地方速率改变比较慢,然后开始加速
AnticipateInterpolator 开始的时候向后然后向前甩
AnticipateOvershootInterpolator 开始的时候向后然后向前甩一定值后返回最后的值
BounceInterpolator 动画结束的时候弹起
CycleInterpolator 动画循环播放特定的次数,速率改变沿着正弦曲线
DecelerateInterpolator 在动画开始的地方快然后慢
LinearInterpolator 以常量速率改变
OvershootInterpolator 向前甩一定值后再回到原来位置
PathInterpolator 路径插值器
5.2 估值器
插值器并不是一个人在战斗,动画的完成需要插值器和估值器协作完成,也就是 TypeEvaluator(类型估值器)。
它的作用是根据当前属性值改变的百分比计算改变后的属性值,也就是说 TypeEvaluator 计算得到的才是属性的值。
时间插值器计算得到的当前点的时间流逝百分比,TypeEvaluator 根据这个百分比、属性起始值,目标值来计算当前时刻该属性的值,最后将这个值设置给 View,不断的重复这个过程就形成了属性动画。
系统预置的估值器有:整型属性(IntEvaluator)、浮点型属性(FloatEvaluator)和 Color 属性(ArgbEvaluator)。
5.3 动画中的时间插值器
时间插值器应用于动画中,而动画作用于 View 上。当我们要对某个 View 执行动画时,我们会先构建一个 Animation 对象,然后调用 View 的 startAnimation(Animation animation)。
public void startAnimation(Animation animation) {
//初始化动画开始时间
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
//对View设置动画
setAnimation(animation);
//刷新父类缓存
invalidateParentCaches();
//刷新View本身及子View
invalidate(true);
}
startAnimation 中首先设置了动画的起始时间,然后将该动画设置到该 View 中,最后再向 ViewGroup 请求刷新视图,随后 ViewGroup 就会调用 disPatchDraw 对这个 View 所在区域进行重绘。对于某个 View 的重绘最终会调用 ViewGroup 中的 drawChild(Canvas canvas, View child, long drawingTime):
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
再看下 View 中的 draw(Canvas canvas, ViewGroup parent, long drawingTime) 方法中是如何调用使用Animation 的:
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
//...
//查看是否需要清除动画信息
final int flags = parent.mGroupFlags;
if ((flags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) == ViewGroup.FLAG_CLEAR_TRANSFORMATION) {
parent.getChildTransformation().clear();
parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
//获取设置的动画信息
final Animation a = getAnimation();
if (a != null) {
//绘制动画
more = drawAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
} else {
//...
}
}
可以看出在父类调用 View 的 draw 方法中,会先判断是否设置了清除到需要做该表的标记,然后再获取设置的动画的信息,如果设置了动画,就会调用 View 中的 drawAnimation 方法,具体如下:
private boolean drawAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
Transformation invalidationTransform;
final int flags = parent.mGroupFlags;
//1.判断动画是否已经初始化过
final boolean initialized = a.isInitialized();
if (!initialized) {
//初始化动画
//如果设置了动画监听,则触发对应回调
a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
onAnimationStart();
}
//获取 Transformation 对象,存储动画信息
final Transformation t = parent.getChildTransformation();
//2.调用了 Animation 的 getTransformation 方法,这里就是通过计算获取动画的相关值
boolean more = a.getTransformation(drawingTime, t, 1f);
if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
if (parent.mInvalidationTransformation == null) {
parent.mInvalidationTransformation = new Transformation();
}
invalidationTransform = parent.mInvalidationTransformation;
a.getTransformation(drawingTime, invalidationTransform, 1f);
} else {
invalidationTransform = t;
}
if (more) {
//3.根据具体实现,判断当前动画类型是否需要进行调整位置大小,然后刷新不同的区域
if (!a.willChangeBounds()) {
//...
}else{
//...
}
}
return more;
}
其中主要的操作是动画始化、动画操作、界面刷新。在 drawAnimation 中首先会判断动画是否进行了初始化,如果未初始化则先初始化,然后调用动画监听器的 onStart 函数。动画的具体实现是调用了 Animation 中的getTransformation(long currentTime, Transformation outTransformation,float scale) 方法。
public boolean getTransformation(long currentTime, Transformation outTransformation, float scale) {
mScaleFactor = scale;
return getTransformation(currentTime, outTransformation);
}
在上面的方法中主要是获取缩放系数和调用 Animation.getTransformation(long currentTime, Transformation outTransformation) 来计算和应用动画效果。
public boolean getTransformation(long currentTime, Transformation outTransformation) {
if (mStartTime == -1) {
mStartTime = currentTime;
}
final long startOffset = getStartOffset();
final long duration = mDuration;
//1.计算当前时间流逝百分比
float normalizedTime;
if (duration != 0) {
normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
(float) duration;
} else {
// time is a step-change with a zero duration
normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
}
//动画是否已经完成
final boolean expired = normalizedTime >= 1.0f || isCanceled();
mMore = !expired;
if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
if (!mStarted) {
fireAnimationStart();
mStarted = true;
if (NoImagePreloadHolder.USE_CLOSEGUARD) {
guard.open("cancel or detach or getTransformation");
}
}
if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if (mCycleFlip) {
normalizedTime = 1.0f - normalizedTime;
}
//2.通过插值器获取动画执行百分比
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
//3.应用动画效果
applyTransformation(interpolatedTime, outTransformation);
}
//4.如果动画执行完毕,那么触发动画完成回调或者执行重复动画操作
if (expired) {
if (mRepeatCount == mRepeated || isCanceled()) {
if (!mEnded) {
mEnded = true;
guard.close();
fireAnimationEnd();
}
} else {
if (mRepeatCount > 0) {
mRepeated++;
}
if (mRepeatMode == REVERSE) {
mCycleFlip = !mCycleFlip;
}
mStartTime = -1;
mMore = true;
fireAnimationRepeat();
}
}
if (!mMore && mOneMoreTime) {
mOneMoreTime = false;
return true;
}
return mMore;
}
在上述函数中,首先会获取已经流逝的时间百分比,然后再通过插值器来重新计算这个百分比,也就是调用了插值器的 getInterpolation(float input) 方法来获取当前时间的百分比,并且以此来计算当前动画的属性值,然后调用 applyTransformation 方法将属性应用到对应的对象上,applyTransformation 在 Animation 基类是个空实现,那么我们选择 ScaleAnimation 看看具体实现:
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float sx = 1.0f;
float sy = 1.0f;
float scale = getScaleFactor();
if (mFromX != 1.0f || mToX != 1.0f) {
sx = mFromX + ((mToX - mFromX) * interpolatedTime);
}
if (mFromY != 1.0f || mToY != 1.0f) {
sy = mFromY + ((mToY - mFromY) * interpolatedTime);
}
//通过 Matrix 对 View 实现缩放
if (mPivotX == 0 && mPivotY == 0) {
t.getMatrix().setScale(sx, sy);
} else {
t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
}
}
当执行完 applyTransformation 后,View 的属性就发送了变化,不断重复这个过程,动画就随之产生。
这个过程中,插值器扮演着重要的角色,他将动画的速率计算封装到一个接口中,也就是 Interpolator 中,该接口只有一个方法 getInterpolation(float input) ,通过这个方法来修改动画的流逝时间百分比,以此达到动画的加速减速等效果。
Interpolator 就是这个计算策略的抽象,LinearInterpolator,CycleInterpolator 等插值器就是具体的实现策略,通过注入不同的插值器实现不同的效果。
Interpolator 策略模式 UML
5.4 深入属性动画
Android 系统在一开始的时候就给我们提供了两种实现动画效果的方式,逐帧动画 (frame-by-frame animation)和补间动画 (tweened animation)。逐帧动画的工作原理很简单,其实就是将一个完整的动画拆分成一张张单独的图片,然后再将它们连贯起来进行播放,类似于动画片的工作原理。补间动画则是可以对 View 进行一系列的动画操作,包括淡入淡出、缩放、平移、旋转四种。
自 Android 3.0 版本开始,系统给我们提供了一种全新的动画模式,属性动画 (property animation),它的功能非常强大,弥补了之前补间动画的一些缺陷,几乎是可以完全替代掉补间动画了。
考虑到很多应用会兼容 Android 3.0 以下的设备,这种情况下需要兼容 Android 3.0 以下的动画库,低版本可采用NineOldAndroids来兼容。
1、属性动画体系的总体设计
Animator UMLAnimator 通过 PropertyValuesHolder 来更新对象的目标属性,若用户没有设置目标属性的 Property 对象,那么会通过反射的形式调用目标属性的 setter 方法来更新属性值;否则,通过 Prpperty 更新属性值。
这个属性值是通过 KeyFrameSet 的计算得到,而KeyFrameSet 又是通过时间插值器和类型股指期来计算,动画执行过程中不断计算当前时刻的目标属性值,然后更新属性值达到动画效果。
2、属性动画核心类介绍
Animator 核心类 | 说明 |
---|---|
ValueAnimator | Animator 的子类,实现了动画的整个处理逻辑,也是属性动画最为核心的类。 |
ObjectAnimator | 对象属性动画操作的类,继承自 ValueAnimator,通过该类使用动画的形式操作对象的属性。 |
TimeInterPolator | 时间插值器,作用是根据时间流逝百分比计算出当前属性值改变的百分比,系统预置的有:LinearInterpolator(线性插值器)、DecelerateInterpolator(减速插值器)、AccelerateDecelerateInterpolator(加速减速插值器) |
TypeEvaluator | 类型估值器,作用是根据当前属性改变的百分比计算改变后的属性值。系统预置的有:整型属性(IntEvaluator)、浮点型属性(FloatEvaluator)和 Color 属性(ArgbEvaluator)。 |
Property | 属性对象,主要是定义了属性的 set 和 get 方法 |
PropertyValuesHolder | PropertyValuesHolder 是持有目标属性的 Property,setter,getter 方法以及关键帧的集合 |
KeyFrameSet | 存储一个动画的关键帧集合 |
AnimatorProxy | 在 Android 3.0 以下事宜 View 的属性动画辅助类 |
3、基本使用
Demo 1:改变一个对象 myObject 的 transactionY 属性,让其沿着 y 轴向上平移一段距离,该动画在默认时间内完成,动画完成时间是可以定义的。
ObjectAnimator.ofFloat(myObject, "transactionY", -myObject.getHeight()).start();
Demo 2:改变一个对象的背景颜色属性,典型的情况是改变 View 的背景颜色,下面的动画是让背景颜色在 3s 内实现从 red 到 blue 的渐变,并且动画会无限循环而且有反转效果。
ValueAnimator colorAnim = ObjectAnimator.ofInt(this,"backgroundColor", Color.RED,Color.BLUE);
colorAnim.setDuration(3000);
colorAnim.setEvaluator(new ArgbEvaluator());
colorAnim.setRepeatCount(ValueAnimator.INFINITE);
colorAnim.setRepeatMode(ValueAnimator.REVERSE);
colorAnim.start();
Demo 3:动画集合,5s 内对 View 的旋转,平移,缩放和透明度都进行改变。
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(myView, "rotationX", 0, 360),
ObjectAnimator.ofFloat(myView, "rotationY", 0, 180),
ObjectAnimator.ofFloat(myView, "rotation", 0, -90),
ObjectAnimator.ofFloat(myView, "translationX", 0, 90),
ObjectAnimator.ofFloat(myView, "translationY", 0, 90),
ObjectAnimator.ofFloat(myView, "scaleX", 1, 1.5f),
ObjectAnimator.ofFloat(myView, "scaleY", 1, 1.5f),
ObjectAnimator.ofFloat(myView, "alpha", 1, 0.25f, 1)
);
set.setDuration(5000).start();
4、流程图
ValueAnimator 流程图
ValueAnimator 流程图
ObjectAnimator 流程图
ObjectAnimator 流程图
5、详细设计 UML 图
详细设计 UML 图6、核心原理设计
以一个具体示例作为分析入口,对某个 View 的 x 轴缩放到原来的 0.3 倍,动画执行时间为 1s。
ValueAnimator scaleAnim = ObjectAnimator.ofFloat(this,"scaleX", 0.3f);
scaleAnim.setDuration(1000);
scaleAnim.start();
从 ObjectAnimator 开始分析
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
//1.构建动画对象
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
//2.设置属性值
anim.setFloatValues(values);
return anim;
}
在 ObjectAnimator 中,首先构建了 ObjectAnimator 对象,然后将属性值设置给该对象,属性值为 values,是一个可变参数。
@Override
public void setFloatValues(float... values) {
if (mValues == null || mValues.length == 0) {
// No values yet - this animator is being constructed piecemeal. Init the values with
// whatever the current propertyName is
if (mProperty != null) {
setValues(PropertyValuesHolder.ofFloat(mProperty, values));
} else {
setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
}
} else {
super.setFloatValues(values);
}
}
在 setFloatValues 中出现了一个类 PropertyValuesHolder,这个类是该动画库的核心类之一,作用是保存属性名称和它的 setter,getter 方法以及它的目标值。我们可以看看它的一些相关函数。
public class PropertyValuesHolder implements Cloneable {
String mPropertyName;//属性名
protected Property mProperty;//属性对象
Method mSetter = null;//属性的setter方法
private Method mGetter = null;//属性的getter方法
Class mValueType;//属性的类型,如 float,int
Keyframes mKeyframes = null;//关键帧集合,在duration时间内动画的集合,保存了每个时刻改属性对应的值
public static PropertyValuesHolder ofFloat(Property<?, Float> property, float... values) {
return new FloatPropertyValuesHolder(property, values);
}
//内部类
static class FloatPropertyValuesHolder extends PropertyValuesHolder {
// Cache JNI functions to avoid looking them up twice
private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap =
new HashMap<Class, HashMap<String, Long>>();
long mJniSetter;
private FloatProperty mFloatProperty;//float型属性
Keyframes.FloatKeyframes mFloatKeyframes;//动画关键帧,这个是重点
float mFloatAnimatedValue;
//构造函数
//......
public FloatPropertyValuesHolder(Property property, float... values) {
super(property);
//设置目标属性
setFloatValues(values);
if (property instanceof FloatProperty) {
mFloatProperty = (FloatProperty) mProperty;
}
}
@Override
public void setProperty(Property property) {
if (property instanceof FloatProperty) {
mFloatProperty = (FloatProperty) property;
} else {
super.setProperty(property);
}
}
//设置动画的目标值
@Override
public void setFloatValues(float... values) {
//调用父类的方法
super.setFloatValues(values);
//获取动画关键帧
mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
}
//计算当前动画值
@Override
void calculateValue(float fraction) {
mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
}
}
PropertyValuesHolder 是属性和属性值的辅助类,它保存了属性的名称,setter,getter以及属性在 duration 时间段内各个时刻对应的属性数值(mKeyFrameSet)。这样,当执行动画时,动画库只需要根据动画执行时间,到 mKeyFrameSet 中查询这个时刻对应的属性值,然后修改执行动画的对象的目标属性值,连续这个过程即可达到动画效果。
在这个例子中,我们的属性值是 scaleX,目标属性是 0.3f,因此,对应的属性类是 FloatPropertyValuesHolder。
在 setFloatValues(float... values) 方法中,调用的是父类的 super.setFloatValues(values)。然后就获取了动画的关键帧,这有可能计算各个时刻的属性值操作是在父类 PropertyValuesHolder 中的 setFloatValues(float... values) 函数中,我们一起来看看 setFloatValues(float... values) 方法:
public void setFloatValues(float... values) {
mValueType = float.class;
mKeyframes = KeyframeSet.ofFloat(values);
}
可以看到里面又调用了 KeyframeSet.ofFloat(values),继续往下看:
public class KeyframeSet implements Keyframes {
int mNumKeyframes;
Keyframe mFirstKeyframe;
Keyframe mLastKeyframe;
TimeInterpolator mInterpolator; // only used in the 2-keyframe case
List<Keyframe> mKeyframes; // only used when there are not 2 keyframes
TypeEvaluator mEvaluator;
public KeyframeSet(Keyframe... keyframes) {
mNumKeyframes = keyframes.length;
// immutable list
mKeyframes = Arrays.asList(keyframes);
mFirstKeyframe = keyframes[0];
mLastKeyframe = keyframes[mNumKeyframes - 1];
mInterpolator = mLastKeyframe.getInterpolator();
}
public List<Keyframe> getKeyframes() {
return mKeyframes;
}
public static KeyframeSet ofFloat(float... values) {
boolean badValue = false;
int numKeyframes = values.length;
FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
if (Float.isNaN(values[0])) {
badValue = true;
}
} else {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] =
(FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
if (Float.isNaN(values[i])) {
badValue = true;
}
}
}
if (badValue) {
Log.w("Animator", "Bad value (NaN) in float animator");
}
return new FloatKeyframeSet(keyframes);
}
//......
}
关键帧的计算时在 KeyframeSet 中,如果以后设置了 1 个目标值,那么这个值就是最终的值,它的起始值会被默认设置为 0;如果没有以后设置了大于等于 1 个目标值,这些关键帧都会被存储到 KeyframeSet 对象中,设置完关键帧后,我们就会调用 start() 启动动画。
@Override
public void start() {
AnimationHandler.getInstance().autoCancelBasedOn(this);
super.start();
}
这支持调用父类的 start() 方法,它的父类是 ValueAnimator,我们看看 start() 方法:
//ValueAnimator.class
private void start(boolean playBackwards) {
//判断 Looper 是否为 null,这里的 Looper 是 UI 线程中的 Looper。
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
//设置状态
mReversing = playBackwards;
mSelfPulse = !mSuppressSelfPulseRequested;
if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
if (mRepeatCount == INFINITE) {
float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
mSeekFraction = 1 - fraction;
} else {
mSeekFraction = 1 + mRepeatCount - mSeekFraction;
}
}
//启动动画
mStarted = true;
mPaused = false;
mRunning = false;
mAnimationEndRequested = false;
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
addAnimationCallback(0);
//如果无延迟,直接开始执行动画
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
//开始执行动画
startAnimation();
if (mSeekFraction == -1) {
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
//将动画加入到等待队列
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
//动画执行逻辑在 AnimationHandler 回调中
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
//AnimationHandler.class
private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime = new ArrayMap<>();
private final ArrayList<AnimationFrameCallback> mAnimationCallbacks = new ArrayList<>();
private final ArrayList<AnimationFrameCallback> mCommitCallbacks = new ArrayList<>();
private AnimationFrameCallbackProvider mProvider;
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);//无延迟的动画执行队列
}
//延迟动画加入到延迟执行队列中
if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}
而将动画添加到 AnimationFrameCallback 回调集合中之后,在 AnimationFrameCallback 回调中,只有两个方法。
interface AnimationFrameCallback {
//执行当前帧的动画
boolean doAnimationFrame(long frameTime);
//解决有反转情况的动画
void commitAnimationFrame(long frameTime);
}
具体实现
private void doAnimationFrame(long frameTime) {
long currentTime = SystemClock.uptimeMillis();
final int size = mAnimationCallbacks.size();
for (int i = 0; i < size; i++) {
final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
if (callback == null) {
continue;
}
if (isCallbackDue(callback, currentTime)) {
callback.doAnimationFrame(frameTime);
if (mCommitCallbacks.contains(callback)) {
getProvider().postCommitCallback(new Runnable() {
@Override
public void run() {
commitAnimationFrame(callback, getProvider().getFrameTime());
}
});
}
}
}
cleanUpList();
}
private void commitAnimationFrame(AnimationFrameCallback callback, long frameTime) {
if (!mDelayedCallbackStartTime.containsKey(callback) &&
mCommitCallbacks.contains(callback)) {
callback.commitAnimationFrame(frameTime);
mCommitCallbacks.remove(callback);
}
}
而在上面的 startAnimation() 中,
private void startAnimation() {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
System.identityHashCode(this));
}
mAnimationEndRequested = false;
initAnimation();//初始化动画
mRunning = true;
if (mSeekFraction >= 0) {
mOverallFraction = mSeekFraction;
} else {
mOverallFraction = 0f;
}
if (mListeners != null) {
notifyStartListeners();//通知动画回调
}
}
在 initAnimation() 中会遍历 PropertyValuesHolder 数组,从第一帧开始不断调用 init() 开启动画。
PropertyValuesHolder[] mValues;
@CallSuper
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].init();
}
mInitialized = true;
}
}
PropertyValuesHolder # init() 中不断设置估值器的值,
//PropertyValuesHolder.class
void init() {
if (mEvaluator == null) {
mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
(mValueType == Float.class) ? sFloatEvaluator :
null;
}
if (mEvaluator != null) {
mKeyframes.setEvaluator(mEvaluator);
}
}
notifyStartListeners() 中调用 onAnimationStart(this, mReversing) 开始执行动画。
private void notifyStartListeners() {
if (mListeners != null && !mStartListenersCalled) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
tmpListeners.get(i).onAnimationStart(this, mReversing);
}
}
mStartListenersCalled = true;
}
六、总结
策略模式主要用来分离算法,根据相同的行为抽象来做不同的具体策略实现。这个模式很好的演绎了开闭原则,也就是定义抽象,注入不同的实现,从而达到很好的扩展性。
优点
- 结构清晰明了、使用简单直观。
- 耦合度相对而言较低,扩展方便。
- 操作封装也更为彻底,数据更为安全。
缺点
- 随着策略的增加,子类也会变得繁多。
网友评论