Android策略设计模式进阶

作者: Android高级架构探索 | 来源:发表于2019-01-18 16:45 被阅读14次

    策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。Android中最典型的的策略设计模式是动画插值器的使用,具体怎么使用的,将是本文所重点所写的内容。

    1、UML类图

    image
    • Context:用来操作策略的上下文环境。
    • Strategy : 策略的抽象。
    • ConcreteStrategyA、ConcreteStrategyB : 具体的策略实现。

    2、Android源码中的模式实现

    日常的Android开发中经常会用到动画,Android中最简单的动画就是Tween Animation了,当然帧动画和属性动画也挺方便的,但是基本原理都类似,毕竟动画的本质都是一帧一帧的展现给用户的,只不要当fps小于60的时候,人眼基本看不出间隔,也就成了所谓的流畅动画。(注:属性动画是3.0以后才有的,低版本可采用NineOldAndroids来兼容。而动画的动态效果往往也取决于插值器Interpolator不同,我们只需要对Animation对象设置不同的Interpolator就可以实现不同的效果,这是怎么实现的呢?

    注‘Android技术交流群878873098,欢迎大家加入交流,畅谈!本群有免费学习资料视频且免费分享

    首先要想知道动画的执行流程,还是得从View入手,因为Android中主要针对的操作对象还是View,所以我们首先到View中查找,我们找到了View.startAnimation(Animation animation)这个方法。

    public void startAnimation(Animation animation) {
        //初始化动画开始时间
        animation.setStartTime(Animation.START_ON_FIRST_FRAME);
        //对View设置动画
        setAnimation(animation); 
        //刷新父类缓存
        invalidateParentCaches();
        //刷新View本身及子View
        invalidate(true);
    }
    

    考虑到View一般不会单独存在,都是存在于某个ViewGroup中,所以google使用动画绘制的地方选择了在ViewGroup中的drawChild(Canvas canvas, View child, long drawingTime)方法中进行调用子View的绘制。

    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;
        //判断动画是否已经初始化过
        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();
        //调用了Animation的getTransformation()方法,这里就是通过计算获取动画的相关值
        boolean more = a.getTransformation(drawingTime, t, 1f);
    
        //代码省略。。。
    
        if (more) {
            //根据具体实现,判断当前动画类型是否需要进行调整位置大小,然后刷新不同的区域
            if (!a.willChangeBounds()) {
                //...
    
            }else{
                //...重新重绘的区域、重新计算有效区域、更新这块区域
            }
        }
        return more;
    }
    

    其中主要的操作是动画始化、动画操作、界面刷新。动画的具体实现是调用了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) {
        //代码省略。。。
        float normalizedTime;
        if (duration != 0) {
            //1、计算当前的时间的流逝百分比
            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 ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
            //代码省略。。。
    
            //2、通过插值器获取动画的执行百分比
            final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
            //3、应用动画效果
            applyTransformation(interpolatedTime, outTransformation);
        }
    
        //4、如果动画指定完毕,那么触发动画完成的回调或者指定重复动画等操作
    
        if (!mMore && mOneMoreTime) {
            mOneMoreTime = false;
            return true;
        }
    
        return mMore;
    }
    

    其中最重要的代码通过红笔标注出来了。

    注‘Android技术交流群878873098,欢迎大家加入交流,畅谈!本群有免费学习资料视频且免费分享

    很容易发现Android系统中在处理动画的时候会调用插值器中的getInterpolation(float input)方法来获取当前的时间点,依次来计算当前变化的情况。这就不得不说到Android中的插值器Interpolator,它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比。看到这里,希望你已经理解了插值器和估值器的区别。之前有一个总结:插值器会传入一个参数,这个参数就是一个时间进度值,也就是所谓的当前时间的流逝百分比。它相当于时间的概念,通过setDuration()制定了动画的时长,在这个时间范围内,动画进度是一点点增加的,相当于一首歌,它的进度从0到1意思一样。插值器需要根据这个值计算返回动画的数值进度,这个值可以根据插值器的不同来设置不同的算法,最终这个值可以在监听回调中拿到。

    系统预置的有LinearInterpolator(线性插值器:匀速动画)、AccelerateDecelerateInterpolator(加速减速插值器:动画两头慢中间快)和DecelerateInterpolator(减速插值器:动画越来越慢)等,如图:

    image

    由于初期比较旧的版本采用的插值器是TimeInterpolator抽象,google采用了多加一层接口继承来实现兼容也不足为怪了。很显然策略模式在这里作了很好的实现,Interpolator就是处理动画时间的抽象,LinearInterpolator、CycleInterpolator等插值器就是具体的实现策略。插值器与Animation的关系图如下:

    image

    这里以LinearInterpolator、AccelerateInterpolator和CycleInterpolator为例:

    LinearInterpolator:

    public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
        public float getInterpolation(float input) {
            return input;
        }
    }
    

    CycleInterpolator:

    public float getInterpolation(float input) {
        return (float)(Math.sin(2 * mCycles * Math.PI * input));
    }
    

    可以看出LinearInterpolator中计算当前时间的方法是做线性运算,也就是返回input*1,所以动画会成直线匀速播放出来,而CycleInterpolator是按照正弦运算,所以动画会正反方向跑一次,其它插值器依次类推。不同的插值器的计算方法都有所差别,用户设置插值器以实现动画速率的算法替换。

    我们再来看看加速插值器的代码:

    public AccelerateInterpolator(float factor) {
        mFactor = factor;
        mDoubleFactor = 2 * mFactor;
    }
    
    public AccelerateInterpolator(Context context, AttributeSet attrs) {
        this(context.getResources(), context.getTheme(), attrs);
    }
    
    public float getInterpolation(float input) {
        if (mFactor == 1.0f) {//默认1.0f,锁着事件的推移,变化范围增大
            return input * input;
        } else {//用户自己设置了值,根据这个值与1.0最大值乘积返回回去
            return (float)Math.pow(input, mDoubleFactor);
        }
    }
    

    我们看到,默认情况下,AccelerateInterpolator 的getInterpolation 方法中会对input进行乘方运算,这个input就是流逝时间百分比(时间进度值)。input取值范围为0.0f~1.0f,当input逐渐增大时,input*input的变化范围越来越大,使得动画属性值在同一时间段内的变化范围更大,从而实现了加速动画的效果。例如,动画指定时间为1000ms,使用的是AccelerateInterpolator 插值器,在动画指定了100ms时百分比为0.1,此时通过插值器计算得到的动画数值进度值为0.01,;又经过100ms,此时的百分比为0.2,经过插值器计算变为0.04,;执行到0..ms,计算得到0.09....可以看到,在相同的100ms内百分比变化频率逐渐增大。100200ms变化值0.03,200300ms变化值为0.05,这样在同一个时间段内百分比差距越来越大,也就形成了加速的效果。

    在调用了插值器的getInterpolation 方法后,会继续调用动画类的applyTransformation方法将属性应用到对应的对象中。在Animation中是空实现,这里选择TranslateAnimation来看看它的具体实现:

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        float dx = mFromXDelta;
        float dy = mFromYDelta;
        if (mFromXDelta != mToXDelta) {
            dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
        }
        if (mFromYDelta != mToYDelta) {
            dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
        }
        t.getMatrix().setTranslate(dx, dy);
    }
    

    当执行完applyTransformation后,View就发生了改变,不断重复这个过程,动画就随之产生了。注意这里是Matrix类的setTranslate方法,并没有真正的修改View的属性,这里也是View动画跟属性动画本质区别的地方了(个人这么理解的,如果有误还望指正)

    注‘Android技术交流群878873098,欢迎大家加入交流,畅谈!本群有免费学习资料视频且免费分享

    相关文章

      网友评论

        本文标题:Android策略设计模式进阶

        本文链接:https://www.haomeiwen.com/subject/kiyndqtx.html