博客主页

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

使用场景:

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

UML 类图

  1. Context 用来操作策略的上下文环境
  2. Strategy 策略的抽象
  3. ConcreteStrategyA、ConcreteStrategyB 具体的策略实现

通常如果一个问题有多种解决方案时,最简单的方式就是利用if-else或者switch-case方式根据不同的情景选择不同的解决方案。但这种简单的方案问题太多,例如耦合性太高、代码臃肿、难以维护等。

策略模式的简单实现

下面以在北京坐公共交通工具的费用计算来演示示例。例如:公交采用分段计价,也就是乘坐的距离越远,价格越高。公交车和地铁的价格计算方式不一样,但是在示例中是需要计算乘不同出行工具的成本,策略模式的第一个版本示例代码,

public class PriceCalculator {
    // 公交车类型
    private static final int BUS = 1;

    // 地铁类型
    private static final int METRO = 2;

    public static void main(String[] args) {
        PriceCalculator calculator = new PriceCalculator();
        System.out.println("坐16公里的公交车票价为:" + calculator.calculatorPrice(16, BUS));

    }

    private int calculatorPrice(int km, int type) {
        if (type == BUS) {
            return busPrice(km);
        } else if (type == METRO) {
            return metroPrice(km);
        }
        return 0;
    }

    /**
     * 公交车,10公里之内一元钱,超过10公里之后每加一元可以乘5公里
     */
    private int busPrice(int km) {
        // 超过10公里的总距离
        int extraTotal = km - 10;
        // 超过的距离是5公里的倍数
        int extraFactor = extraTotal / 5;
        // 超过的距离对5公里取余
        int fraction = extraFactor % 5;

        // 价格计算
        int price = 1 + extraFactor;

        return fraction > 0 ? ++price : price;
    }

    /**
     * 6公里(含)3元,6~12公里(含)4元,12~22公里(含)5元,22~32(含)6元
     */
    private int metroPrice(int km) {
        if (km <= 6) {
            return 3;
        } else if (km <= 12) {
            return 4;
        } else if (km <= 22) {
            return 5;
        } else if (km <= 32) {
            return 6;
        }
        return 7;
    }
}

这个版本很明显的问题就是并不是单一职责,首先是承担了计算公交车和地铁乘坐价格的职责,另一个是if-else的形式来判断使用哪种计算形式。当增加出租车出行方式,就要在PriceCalculator中增加一个方法来计算出租车出行的价格,并且在calculatorPrice函数中增加一个判断。

下面使用策略模式进行重构。

首先定义一个抽象的价格计算接口,如:CalculateStrategy

// 计算价格的接口
public interface CalculateStrategy {
    /**
     * 按距离来计算价格
     * @param km 公里
     * @return 返回价格
     */
    int calculatePrice(int km);
}

对于每一种出行方式都有一个独立的计算策略类,且实现CalculateStrategy接口
BusStrategy : 公交车价格计算策略

// 公交车价格计算策略
public class BusStrategy implements CalculateStrategy {
    // 10公里之内一元钱,超过10公里之后每加一元可以乘5公里
    @Override
    public int calculatePrice(int km) {
        // 超过10公里的总距离
        int extraTotal = km - 10;
        // 超过的距离是5公里的倍数
        int extraFactor = extraTotal / 5;
        // 超过的距离对5公里取余
        int fraction = extraFactor % 5;

        // 价格计算
        int price = 1 + extraFactor;

        return fraction > 0 ? ++price : price;
    }
}

MetroStrategy : 地铁价格计算策略

// 地铁价格计算策略
public class MetroStrategy implements CalculateStrategy {
    // 6公里(含)3元,6~12公里(含)4元,12~22公里(含)5元,22~32(含)6元
    @Override
    public int calculatePrice(int km) {
        if (km <= 6) {
            return 3;
        } else if (km <= 12) {
            return 4;
        } else if (km <= 22) {
            return 5;
        } else if (km <= 32) {
            return 6;
        }
        return 7;
    }
}

在创建一个扮演Context角色的类,如:TranficCalculator

