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();
}
}
至此就把对应的属性改为了插值器计算的值