Tomcat C语言 线程 微信小程序实战教程 存量客户 delphi audio dll Draggabilly vue网页 sketch up教程 nodejs教程视频 java后台框架 jq去空格 mysql数据库名称 java两个数组合并 ipex接口 nginx默认端口号 mysql修改字段值 math保留两位小数 mysql自然连接 python加注释 python功能 pythonapi python写文件 python链接mysql数据库 windows安装python环境 python排序 java接口的使用 java基本数据结构 sql实例 局域网助手 mathcad下载 黑帮之地修改器 快点蛆虫成就单刷 c语言指数函数 ram容量是什么意思 凤凰刷机 dxsetup 脚本列表
当前位置: 首页 > 学习教程  > 编程语言

第7章 时势造英雄——策略模式

2020/12/28 19:54:53 文章标签:

第7章 时势造英雄——策略模式7.1 策略模式介绍7.2 策略模式的定义7.3 策略模式的使用场景7.4 策略模式的UML类图7.5 策略模式的实现7.6 Android源码中的策略模式实现7.6.1 时间插值器7.6.2 动画中的时间插值器7.7 深入属性动画7.7.1 属性动画体系的总体设计7.7.2 属性动画的核…

第7章 时势造英雄——策略模式

  • 7.1 策略模式介绍
  • 7.2 策略模式的定义
  • 7.3 策略模式的使用场景
  • 7.4 策略模式的UML类图
  • 7.5 策略模式的实现
  • 7.6 Android源码中的策略模式实现
    • 7.6.1 时间插值器
    • 7.6.2 动画中的时间插值器
  • 7.7 深入属性动画
    • 7.7.1 属性动画体系的总体设计
    • 7.7.2 属性动画的核心类介绍
    • 7.7.3 基本使用
    • 7.7.4 流程图
    • 7.7.5 详细设计
    • 7.7.6 核心原理分析
  • 7.8 策略模式实战应用
  • 7.9 小结

7.1 策略模式介绍

在软件开发中也常常遇到这样的情况:实现某一个功能可以有多种算法或者策略,我们根据实际情况选择不同的算法或者策略来完成该功能。例如,排序算法,可以使用插入排序、归并排序、 冒泡排序等。

针对这种情况,一种常规的方法是将多种算法写在一个类中。例如,需要提供多种排序算法,可以将这些算法写到一个类中,每一个方法对应一个具体的排序算法;当然,也可以将这些排序算法封装在一个统一的方法中,通过if…else…或者case等条件判断语句来选择具体的算法。这两种实现方法我们都可以称为硬编码。然而,当很多个算法集中在一个类中时,这个类就会变得臃肿,这个类的维护成本会变高,在维护时也更容易引发错误。如果我们需要增加一种新的排序算法,需要修改封装算法类的源代码。这就明显违反了我们上面所说的OCP原则和单一职责原则。

如果将这些算法或者策略抽象出来,提供一个统一的接口,不同的算法或者策略有不同的实现类,这样在程序客户端就可以通过注入不同的实现对象来实现算法或者策略的动态替换,这种模式的可扩展性、可维护性也就更高,也就是我们本章要说的策略模式。

7.2 策略模式的定义

策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化

7.3 策略模式的使用场景

  • 针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。
  • 需要安全地封装多种同一类型的操作时。
  • 出现同一抽象类有多个子类,而又需要使用if-else或者switch-case来选择具体子类时。

7.4 策略模式的UML类图

UML类图如图7-1所示。
在这里插入图片描述
图7-1中的角色介绍:

  • Context——用来操作策略的上下文环境;
  • Stragety——策略的抽象;
  • ConcreteStragetyAConcreteStragetyB——具体的策略实现。

7.5 策略模式的实现

通常如果一个问题有多个解决方案时,最简单的方式就是利用if-else或者switch-case方式根据不同的情景选择不同的解决方案,但这种简单的方案问题太多,例如耦合性太高、代码臃肿、难以维护等。但是,如果解决方案中包括大量的处理逻辑需要封装,或者处理方式变动较大的时候则就显得混乱、复杂,当需要增加一种方案时就需要修改类中的代码。怎么对于修改不关闭?不是说好的要遵循开闭原则吗?

