Android中三种动画

Android中动画可以分为三种:帧动画、tween动画、属性动画。

1.帧动画就像看电视一样,通过快速切换图片让人以为看到的一直在动。主要用到的类是AnimationDrawable。

使用方法:

imageview.setImageResource(R.drawable.animation1); 
animationDrawable = (AnimationDrawable) imageview.getDrawable();
animationDrawable.start();  

通过查看源码我们来具体分析一下是怎么实现帧动画的。

 public void setImageResource(int resId) {
        if (mUri != null || mResource != resId) {
            updateDrawable(null);
            mResource = resId;
            mUri = null;
           resolveUri();
            requestLayout();
            invalidate();
        }
    }

把设置的资源文件复制给了一个全局变量,然后调用了另一个方法:

  private void resolveUri() {
     ……
        if (mResource != 0) {
             d = rsrc.getDrawable(mResource);
        ……
        updateDrawable(d);//在这个方法里把d付给了mDrawable也就是使用时候get出来的drawable
    }

在这个方法里看到通过设置过来的资源文件把他转换成了一个drawable。方法调用的步骤依次是Resources.getDrawable——》Resources.loadDrawable——》Drawable.createFromXml——》Drawable.createFromXmlInner在这个方法中通过xml解析器把xml写好了animation-list解析成了drawable

 public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs)
    throws XmlPullParserException, IOException {
        Drawable drawable;

        final String name = parser.getName();

        if (name.equals("selector")) {
            drawable = new StateListDrawable();
        } else if (name.equals("level-list")) {
            drawable = new LevelListDrawable();
        } else if (name.equals("layer-list")) {
            drawable = new LayerDrawable();
        } else if (name.equals("transition")) {
            drawable = new TransitionDrawable();
        } else if (name.equals("color")) {
            drawable = new ColorDrawable();
        } else if (name.equals("shape")) {
            drawable = new GradientDrawable();
        } else if (name.equals("scale")) {
            drawable = new ScaleDrawable();
        } else if (name.equals("clip")) {
            drawable = new ClipDrawable();
        } else if (name.equals("rotate")) {
            drawable = new RotateDrawable();
        } else if (name.equals("animated-rotate")) {
            drawable = new AnimatedRotateDrawable();            
       } else if (name.equals("animation-list")) {
            drawable = new AnimationDrawable();//这个就是通过xml的name创建一个AnimationDrawable这也真好解释了为什么使用的时候强转
   } else if (name.equals("inset")) {
       drawable = new InsetDrawable();
        } else if (name.equals("bitmap")) {
            drawable = new BitmapDrawable();
            if (r != null) {
               ((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
            }
        } else if (name.equals("nine-patch")) {
            drawable = new NinePatchDrawable();
            if (r != null) {
                ((NinePatchDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
             }
        } else {
            throw new XmlPullParserException(parser.getPositionDescription() +
                    ": invalid drawable tag " + name);
        }

        drawable.inflate(r, parser, attrs);
        return drawable;
    }

使用时候的第一行代码就做了这些事,然后第二行通过这个方法:

    public Drawable getDrawable() {
        return mDrawable;
    }

拿到了imageview身上的drawable,然而mDrawable是哪里复制的呢?还记得resolveUri中的这个方法updateDrawable吗?请看

 private void updateDrawable(Drawable d) {
        if (mDrawable != null) {
            mDrawable.setCallback(null);
            unscheduleDrawable(mDrawable);
        }
        mDrawable = d;//这句是关键,这个不就是get的drawable么
        if (d != null) {
            d.setCallback(this);
            if (d.isStateful()) {
                d.setState(getDrawableState());
            }
            d.setLevel(mLevel);
            mDrawableWidth = d.getIntrinsicWidth();
            mDrawableHeight = d.getIntrinsicHeight();
            applyColorMod();
            configureBounds();
        }
    }

第三行代码用了这个类,这个AnimationDrawable有个特点,通过看他的源码发现他的内部通过一个辅助类AnimationState来控制动画,AnimationState简单来说就是一个javaBean附加了一些操作这些field的方法,里面用数组存储了每帧图片和显示时间。
方法调用次序

    public void start() {//当我们写下这行代码的时候实际上是间接调用run
        if (!isRunning()) {
            run();
        }
    }
         /**这里的run和start虽然是runnable开启多线程的方法,但是并没有开启其他线程,google的官方注释是This method exists for implementation purpose only and should not be,实际这个方法是为handler.post(runnable),后面就会看到了
     * called directly. Invoke {@link #start()} instead.真不明白他们是什么意思
     * @see #start()
     */ 
 public void run() {
       nextFrame(false); 
  }
    private void nextFrame(boolean unschedule) {//显示下一帧图片
        int next = mCurFrame+1;
        final int N = mAnimationState.getChildCount();
        if (next >= N) {
            next = 0;
        }
       setFrame(next, unschedule, !mAnimationState.mOneShot || next < (N - 1));//参数1下一帧的index,参数2润方法里给的是false,参数3得出一个boolean表示是不是要播放下一帧。
    }
    private void setFrame(int frame, boolean unschedule, boolean animate) {
        if (frame >= mAnimationState.getChildCount()) {
            return;
        }
        mCurFrame = frame;
        selectDrawable(frame);
        if (unschedule) {//不会进到这里来
            unscheduleSelf(this);
        }
        if (animate) {
          scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]);
        }
    }

这个方法在drawable里面,它用到了一个callback,我们来看看这个接口是从哪里设置的呢?

    public void scheduleSelf(Runnable what, long when)
    {
        if (mCallback != null) {
           mCallback.scheduleDrawable(this, what, when);//这个方法是在view里实现的
        }
    }

这就要找imageview了是不是调用了这个方法,这个方法是在updateDrawable里调用的,刚才已经看了updateDrawable早在setImageResource就已经被调用了 ,已经在上边用加粗标出。

    public final void setCallback(Callback cb) {
        mCallback = cb;
    }

imageview并没有实现CallBack,是他的父类view实现了这个接口,
public void scheduleDrawable(Drawable who, Runnable what, long when) {
if (verifyDrawable(who) && what != null && mAttachInfo != null) {
mAttachInfo.mHandler.postAtTime(what, who, when);//看到这句代码就能跟start里执行的run方法连到一起了
}
}
加粗的这行代码是view使用他自身的一个handler(在view.dispatchAttachedToWindow赋值)post在当前时间+当前帧设定显示时间为这个drawable执行Runnable ,也就是调用了start方法里调用的run方法。

2.tween动画和帧动画的实现原理截然不同,帧动画说白了是drawable,tween是animation改变view的Transformation,android自带有4种实现方式,也可以自定义其他的实现方式。

使用方法:

    AlphaAnimation aa = new AlphaAnimation(0f, 1f);
    view.startAnimation(aa);
或者  
    Animation aa = AnimationUtils.loadAnimation(context, R.anim.alpha_in);
    view.startAnimation(aa);

animation类似一个javaBan保存了动画的各种属性,并且他身上有一个空方法:

    protected void applyTransformation(float interpolatedTime, Transformation t) {
    }

AlphaAnimation、ScaleAnimation、RotateAnimation、TranslateAnimation这四种动画都是Animation的子类,并且他们都通过构造对属性进行初始化,复写了applyTransformation(float interpolatedTime, Transformation t)这个方法,在这个方法中通过传过来的interpolatedTime计算出不同的值通过 t.getMatrix(),改变matrix来改变显示的状态,但是这种改变transformation对view的位置和和大小的改变不能改变view的真实属性,也就是view所有的touchevent都按原始位置计算。
实际上不管是 new AlphaAnimation(0f, 1f)还是AnimationUtils.loadAnimation(context, R.anim.alpha_in)都是new出来一个javaBean。这里面有个设计模式值得说说,animation有个属性是Interpolator大家都知道是加速器,他有几个实现类,他们通过不同的算法去复写一个方法,这是明显的策略模式。

public interface Interpolator {

    /**
     原有的英文注释大概意思是通过传入的input值(取值范围0-1)根据指定算法返回一个应该在0-1之间的数,也有个别的返回值大于1或小于0.  */
    float getInterpolation(float input);
}

如果您有兴趣看看这几个算法可以通过坐标系的形式画出来,他们都是二院多次函数。
animation和Interpolator都可以自己定义,分别要实现applyTransformation、getInterpolation这两个方法。
AnimationUtils.loadAnimation这个方法也是通过xml解析器解析xml生成对应的animation。

 public static Animation loadAnimation(Context context, int id)
            throws NotFoundException {
        XmlResourceParser parser = null;
        try {
            parser = context.getResources().getAnimation(id);
            return createAnimationFromXml(context, parser);
        } catch (XmlPullParserException ex) {
        ……
        } finally {
            if (parser != null) parser.close();
        }
    }
    private static Animation createAnimationFromXml(Context c, XmlPullParser parser)
            throws XmlPullParserException, IOException {
        return createAnimationFromXml(c, parser, null, Xml.asAttributeSet(parser));
    }

    private static Animation createAnimationFromXml(Context c, XmlPullParser parser, AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException {
       ……
            if (name.equals("set")) {
                anim = new AnimationSet(c, attrs);
                createAnimationFromXml(c, parser, (AnimationSet)anim, attrs);
            } else if (name.equals("alpha")) {
                anim = new AlphaAnimation(c, attrs);
            } else if (name.equals("scale")) {
                anim = new ScaleAnimation(c, attrs);
            }  else if (name.equals("rotate")) {
                anim = new RotateAnimation(c, attrs);
            }  else if (name.equals("translate")) {
                anim = new TranslateAnimation(c, attrs);
            } else {
                throw new RuntimeException("Unknown animation name: " + parser.getName());
            }
            if (parent != null) {//不会走到这里来
                parent.addAnimation(anim);
            }
        }
        return anim;

    }
view.startAnimation把animation设置给了view。
    public void startAnimation(Animation animation) {
        animation.setStartTime(Animation.START_ON_FIRST_FRAME);
        setAnimation(animation);
        invalidate();
    }

invalidate方法的作用是使他的父view重绘自己和所有的子view。他的执行过程比较复杂。这个方法标记了view中的一个mPrivateFlags&PFLAG_DIRTY当viewroot接收到vsync信号执行performDraw()刷新界面的时候的时候检测到标记的dirty区域,view会重新绘制执行draw()这个方法,它里面有个空方法是在viewgroup里实现的:

 protected void dispatchDraw(Canvas canvas) {
     ……
        if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        } else {
            for (int i = 0; i < count; i++) {
                final View child = children[getChildDrawingOrder(count, i)];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        }
        // Draw any disappearing views that have animations
     ……
    }
 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        ……
        final Animation a = child.getAnimation();
        ……
          more = a.getTransformation(drawingTime, mChildTransformation);
        ……
        return more;
    }

在viewgroup重绘所有的子view时候,子view调用了animation的getTransformation,这个方法animation判断是不是要开始动画,最终要执行上面第一步早准备好的applyTransformation(interpolatedTime, outTransformation),就到了所有animation要复写的方法,不同的方法复写实现不同的动画,当然也可以自定义一个动画。

    public boolean getTransformation(long currentTime, Transformation outTransformation) {
     ……
        if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
            if (!mStarted) {
                if (mListener != null) {
                    mListener.onAnimationStart(this);
                }
                mStarted = true;
            }
            if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
            if (mCycleFlip) {
                normalizedTime = 1.0f - normalizedTime;
            }
           final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
            applyTransformation(interpolatedTime, outTransformation);
        }
    ……
        return mMore;
    }