// 公交出行价格计算器
public class TranficCalculator {
    public static void main(String[] args) {
        TranficCalculator calculator = new TranficCalculator();

        // 设置策略
        calculator.setStrategy(new BusStrategy());
        // 计算价格
        System.out.println("坐16公里的公交车票价为:" + calculator.calculatePrice(16));
    }

    private int calculatePrice(int km) {
        return strategy.calculatePrice(km);
    }

    private CalculateStrategy strategy;
    private void setStrategy(BusStrategy strategy) {
        this.strategy = strategy;
    }

}

这种方案隐藏实现的同时,可扩展性变的很强。通过建立抽象,将不同的策略构建成一个具体的策略实现,通过不同的策略实现算法替换,简化逻辑、结构的同时,增强了系统的可读性、稳定性、可扩展性,对于较为复杂的业务逻辑显得更为直观,扩展也更方便。

深入分析属性动画

在Android 3.0之前,Android 提供了几种动画类型:View Animation、Drawable Animation、Property Animation。View Animation只能支持简单的缩放、平移、旋转、透明度这几个基本的动画,且有一定的局限性。例如:希望View有一个颜色的切换动画,希望可以使用3D旋转动画,希望当动画停止时View的位置就是当前的位置,这些View Animation是无法做到。

Google在 Android 3.0提供了属性动画,为了兼容Android 3.0以下的动画库,在github上有一个开源的动画库NineOldAnimations,它通过判断系统版本来选择实现属性动画的方式。

属性动画体系的整体设计:

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

属性动画核心类介绍

  1. ValueAnimator:该类是Animator的子类,实现了动画的整个处理逻辑,也是属性动画最为核心的类
  2. ObjectAnimator: 对象属性动画的操作类,继承自ValueAnimator,通过该类使用动画的形式操作对象的属性
  3. TimeInterpolator:时间插值器,它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有线性插值器(LinearInterpolator)、加速减速插值器(AccelerateDecelerateInterpolator)、减速插值器(DecelerateInterpolator)等
  4. TypeEvaluator:类型估值器,它的作用是根据当前属性改变的百分比来计算改变后的属性值,系统预置的有针对整型属性(IntEvaluator)、针对浮点型属性(FloatEvaluator)、针对Color属性(ArgbEvaluator)等
  5. Property:属性对象,主要定义了属性的set和get方法
  6. PropertyValuesHolder:持有目标属性Property、setter 和 getter方法,以及关键帧集合的类
  7. KeyframeSet:存储一个动画的关键帧集合

基本使用

示例一
改变一个对象(TextView)的 translationY 属性,让其沿着y轴向下平移一段距离,该动画在默认时间内完成,动画的完成时间是可以定义的,想要灵活的效果还可以定义插值器和估值算法。

ObjectAnimator.ofFloat(textview, "translationY", 100.f).start();

示例二
改变一个对象的背景色属性,如改变View的背景色。可以让背景色在3s内实现从0x22ff0000到0x2200ff00的渐变,并且动画会无限循环且会有反转的效果

ValueAnimator animator = ObjectAnimator.ofInt(textview, "backgroundColor", 0x22ff0000, 0x2200ff00);
animator.setDuration(3000); // 时间,默认300
animator.setEvaluator(new ArgbEvaluator()); // 类型估值器
animator.setRepeatCount(ValueAnimator.INFINITE); // 重复次数
animator.setRepeatMode(ValueAnimator.REVERSE); // 重复模式
animator.start();

示例三
动画集合,5s内对View的循转、平移、缩放和透明度都进行了改变

AnimatorSet set = new AnimatorSet();
set.playTogether(
        ObjectAnimator.ofFloat(textview, "rotationX", 0, 360),
        ObjectAnimator.ofFloat(textview, "rotationY", 0, 180),
        ObjectAnimator.ofFloat(textview, "rotation", 0, -90),
        ObjectAnimator.ofFloat(textview, "translationX", 0, 90),
        ObjectAnimator.ofFloat(textview, "translationY", 0, 90),
        ObjectAnimator.ofFloat(textview, "scaleX", 1, 1.5f),
        ObjectAnimator.ofFloat(textview, "scaleY", 1, 0.5f),
        ObjectAnimator.ofFloat(textview, "alpha", 1, 0.25f, 1)
);
set.setDuration(5 * 1000).start();