是的,if-else这种方法确实不会遵循开闭原则,而应对这种情况策略模式就能很好地解决这类问题,它将各种方案分离开来,让程序客户端根据具体的需求来动态地选择不同的策略方案。

下面我们以在北京坐公共交通工具的费用计算来演示一个简单示例。在2014年12月28号之后,北京提高公交价格,不再是单一票价制,而是分段计价,也就是说乘坐的距离越远,价格越高。

显然,公交车和地铁的价格计算方式是不一样的,但是,我们的示例中是需要计算乘不同出行工具的成本,下面是我们的第一个版本的代码。
在这里插入图片描述在这里插入图片描述
PriceCalculator类很明显的问题就是并不是单一职责,首先它承担了计算公交车和地铁乘坐价格的职责;另一个问题就是通过if-else的形式来判断使用哪种计算形式。当我们增加一种出行方式,如出租车,那么我们就需要在PriceCalculator中增加一个方法来计算出租车出行的价格,并且在calculatePrice(int km, int type)函数中增加一个判断,代码添加后大致如下。
在这里插入图片描述在这里插入图片描述
此时的代码已经比较混乱,各种if-else语句缠绕其中。当价格的计算方法变化时,需要直接修改这个类中的代码,那么很可能有一段代码是其他几个计算方法所共同使用的,这就容易引入错误。 另外,在增加出行方式时,我们又需要在calculatePrice中添加if-else,此时很有可能就是复制上一个if-else,然后手动进行修改,手动复制代码也是最容易引入错误的做法之一。这类代码必然是难以应对变化的,它会使得代码变得越来越臃肿,难以维护,我们解决这类问题的手法也就是本章谈到的策略模式。当然,我们也可以把每种计算方法独立成一个函数,然后外部调用对应的方法即可,但这也是另一种耦合的形式,对于可变性较大的算法族来说还是不适合使用这种方式。

下面我们对上述示例用策略模式进行重构。

首先我们需要定义一个抽象的价格计算接口,这里命名为CalculateStrategy,具体代码如下。
在这里插入图片描述
对于每一种出行方式我们都有一个独立的计算策略类,这些策略类都实现了CalculateStrategy接口,例如下面是公交车和地铁的计算策略类。
在这里插入图片描述在这里插入图片描述
我们再创建一个扮演Context角色的类,这里将它命名为TranficCalculator,具体代码如下。
在这里插入图片描述
经过上述的重构之后,去掉了各种各样的if-else语句,结构变得也很清晰,其结构如图7-2
在这里插入图片描述
这种方案在隐藏实现的同时,可扩展性变得很强,例如,当我们需要增加出租车的计算策略时, 只需要添加一个出租车计算策略类,然后将该策略设置给TranficCalculator,最好直接通过 TranficCalculator对象的计算方法即可。示例代码如下。
在这里插入图片描述
将策略注入到TranficCalculator中。
在这里插入图片描述
通过上述示例我们可以清晰地看出二者的区别所在。前者通过if-else来解决问题,虽然实现较为简单,类型层级单一,但暴露的问题非常明显,即代码臃肿,逻辑复杂,难以升级和维护,没有结构可言;后者则是通过建立抽象,将不同的策略构建成一个具体的策略实现,通过不同的策略实现算法替换。在简化逻辑、结构的同时,增强了系统的可读性、稳定性、可扩展性,这对于较为复杂的业务逻辑显得更为直观,扩展也更为方便。

7.6 Android源码中的策略模式实现

随着技术的发展,工程师们已经越来越重视用户体验、用户交互。因此,动画成了很多应用中必不可少的部分,一个简单的引导页面也要做成动画的效果,一个按钮的隐藏也需要加入淡入淡出的动画效果。动画的实现原理就是在短时间内快速地进行画面切换,这个切换频率需要达到人眼感觉不出卡顿,例如标准的电影是24帧/秒。在比较流畅时,Android上的动画能够达到60帧/秒,人眼基本看不出间隔,所以,在我们看到这个动画就非常流畅。

单纯是动画还不足以满足我们的需求,在动画执行的过程中,我们还需要一些动态效果,这有点类似于电影的慢镜头,有的时候我们需要它慢一点,有的时候需要快一点,这样动画也变得灵动起来。这些动态效果就是通过插值器(Timelntcrpolator)实现的,我们只需要对Animation对象设置不同的插值器就可以实现不同的动态效果。