3.属性动画

3.0以下的系统版本需要使用开源框架https://github.com/JakeWharton/NineOldAndroids/
使用方法:

ObjectAnimator oa = ObjectAnimator.ofFloat(view, "alpha", 0, 1);
oa.start();

实际上这个方法通过构造参数把target和它身上对应的属性赋值给了ObjectAnimator

    public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }

这个方法进行了一系列的判空,然后调用了方法setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));里面的参数PropertyValuesHolder是一个很重要的类后面还会用到,暂时把对应的属性和值都

    public void setFloatValues(float... values) {
        if (mValues == null || mValues.length == 0) {
            if (mProperty != null) {
                setValues(PropertyValuesHolder.ofFloat(mProperty, values));
            } else {
                setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
            }
        } else {
            super.setFloatValues(values);
        }
    }
    public void setValues(PropertyValuesHolder... values) {//这个方法很明显是把可变参数里的属性转移到一个map
        int numValues = values.length;
        mValues = values;
        mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
        for (int i = 0; i < numValues; ++i) {
            PropertyValuesHolder valuesHolder = (PropertyValuesHolder) values[i];
            mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
        }
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

然后看一下第二行ObjectAnimator::start()做了什么,这个方法里直接掉了super.start在valueAnimator里又调用start(boolean),这个方法很明确的把一个内部类handler存储到threadlocal然后像这个handler发送一个消息在处理这个消息的时候才真正的开启动画 anim.startAnimation();这里的animation是在threadlocal里get出来的,并不是当前的valueanimator是他的子类ObjectAnimator。

 private void start(boolean playBackwards) {
     ……
        sPendingAnimations.get().add(this);//把ObjectAnimator存到了threadlocal里
      ……
       animationHandler.sendEmptyMessage(ANIMATION_START);
    }

startAnimation这个方法子类并没有复写,valueanimator的代码如下。
private void startAnimation() {
initAnimation();//这里是执行ObjectAnimator里面的init,明显的多态

        sAnimations.get().add(this);
        if (mStartDelay > 0 && mListeners != null) {
            ArrayList<AnimatorListener> tmpListeners =
                    (ArrayList<AnimatorListener>) mListeners.clone();
            int numListeners = tmpListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                tmpListeners.get(i).onAnimationStart(this);
            }
        }
    }

ObjectAnimator是这样复写的:

void initAnimation() {
        if (!mInitialized) {
            if ((mProperty == null) && AnimatorProxy.NEEDS_PROXY && (mTarget instanceof View) && PROXY_PROPERTIES.containsKey(mPropertyName)) {
                setProperty(PROXY_PROPERTIES.get(mPropertyName));
            }
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].setupSetterAndGetter(mTarget);//通过反射把属性生成了get、set方法
                //android里把java的内省去掉了,这里是通过拼字符串的方法google自己拼的            
            super.initAnimation();
        }
    }

至此就把对应的属性改为了插值器计算的值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值