01-16-13 策略模式 - Animation的插值器策略模式定义策略模式Strategy Pattern属于行为型设计模式定义一族算法将每个算法封装起来并使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户端。解决的问题当一个系统需要在多种算法中动态选择时最直接的做法是在客户端代码中使用 if-else 或 switch-case 分支。但随着算法种类增多分支逻辑会变得臃肿且难以扩展——每新增一种算法就要修改已有代码违背开闭原则。策略模式将每种算法抽取为独立的类客户端通过持有策略接口的引用来调用算法新增算法只需新增类无需修改已有代码。类图结构┌────────────────┐ ┌──────────────────┐ │ Context │ │ Strategy │ │────────────────│ │──────────────────│ │ -strategy: S │────────│ algorithm() │ │ setStrategy() │ └────────┬─────────┘ │ execute() │ │ └────────────────┘ ┌────────┴─────────┐ │ │ ┌────────┴───┐ ┌─────────┴──────┐ │ConcreteStratA│ │ConcreteStratB │ │algorithm() │ │algorithm() │ └─────────────┘ └────────────────┘三个核心角色Context上下文持有策略引用将算法的调用委托给策略对象Strategy策略接口定义算法的公共接口ConcreteStrategy具体策略实现具体的算法逻辑Android源码中的实现案例一Animation 的 Interpolator 插值器Android 动画系统通过 Interpolator插值器来控制动画的速率变化曲线。动画引擎在每一帧计算当前的时间进度0.0 到 1.0 的线性值然后将这个值传给 Interpolator由 Interpolator 返回变换后的进度值。不同的 Interpolator 产生不同的动画效果匀速、加速、减速、弹性等。源码路径frameworks/base/core/java/android/animation/TimeInterpolator.javaframeworks/base/core/java/android/view/animation/LinearInterpolator.javaframeworks/base/core/java/android/view/animation/AccelerateInterpolator.javaframeworks/base/core/java/android/view/animation/Animation.java策略接口// frameworks/base/core/java/android/animation/TimeInterpolator.javapublicinterfaceTimeInterpolator{/** * 将时间进度映射为动画进度 * param input 时间进度范围 [0.0, 1.0] * return 变换后的动画进度 */floatgetInterpolation(floatinput);}具体策略实现LinearInterpolator——匀速运动// frameworks/base/core/java/android/view/animation/LinearInterpolator.javapublicclassLinearInterpolatorextendsBaseInterpolatorimplementsNativeInterpolator{publicfloatgetInterpolation(floatinput){returninput;// 输入即输出匀速运动}}AccelerateInterpolator——加速运动// frameworks/base/core/java/android/view/animation/AccelerateInterpolator.javapublicclassAccelerateInterpolatorextendsBaseInterpolatorimplementsNativeInterpolator{privatefinalfloatmFactor;privatefinaldoublemDoubleFactor;publicAccelerateInterpolator(floatfactor){mFactorfactor;mDoubleFactor2*factor;}publicfloatgetInterpolation(floatinput){// factor1 时返回 input^2二次方加速factor 越大加速越陡峭if(mFactor1.0f){returninput*input;}else{return(float)Math.pow(input,mDoubleFactor);}}}DecelerateInterpolator——减速运动// frameworks/base/core/java/android/view/animation/DecelerateInterpolator.javapublicclassDecelerateInterpolatorextendsBaseInterpolatorimplementsNativeInterpolator{privatefloatmFactor1.0f;publicfloatgetInterpolation(floatinput){if(mFactor1.0f){// 1 - (1-input)^2起初快速变化接近终点时放缓return(float)(1.0f-(1.0f-input)*(1.0f-input));}else{return(float)(1.0f-Math.pow((1.0f-input),2*mFactor));}}}AccelerateDecelerateInterpolator——先加速后减速// frameworks/base/core/java/android/view/animation/AccelerateDecelerateInterpolator.javapublicclassAccelerateDecelerateInterpolatorextendsBaseInterpolator{publicfloatgetInterpolation(floatinput){// 余弦函数变换在动画中间达到最大速度return(float)(Math.cos((input1)*Math.PI)/2.0f)0.5f;}}OvershootInterpolator——超越终点后回弹// frameworks/base/core/java/android/view/animation/OvershootInterpolator.javapublicclassOvershootInterpolatorextendsBaseInterpolator{privatefinalfloatmTension;// 张力系数默认 2.0publicfloatgetInterpolation(floatt){t-1.0f;returnt*t*((mTension1)*tmTension)1.0f;}}Context 角色——Animation 如何使用插值器// frameworks/base/core/java/android/view/animation/Animation.javapublicabstractclassAnimationimplementsCloneable{privateInterpolatormInterpolator;publicvoidsetInterpolator(Interpolatori){mInterpolatori;}publicbooleangetTransformation(longcurrentTime,TransformationoutTransformation){// 计算线性时间进度floatnormalizedTime((float)(currentTime-mStartTime))/(float)mDuration;normalizedTimeMath.max(Math.min(normalizedTime,1.0f),0.0f);// 关键一行通过插值器将线性时间进度转换为动画进度finalfloatinterpolatedTimemInterpolator.getInterpolation(normalizedTime);// 子类根据 interpolatedTime 计算具体变换透明度、位移、缩放等applyTransformation(interpolatedTime,outTransformation);returnnormalizedTime1.0f;}}getTransformation()的核心就是mInterpolator.getInterpolation(normalizedTime)这一行。Animation 自身不关心速率变化逻辑替换插值器就能改变动画效果。案例二RecyclerView.LayoutManager 布局策略RecyclerView 将布局逻辑完全委托给 LayoutManager不同的 LayoutManager 实现不同的布局策略。源码路径androidx/recyclerview/widget/RecyclerView.javaandroidx/recyclerview/widget/LinearLayoutManager.javaandroidx/recyclerview/widget/GridLayoutManager.java// androidx/recyclerview/widget/RecyclerView.javapublicclassRecyclerViewextendsViewGroup{privateLayoutManagermLayout;publicvoidsetLayoutManager(LayoutManagerlayout){if(layoutmLayout)return;stopScroll();if(mLayout!null){mLayout.removeAndRecycleAllViews(mRecycler);mLayout.mRecyclerViewnull;}mLayoutlayout;if(layout!null){layout.mRecyclerViewthis;}requestLayout();}OverrideprotectedvoidonLayout(booleanchanged,intl,intt,intr,intb){dispatchLayout();// 内部调用 mLayout.onLayoutChildren()}// 策略接口抽象类publicabstractstaticclassLayoutManager{publicvoidonLayoutChildren(Recyclerrecycler,Statestate){// 子类实现具体布局算法}publicbooleancanScrollHorizontally(){returnfalse;}publicbooleancanScrollVertically(){returnfalse;}}}三种内置布局策略的差异LayoutManager布局方式滚动方向典型场景LinearLayoutManager单行/单列线性排列水平或垂直聊天列表、设置页GridLayoutManager网格排列每行固定列数水平或垂直图片墙、应用列表StaggeredGridLayoutManager瀑布流列高度不等水平或垂直商品瀑布流、Pinterest风格只需一行recyclerView.layoutManager GridLayoutManager(this, 3)就能将线性列表切换为三列网格RecyclerView 的缓存、动画、触摸处理完全不受影响。案例三Comparator 排序策略Java 集合框架中的排序通过 Comparator 接口实现策略模式。源码路径libcore/ojluni/src/main/java/java/util/Arrays.java// java/util/Comparator.javaFunctionalInterfacepublicinterfaceComparatorT{intcompare(To1,To2);}// java/util/Arrays.javapublicstaticTvoidsort(T[]a,Comparator?superTc){if(cnull){sort(a);// 自然排序}else{// TimSort 排序算法固定比较逻辑由 Comparator 策略决定TimSort.sort(a,0,a.length,c,null,0,0);}}排序算法TimSort不变比较规则Comparator可替换。同样的数据传入不同的 Comparator 就能得到不同的排序结果。源码设计分析为什么 Android 在动画系统中选择策略模式算法独立性动画的运动方式匀速、加速、弹性与变换内容透明度、位移、缩放是两个正交的维度。策略模式将二者解耦——Interpolator 管怎么动Animation 子类管动什么两个维度自由组合避免了类爆炸。XML 可配置策略模式使得 Interpolator 可以在 XML 中声明setandroid:interpolatorandroid:anim/accelerate_interpolatoralphaandroid:fromAlpha0android:toAlpha1android:duration300//setLayoutInflater 根据属性值反射创建对应的 Interpolator 实例并注入 Animation。不用策略模式这种声明式配置无法实现。Native 加速每个 Interpolator 都实现了NativeInterpolator接口。Android 的 RenderThread 在硬件加速动画时调用createNativeInterpolator()获取 native 层指针直接在 native 代码中计算插值避免跨层调用开销。策略模式的接口抽象使得这种优化可以透明进行。优缺点权衡优点算法自由切换且不修改客户端代码避免多重条件判断扩展性好新增策略只需新增类策略对象可被多个 Context 共享复用。缺点策略类数量增多Android 内置十余种 Interpolator客户端需了解各策略区别策略间共享逻辑可能重复Android 通过 BaseInterpolator 基类缓解。实战应用场景一网络请求重试策略不同类型的网络错误需要不同的重试策略interfaceRetryStrategy{fungetRetryDelay(attempt:Int,error:Throwable):Long// 返回 -1 表示不再重试}classFixedDelayRetry(privatevalmaxRetries:Int3,privatevaldelayMs:Long1000):RetryStrategy{overridefungetRetryDelay(attempt:Int,error:Throwable):Longif(attemptmaxRetries)-1elsedelayMs}classExponentialBackoffRetry(privatevalmaxRetries:Int5,privatevalbaseDelayMs:Long500,privatevalmaxDelayMs:Long30_000):RetryStrategy{overridefungetRetryDelay(attempt:Int,error:Throwable):Long{if(attemptmaxRetries)return-1valdelaybaseDelayMs*(1Lshl(attempt-1))valjitter(Math.random()*baseDelayMs).toLong()returnminOf(delayjitter,maxDelayMs)}}classSmartRetry(privatevalmaxRetries:Int3):RetryStrategy{overridefungetRetryDelay(attempt:Int,error:Throwable):Long{if(attemptmaxRetries)return-1returnwhen(error){isjava.net.SocketTimeoutException-2000L*attemptisjava.net.UnknownHostException--1isjava.io.IOException-1000Lelse--1}}}classNetworkClient(privatevarretryStrategy:RetryStrategyExponentialBackoffRetry()){funsetRetryStrategy(strategy:RetryStrategy){this.retryStrategystrategy}suspendfunTexecuteWithRetry(request:suspend()-T):T{varattempt0while(true){attempttry{returnrequest()}catch(e:Exception){valdelayretryStrategy.getRetryDelay(attempt,e)if(delay0)throwe Log.w(NetworkClient,第${attempt}次失败${delay}ms后重试)delay(delay)}}}}场景二自定义 Interpolator开发中经常需要自定义动画曲线直接实现 TimeInterpolator 接口// 弹性插值器模拟弹簧效果classSpringInterpolator(privatevalfactor:Float0.4f):TimeInterpolator{overridefungetInterpolation(input:Float):Float{return(2.0.pow(-10.0*input)*sin((input-factor/4)*(2*Math.PI)/factor)1).toFloat()}}// 贝塞尔曲线插值器classCubicBezierInterpolator(privatevalcx1:Float,privatevalcy1:Float,privatevalcx2:Float,privatevalcy2:Float):TimeInterpolator{companionobject{valEASECubicBezierInterpolator(0.25f,0.1f,0.25f,1.0f)valEASE_INCubicBezierInterpolator(0.42f,0.0f,1.0f,1.0f)valEASE_OUTCubicBezierInterpolator(0.0f,0.0f,0.58f,1.0f)}overridefungetInterpolation(input:Float):Float{varstart0f;varend1fwhile(startend){valmid(startend)/2valxcubicBezier(mid,cx1,cx2)if(Math.abs(x-input)0.001f)returncubicBezier(mid,cy1,cy2)if(xinput)startmidelseendmid}returncubicBezier((startend)/2,cy1,cy2)}privatefuncubicBezier(t:Float,c1:Float,c2:Float):Float{vals1-treturn3*s*s*t*c13*s*t*t*c2t*t*t}}// 使用ObjectAnimator.ofFloat(view,translationY,200f,0f).apply{duration400interpolatorCubicBezierInterpolator.EASE_OUTstart()}场景三数据验证策略表单验证中不同字段有不同的验证规则策略模式使验证逻辑可灵活组合funinterfaceValidatorT{funvalidate(value:T):String?// 返回 null 表示通过}objectValidators{funnotEmpty()ValidatorString{if(it.isBlank())不能为空elsenull}funminLength(min:Int)ValidatorString{if(it.lengthmin)长度不能少于${min}个字符elsenull}funmatchPattern(pattern:Regex,msg:String)ValidatorString{if(!pattern.matches(it))msgelsenull}valemailmatchPattern(Regex(^[A-Za-z0-9_.-][A-Za-z0-9.-]\\.[A-Za-z]{2,}$),邮箱格式不正确)}// 组合多个验证策略funTvalidatorOf(varargvalidators:ValidatorT)ValidatorT{value-validators.firstNotNullOfOrNull{it.validate(value)}}// 使用classRegisterViewModel:ViewModel(){privatevalusernameValidatorvalidatorOf(Validators.notEmpty(),Validators.minLength(3),Validators.matchPattern(Regex(^[a-zA-Z0-9_]$),只能包含字母、数字和下划线))funvalidateUsername(input:String):String?usernameValidator.validate(input)}与其他模式的对比策略模式 vs 状态模式维度策略模式状态模式意图算法替换状态驱动行为变化切换方式由客户端主动选择由状态对象内部触发策略间关系各策略相互独立各状态知道彼此定义转换规则Android实例InterpolatorView 的 ENABLED/DISABLED 等状态二者在结构上几乎一模一样区别在于语义策略模式的策略由外部决定状态模式的状态由内部流转。策略模式 vs 模板方法模式策略模式通过组合持有策略对象引用实现算法替换模板方法模式通过继承子类重写钩子方法实现算法定制。策略模式更灵活——策略可在运行时切换模板方法在编译时就已确定。Android 中 Interpolator 用的是策略模式AsyncTask 的 doInBackground/onPostExecute 用的是模板方法模式。策略模式 vs 命令模式策略模式封装算法关注如何完成一件事命令模式封装请求关注将操作对象化以便排队、撤销、重做。Interpolator 是策略——不同的计算方式Runnable 传给 Handler.post() 更接近命令——将操作封装后延迟执行。总结与最佳实践策略模式在 Android 中的渗透度极高。动画系统的 Interpolator、RecyclerView 的 LayoutManager、列表排序的 Comparator——这些 API 都遵循定义接口、注入实现的策略模式核心范式。最佳实践优先使用函数类型而非接口Kotlin 中如果策略接口只有一个方法可以用函数类型(Input) - Output代替接口定义。Comparator可以直接用compareBy { it.name }的 lambda 形式比匿名内部类简洁得多。策略对象尽量无状态无状态的策略对象可以被多个 Context 共享。Android 的 LinearInterpolator 是无状态的一个实例可被所有动画共用。如果策略有配置参数如 AccelerateInterpolator 的 factor确保构造时传入且不可变。考虑用枚举简化简单策略当策略数量固定且逻辑简单时枚举比独立的类更紧凑enumclassSortOrder:ComparatorItem{BY_NAME{overridefuncompare(a:Item,b:Item)a.name.compareTo(b.name)},BY_DATE{overridefuncompare(a:Item,b:Item)a.date.compareTo(b.date)},BY_SIZE{overridefuncompare(a:Item,b:Item)a.size.compareTo(b.size)}}策略组合优于策略继承需要组合多个策略时如先验证非空再验证格式采用组合模式串联多个策略而不是通过继承创建新策略类。提供合理的默认策略Context 应有一个默认策略避免空指针。Android 的 Animation 在未设置 Interpolator 时默认使用 AccelerateDecelerateInterpolator保证动画系统始终可用。