7.6.1 时间插值器

在开始之前我们需要先了解动画中的Timelnterpolator,也就是时间插值器。它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有线性插值器(Linearlnterpolator) 用于匀速动画;加速减速插值器(AccelerateDeceleratelnterpolator)用于起始时动画加速,结尾时动画减速;减速插值器(Deccleratclnterpolator)用于随着时间的推移动画越来越慢,即减速动画。这些插值器就是策略模型的典型运用。可能这么说还有点抽象,我们看如图7-3和图7-4所示。
在这里插入图片描述
图7-3中是一个匀速动画,其采用了线性插值器,在30ms内,View的x属性实现从0到30的变换,由于是匀速动画,因此,在相同的时间间隔内x的变化范围是一致的,例如,在时间t=0时,x的坐标为0;当t=10ms时,x=10。也就是说当在时间间隔为10ms时,x的变化范围都是10,所以,形成了速度不变的动画。再看图7-4中的加速动画,在相同的时间间隔内,时间越靠后速度变得越快,也就是说在同等的时间间隔内x的变化范围越来越大,也就形成了加速动画的效果。

当然插值器并不是一个“人在战斗”,它还有一位“好基友”——TypeEvaluator,也就是类型估值器。它的作用是根据当前属性改变的百分比来计算改变后的属性值,也就是说TypeEvaluator计算得到的才是属性的值。时间插值器计算得到当前时间点的时间流逝百分比,TypeEvaluator根据这个百分比、属性起始值、目标值来计算出当前时刻该属性的值,最后这个值被设置给View,不断地重复这个过程就形成了动画。系统预置的有整型属性(IntEvaluator)、浮点型属性(FloatEvaluator)和Color属性(ArgbEvaluator)。

7.6.2 动画中的时间插值器

时间插值器运用于动画中,而动画作用于View上。因此,需要从View上切入到动画,再从动画中寻找插值器的身影。当我们要对某个View执行某个动画时,我们首先会构建一个Animation对象,然后调用View的startAnimation(Animation animation)方法,此时动画就启动了。
在这里插入图片描述
startAnimation中首先设置了动画的起始时间,然后将该动画设置到该View中,最后再向ViewGroup请求刷新视图,随后ViewGroup就会调用dispatchDraw方法对这个View所在的区域进行重绘对于某个View的重绘最终会调用ViewGroup中的drawChild(Canvas canvas, View child, long drawingTime)方法,我们看看这个函数。
在这里插入图片描述
只是进行了一个转发,我们再看一下View的draw(Canvas canvas, ViewGroup parent, long drawingTime)方法中是如何调用Animation的
在这里插入图片描述
在这里插入图片描述
可以看出在父类调用View的draw方法中,会先判断是否设置了清除动画的标记,然后再获取该View动画的信息,如果设置了动画,就会调用View中的drawAnimation方法,具体代码如下。
在这里插入图片描述
在drawAnimation中主要的操作是动画初始化、动画操作、界面刷新在drawAnimation中首先会判断动画是否进行了初始化,如果未初始化则先初始化,然后调用动画监听器的onStart函数动画的具体实现是通过Animation中的 getTransformation(long currentTime, Transformation outTransformation,float scale)方法,我们看看相关代码。
在这里插入图片描述
在上面的方法中,主要是获取缩放系数和调用Animation.getTransformation(long currentTime,Transformation outTransformation)来计算和应用动画效果
在这里插入图片描述
在上述getTransformation函数中,首先会获取己经流逝的动画执行时间百分比,然后再通过插值器来重新计算这个百分比,也就是调用了插值器的getlnterpolation(float input)方法来获取当前的时间百分比,并且以此来计算当前动画的属性值,例如,线性插值器的输出百分比就是输入的百分比,不做任何处理,使得动画的速率不会发生变化。
在这里插入图片描述
我们再看看加速插值器的代码。
在这里插入图片描述在这里插入图片描述
我们看到,在默认情况下,Acceleratelnterpolator的getlnterpolation方法中会对input进行乘方操作,这个input就是流逝的时间百分比,input的取值为0.0f〜1.0f,当input逐渐增大时,input*input的变化范围越来越大,使得动画的属性值在同一时间段内的变化范围更大,从而实现了加速动画的效果。例如,动画执行总时间为1秒,使用的是Acceleratelnterpolator插值器,在动画执行了100毫秒时百分比为0.1,那么此时通过插值器的计算,百分比成了0.01;又过了100毫秒,此时百分比为0.2,经过插值器的计算变为0.04;在执行到300毫秒时,百分比经过插值器计算会变为0.09。我们看到,在相同的100毫秒内百分比的变化频率逐渐增大,100到200毫秒之间的变化值为0.03, 200到300毫秒之间的变化值则是0.05,这样在同一时间段内百分比差距越来越大,也就形成了加速的效果。