示例四
animate()方法是属性动画特有的,动画持续时间为2s,在y轴上旋转720度,且平移到(100,100)的位置

textview.animate().setDuration(2000).rotationYBy(720).x(100).y(100);

核心原理分析

下面以具体的示例作为分析的入口,如:要对某个View的小、轴缩放到原来的0.3倍,动画执行时间为1s

ValueAnimator animator = ObjectAnimator.ofFloat(textview, "scaleX", 0.3f);
animator.setDuration(1000);
animator.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的ofFloat函数中会先构建属性动画对象,然后根据设置的属性值来初始化各个时间段对应的属性值,这个属性值就是values参数,它是一个可变参数,如果是一个参数,那么该函数为目标值;如果是两个参数,那么一个是起始值,另一个是目标值。先来看看setFloatValues函数实现

PropertyValuesHolder[] mValues; // mValues是动画的各个数值的集合
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;
    // 属性setter方法
    Method mSetter = null;
    // 属性getter方法
    private Method mGetter = null;
    // 属性的类类型,如float、int等
    Class mValueType;
    // 这里是动画关键帧即可,即在duration时间内的动画帧集合,它保存的是在每个时刻该属性对应的值
    Keyframes mKeyframes = null;

    public static PropertyValuesHolder ofFloat(Property<?, Float> property, float... values) {
        // 构建的是FloatPropertyValuesHolder
        return new FloatPropertyValuesHolder(property, values);
    }
    
    // 内部类,Float类型的PropertyValuesHolder
    static class FloatPropertyValuesHolder extends PropertyValuesHolder {
        // float型的属性
        private FloatProperty mFloatProperty;
        // 动画的关键帧,这个是重点
        Keyframes.FloatKeyframes mFloatKeyframes;
        float mFloatAnimatedValue;

        // 构造函数
        public FloatPropertyValuesHolder(Property property, float... values) {
            super(property);
            // 设置目标属性值
            setFloatValues(values);
            if (property instanceof  FloatProperty) {
                mFloatProperty = (FloatProperty) mProperty;
            }
        }

        // 设置动画的目标值
        public void setFloatValues(float... values) {
            super.setFloatValues(values);
            // 获取动画关键帧
            mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
        }
        // 计算当前的动画帧
        void calculateValue(float fraction) {
            mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
        }
        // ...省略
    }
}

该类是属性和属性值的辅助类,它保存了属性的名称、setter、getter,以及该属性在duration时间段内各个时刻对应属性数值(mKeyframeSet)。这样当执行动画时,动画库只需要根据动画的执行时间,到mKeyframeSet中查询这个时刻对应的属性值,然后修改执行动画的对象的目标属性值,连续这个过程即可达到动画的效果。

计算各个时刻的属性值的操作放在了父类(即PropertyValuesHolder)的setFloatValues的函数中,结下来看下setFloatValues实现

public void setFloatValues(float... values) {
    mValueType = float.class;
    mKeyframes = KeyframeSet.ofFloat(values);
}

该函数又调用了KeyframeSet的ofFloat方法

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);
}

关键帧的计算在函数ofFloat中实现。如果设置了一个目标值,那么这个值就是最终的值,它的起始值会被默认设置为0,如果用户设置了大于1个目标值,这些关键帧都会被存储到KeyframeSet对象中。设置完关键帧后,就会调用start()方法启动动画。

public void start() {
    super.start();
}

调用父类的start()方法,它的父类是ValueAnimator

public void start() {
    start(false);
}

private void start(boolean playBackwards) {
    // 判断Looper是否为空,这里的Looper是UI线程的Looper
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }
    mReversing = playBackwards;
    mSelfPulse = !mSuppressSelfPulseRequested;
    // Special case: reversing from seek-to-0 should act as if not seeked at all.
    if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
        if (mRepeatCount == INFINITE) {
            // Calculate the fraction of the current iteration.
            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);
        }
    }
}

如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)


小兵兵同学
56 声望23 粉丝

Android技术分享平台,每个工作日都有优质技术文章分享。从技术角度,分享生活工作的点滴。