在调用了插值器的getlnterpolation方法之后,会继续调用动画类的applyTransformation方法将属性应用到对应的对象中。applyTransformation在Animation基类中是空实现,那我们选择缩放动 画(ScaleAnimation)来看看它的具体实现。
在这里插入图片描述
当执行完applyTransformation之后,View的属性就发生了变化,不断地重复这个过程,动画就随之产生了。

这个过程中,插值器扮演了很重要的角色。它将动画的速率计算封装到一个抽象中,也就是Interpolator中,该接口中只有一个getlnterpolation(float input)方法,通过这个方法来修改动画的流逝时间百分比,以此达到动画的加速、减速等效果,结构如图7-5所示。
在这里插入图片描述
Interpolator就是这个计算策略的抽象,LinearInterpoIator、Cyclelnteipolator等插值器就是具体的实现策略,通过注入不同的插值器实现不同的动态效果

7.7 深入属性动画

在 Android 3.0 之前,Android 提供了几种动画类型:View Animation、Drawable Animation、Property Animation。View Animation相当简单,不过只能支持简单的缩放、平移、旋转、透明度这几个基本的动画,且有一定的局限性。例如:希望View有一个颜色的切换动画;希望可以使用3D旋转动画;希望当动画停止时,View的位置就是当前的位置。这些View Animation都无法做到。 因此,Google在Android 3.0提供了属性动画,这使得动画系统变得极其强大。考虑到很多应用会兼容Android 3.0以下的设备,在这种情况下就需要兼容Android 3.0以下的动画库,Nineoldanimations就是这么一个兼容库,它使得Android 3.0以下的设备也能够使用属性动画。NineOldAnimations通过判断系统版本来选择实现属性动画的方式,它的API与Android自带的接口一致,因此,在这里我们选择适用性更广的NineOldAnimations作为分析对象。

7.7.1 属性动画体系的总体设计

总体设计如图7-6所示。
在这里插入图片描述
图7-6是属性动画体系的整体设计,Animator通过PropertyValuesHolder来更新对象的目标属性,如果用户没有设置目标属性的Property对象,那么会通过反射的形式调用目标属性的setter方法来更新属性值;否则,通过Property的set方法来设置属性值。这个属性值则通过KeyFrameSet的计算得到,而KeyFrameSet又是通过时间插值器和类型估值器来计算,在动画执行过程中不断地计算当前时刻目标属性的值,然后更新属性值来达到动画效果

7.7.2 属性动画的核心类介绍

在进行下一步的分析之前,我们先来了解一下属性动画中一些核心的类以及它们的作用。

  • ValueAnimator:该类是Animator的子类,实现了动画的整个处理逻辑,也是属性动画最为
    核心的类。
  • ObjectAnimator:对象属性动画的操作类,继承自ValueAnimator,通过该类使用动画的形式 操作对象的属性。
  • Timelnterpolator:时间插值器,它的作用是根据时间流逝的百分比来计算出当前属性值改变 的百分比,系统预置的有线性插值器(Linearlnterpolator)、加速减速插值器(AccelerateDecelerateInterpolator)和减速插值器(Deceleratelnterpolator)等。
  • TypeEvaluator:TypeEvaluator的中文翻译为类型估值算法,它的作用是根据当前属性改变的百分比来计算改变后的属性值,系统预置的有针对整型属性(IntEvaluator)、针对浮点型属性(FloatEvaluator)和针对Color属性(ArgbEvaluator)。
  • Property:属性对象,主要是定义了属性的set和get方法。
  • PropertyValuesHolder: PropertyValuesHoIder是持有目标属性Property、setter和getter方法、以及关键帧集合的类。
  • KeyframeSet:存储一个动画的关键帧集合。
  • AnimationProxy:在Android 3.0以下使用View的属性动画的辅助类。

7.7.3 基本使用

示例1:改变一个对象(myObject)的translationY属性,让其沿着y轴向上平移一段距 离,该动画在默认时间内完成,动画的完成时间是可以定义的,想要更灵活的效果还可以定义插值器和估值算法,但是,一般来说我们不需要自定义,系统已经预置了一些,能够满足常用的动画。
在这里插入图片描述
示例2:改变一个对象的背景色属性,典型的情形是改变View的背景色,下面的动画可以让背景色在3s内实现从0xFFFF8080到0xFF8080FF的渐变,并且动画会无限循环而且会有反转的效果。
在这里插入图片描述
示例3:动画集合,5s内对View的旋转、平移、缩放和透明度都进行了改变。
在这里插入图片描述
在这里插入图片描述
示例4:下面是个简单的调用方式,其animate方法是属性动画特有的,动画持续时间为2s, 在y轴上旋转720°,并且平移到(100,100)的位置。
在这里插入图片描述

7.7.4 流程图

  1. ValueAnimator流程图(如图7-7所示)
    在这里插入图片描述
  2. ObjectAnimator流程图(如图7-8所示)
    在这里插入图片描述

7.7.5 详细设计

详细设计如图7-9所示。

7.7.6 核心原理分析

我们以一个具体的示例作为分析的入口,例如,要对某个View的工轴缩放到原来的0.3倍,动画执行时间为1s,具体代码如下。
在这里插入图片描述
在这里插入图片描述
它的背后是什么原理,在Android 3.0以上和以下的系统版本中它的执行代码有什么不一样, 这就是我们本节要研究的。首先我们从它的入口,即ObjectAnimator入手。
在这里插入图片描述
在ObjectAnimator的ofFdddddddddloat函数中会先构建属性动画对象,然后根据设置的属性值来初始化各个时间段对应的属性值,这个属性值就是values参数,它是一个可变参数,如果是一个参数,那么该参数为目标值;如果是两个参数,那么一个是起始值,另一个是目标值。我们先看看setFloatValues函数的实现。
在这里插入图片描述在这里插入图片描述
我们看到setFloatValues出现了一个类PropertyValuesHolder,这个类是该动画库的一个核心类之一,它的作用就是保存属性的名称和它的setter,getter方法,以及它的目标值。我们看看它的一些相关函数。
在这里插入图片描述在这里插入图片描述
该类是属性和属性值的辅助类,它保存了属性的名称、setter、getter,以及该属性在duration时间段内各个时刻对应属性数值(mKeyframeSet)。这样,当执行动画时,动画库只需要根据动画的执行时间,到mKeyframeSet中查询这个时刻对应的属性值,然后修改执行动画的对象的目标属性值,连续这个过程即可达到动画的效果。在这个例子中,我们的属性是scaleX,目标属性值是0.3f。因此,对应的属性类为FloatPropertyValuesHolder,还有一种是 IntPropertyValuesHolder,这都很好理解,就不多介绍了。

这个例子中,我们会调用注释2中的构造函数,该函数会调用setFloatValues来设置动画的目标值,然后会到达注释3处的函数,在这个函数中调用了父类中对应的方法,然后就获取到了动画的关键帧,这有可能计算各个时刻的属性值的操作放在了父类(即PropertyValuesHolder)的setFloatValues函数中。我们一起来看看setFloatValues实现。
在这里插入图片描述
可以看到该函数又调用了KeyFrameSet的ofFloat方法,继续看下面的程序。
在这里插入图片描述
在这里插入图片描述
我们看到关键帧的计算就在ofFloat函数中如果用户设置了一个目标值,那么这个值就是最终的值,它的起始值会被默认设置为0;如果用户设置了大于等于1个目标值,这些关键帧都会被存储到KetFrameSet对象中设置完关键帧之后,我们就会调用start()方法启动动画了。我们看看ObjectAnimator的start()方法。
在这里插入图片描述
这只是调用了父类的start()方法,它的父类是ValueAnimator,我们看看该类的start()方法。
在这里插入图片描述
我们看到,启动动画的操作设置了一些基本参数,然后把自己添加到待执行的动画列表(sPendingAnimations)中,通过AnimationHandler发送了一个ANIMATION_START消息,真是哪里都有Handler的事啊!我们继续看下面的程序。
在这里插入图片描述在这里插入图片描述在这里插入图片描述
该AnimationHandler的作用简单来说就是处理动画的启动和各个关键帧动画的执行,在ANIMATION_START阶段,将sPendingAnimations中的动画取出,并且根据当前时间和动画的执行时间对比,将一些需要延迟的动画放到delay列表中,将不需要延迟的动画立即执行动画开始执行后,并没有break出来,而是直接到ANIMATION_FRAME阶段,在这个阶段,会遍历已经ready的动画列表和delay的动画列表,并且根据当前时间与动画的执行时间进行比对,如果到了动画的执行时间,那么执行动画,然后才是遍历sAnimations中的动画,其中的动画就是立即可以执行的动画,最后,如果动画列表中的动画没有被执行完成,则会发送一个ACTION_FRAME消息,使得它又进入到ANIMATION_FRAME这个处理流程,然后又是重复上述的动画执行过程,直到动画全部执行完毕。sAnimations等列表的声明如下。
在这里插入图片描述
那么,我们的动画是什么时候添加到这个sAnimations列表中的呢

从程序中看到,在ANIMATION_START,对于没有延时的动画,调用了一个anim.startAnimation()方法,我们看看它做了些什么操作。
在这里插入图片描述
从程序中可以看到,进行动画初始化之后,在startAnimation中确实将该动画添加到sAnimations中了,并且执行了相应的回调方法

我们看看ObjectAnimator中的initAnimation函数到底做了些什么
在这里插入图片描述
我们看到initAnimation中有个很长的if判断语句,这个判断语句就是对系统版本进行判断的地方,如果系统版本小于API11,那么,AnimatorProxy.NEEDS_PROXY为真。我们看看它的声明。
在这里插入图片描述
可以看到当Android SDK的版本号小于HONEYCOMB,NEEDS_PROXY就为真,HONEYCOMB就是系统3.0的代号,即API11的代号。如果NEEDS_PROXY为真,且属性是合法的属性,那么就会进入到 if 语句中,即会调用 setProperty(PROXY_PROPERTIES.get(mPropertyName))。在这个例子中,我们的属性是scaleX,可以看到,PROXY_PROPERTIES会get一个key为mPropertyName的值,这个mPropertyName就是我们传递进来的scaleX属性。我们看看PROXY_PROPERTIES是什么。
在这里插入图片描述
PROXY PROPERTIES是一个HashMap,我们传进来的是scaleX,那么我们get得到的就应该是PreHoneycombCompat.SCALEX,继续看它具体是什么。
在这里插入图片描述在这里插入图片描述
这原来就是包装了一下View,并且在setValue时通过它的包装对它调用setScaleX函数。那么 AnimatorProxy又是什么呢?其实它就是低于API 11时对View进行包装,从而通过矩阵来达到View的缩放、平移、翻转等效果的动画代理类。看看下面的部分代码。
在这里插入图片描述在这里插入图片描述
很明显,就是通过矩阵变换达到动画的效果,关于矩阵的使用和原理请参考其他资料,这里不再赘述。

在低于API 11的版本中,当目标属性是scaleX时会调用AnimationProxy的setScaleX函数实现缩放动画效果,这个Proxy的动画的duration为0,也就是动画会立即完成。每次需要更新属性时,都会调用 FloatProperty的setValue方法,而setValue又调用 AnimationProxy的setScaleX()方法,setScaleX在低于API 11时通过矩阵变换来完成缩放效果。在动画执行周期内不断地执行这个setScaleX函数就达到了动画效果。

当大于等于API 11时它又是怎样的呢

我们看到,在initAnimation中,还有一个地方需要注意。
在这里插入图片描述
看一下注释1处的程序,这里会初始化属性的setter和getter方法。通过反射调用这些函数就可以修改其属性值了。

那么,在什么时候会调用相应的方法来更新属性值呢?更新属性值,我们想到的应该是在ANIMATION_FRAME中进行,那里就是循环调用动画,直到动画结束,因此,那应该是更新属性 的地方。我们注意到一个地方,具体程序如下。
在这里插入图片描述
这里有一个关键的函数,即animationFrame是个关键函数。它是根据当前时间计算动画在当前时刻的属性值,具体代码如下。
在这里插入图片描述
通过时间百分比计算得到了fraction之后调用了 animateValue函数,我们看看ObjectAnimator中的animateValue函数实现。
在这里插入图片描述
在本实例中,PropertyValuesHolder为FloatPropertyValuesHolder类型,因此,mValues也就是FloatPropertyValuesHolder类型,继续深入 FloatPropertyValuesHolder中的setAnimatedValue 方法
在这里插入图片描述
可以看到,这种形式的更新方式主要是通过setValue和反射。

可以看到,小于API 11时会设置Property,对于本例中的scaleX,这个Property为FloatProperty,因此,会在setAnimationValue中执行注释1的代码,最后使用矩阵变换的形式变换View;如果大于等于API 11则会调用目标对象的setter方法来更新属性值,例如scaleX对应的setter方法为setScaleX。NineOldAnimations通过这两种形式来实现属性动画在低版本系统的兼容,保证了UI效果的一致性。

7.8 策略模式实战应用

经过小民的几次重构,他的ImageLoader逐渐稳定起来,在灵活性、可定制化上都有了一定的成绩。小民的编程能力也有了很大的提升,在工作上越来越得心应手。这不,在完成了主管分配的工作之后,小民悠哉地坐在办公椅上,喝了口茶,仰望着天空,顿时觉得今天的阳光特别明媚。

休息了半晌,小民回到计算机傍,发现有ImageLoader的用户发来了邮件,大致的意思是让小民的ImageLoader不仅能够实现顺序加载图片,也能够逆序加载。什么意思呢?在我们加载图片时,加载请求会被封装成一个Request对象添加到请求队列中,ImageLoader会为每个请求分配一个序列号,越晚加入的请求序列号值越大,默认情况下:ImageLoader会按照先后顺序加载图片。但是现实中,我们可能需要最后添加到队列的请求先被执行,例如,我们在滚动ListView时,在最后一项肯定是最晚被加载的,此时它里面的图片加载请求也就是最晚被添加到请求队列。如果上面有100个加载请求,就需要等到这前100个请求加载完之后才会加载当前显示在我们面前的最后的一项,这必然是不符合情理的。如图7-10所示,Item5是当前显示在手机屏幕上的,但是是最后一个添加到请求列表的,Item1以上的已经变得不可见,它是早于 Item5加入到请求列表。
在这里插入图片描述
此时,我们的需求是在屏幕上显示的Item View的图片优先被加载,因此,我们就需要ImageLoader支持从请求队列的尾部开始加载。

“策略模式! ”小民看完用户需求之后脱口而出。在经历了上几次的失败之后,小民痛下决心学习设计模式,并且加以实践,对于一些常用的模式已是熟记在心。既然已经知道了解决方案,那我们直接看看小民的实现吧。
在这里插入图片描述在这里插入图片描述
首先定义了一个LoadPolicy接口,在这个接口中有一个compare方法,用来对比两个请求。小民默认实现了顺序加载、逆序加载两个策略,因为每个请求都有一个序列号,这个序列号以递增的形式增长,越晚加入队列的请求的序列号越大,而我们的请求列队是优先级队列,因此,我们需要在图片加载请求类中实现comparable接口,以此实现对这些请求的排序处理。我们看看这部分代码。
在这里插入图片描述
就这么简单,用户在配置ImageLoader时可以设置加载策略,这个策略会被设置给每个图片加载请求对象,具体代码如下。
在这里插入图片描述
图片加载请求对象的compare方法通过转发给mLoadPolicy处理来实现这些请求在请求队列中的排序,通过修改排序策略来实现修改图片加载顺序的效果。不同的策略有不同的排序方式,策略的可替换性又一次保证了ImageLoader的可扩展性。

7.9 小结

策略模式主要用来分离算法,在相同的行为抽象下有不同的具体实现策略。这个模式很好地演示了开闭原则,也就是定义抽象,注入不同的实现,从而达到很好的可扩展性。

优点

  • 结构清晰明了、使用简单直观;
  • 耦合度相对而言较低,扩展方便;
  • 操作封装也更为彻底,数据更为安全。

缺点

  • 随着策略的增加,子类也会变得繁多。

本文链接: http://www.dtmao.cc/news_show_550401.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?