zoukankan      html  css  js  c++  java
  • Android动画深入分析

    动画分类

    Android动画能够分3种:View动画。帧动画和属性动画;属性动画为API11的新特性。在低版本号是无法直接使用属性动画的。但能够用nineoldAndroids来实现(可是本质还是viiew动画)。

    学习本篇内容主要掌握下面知识:

    1,View动画以及自己定义View动画。


    2,View动画的一些特殊使用场景。
    3,对属性动画做了一个全面的介绍。
    4,使用动画的一些注意事项。


    view动画

  • View动画的四种变换效果相应着Animation的四个子类:TranslateAnimation(平移动画)、ScaleAnimation(缩放动画)、RotateAnimation(旋转动画)和AlphaAnimation(透明度动画),他们即能够用代码来动态创建也能够用XML来定义。推荐使用可读性更好的XML来定义。

  • <set>标签表示动画集合,相应AnimationSet类,它能够包括若干个动画,而且他的内部也能够嵌套其它动画集合。android:interpolator 表示动画集合所採用的插值器。插值器影响动画速度,比方非匀速动画就须要通过插值器来控制动画的播放过程。
    android:shareInterpolator表示集合中的动画是否和集合共享同一个插值器,假设集合不指定插值器,那么子动画就须要单独指定所需的插值器或默认值。

  • Animation通过setAnimationListener方法能够给View动画加入过程监听。
  • 自己定义View动画仅仅须要继承Animation这个抽象类,并重写initialize和applyTransformation方法,在initialize方法中做一些初始化工作,在applyTransformation中进行相应的矩形变换,非常多时候须要採用Camera来简化矩形变换过程。

  • 帧动画是顺序播放一组预先定义好的图片,相似电影播放;使用简单但easy引发OOM,尽量避免使用过多尺寸较大的图片。

  • view动画应用场景

    LayoutAnimation作用于ViewGroup,为ViewGroup指定一个动画。当他的子元素出场的时候都会具有这样的动画。ListView上用的多,LayoutAnimation也是一个View动画。
    代码实现:

<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
 android:animationOrder="normal"
 android:delay="0.3" android:animation="@anim/anim_item"/>

//--- animationOrder 表示子元素的动画的顺序,有三种选项:
//normal(顺序显示)、reverse(逆序显示)和random(随机显示)。

<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:duration="300" android:shareInterpolator="true"> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" /> <translate android:fromXDelta="300" android:toXDelta="0" /> </set>

第一种。在布局中引用LayoutAnimation
<ListView
     android:id="@+id/lv"
     android:layout_width="match_parent"
     android:layout_height="0dp"
     android:layout_weight="1"
     android:layoutAnimation="@anim/anim_layout"/>
另外一种。代码种使用
Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_item);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.5f);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
listview.setLayoutAnimation(controller);

帧动画

  逐帧动画(Frame-by-frame Animations)从字面上理解就是一帧挨着一帧的播放图片,相似于播放电影的效果。不同于View动画。Android系统提供了一个类AnimationDrawable来实现帧动画,帧动画比較简单,我们看一个样例就可以了。
<?xml version="1.0" encoding="utf-8"?

> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@mipmap/lottery_1" android:duration="200" /> // ...省略非常多 <item android:drawable="@mipmap/lottery_6" android:duration="200" /> </animation-list>


然后
imageView.setImageResource(R.drawable.frame_anim);
AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getDrawable();
animationDrawable.start();//启动start,关闭stop

属性动画

属性动画是Android 3.0新加入(api 11)的功能,不同于之前的view动画(看过的都知道,view动画比方实现的位移事实上不是真正的位置移动,仅仅是实现了一些简单的视觉效果)。属性动画对之前的动画做了非常大的拓展。毫不夸张的说。属性动画能够实现不论什么动画效果,由于在作用的对象是属性(对象),属性动画中有几个概念须要我们注意下,

ValueAnimator、ObjectAnimator、AnimatorSet等。

属性动画作用属性

1,属性动画能够对随意对象的属性进行动画而不仅仅是View,属性动画默认间隔300ms,默认帧率10ms/帧。
2,看一段代码
<set
  android:ordering=["together" | "sequentially"]>

    <objectAnimator
        android:propertyName="string"
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <animator
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <set>
        ...
    </set>
</set>

<set>
它代表的就是一个AnimatorSet对象。

里面有一个 ordering属性。主要是指定动画的播放顺序。


<objectAnimator> 
它表示一个ObjectAnimator对象。它里面有非常多属性,我们重点须要了解的也是它。
android:propertyName -------属性名称,比如一个view对象的”alpha”和”backgroundColor”。
android:valueFrom   --------变化開始值
android:valueTo ------------变化结束值
android:valueType -------变化值类型 ,它有两种值:intType和floatType。默认值floatType。
android:duration ---------持续时间
android:startOffset ---------动画開始延迟时间
android:repeatCount --------反复次数。-1表示无限反复,默觉得-1
android:repeatMode 反复模式,前提是android:repeatCount为-1 。它有两种值:”reverse”和”repeat”。分别表示反向和顺序方向。

<animator>
它相应的就是ValueAnimator对象。

它主要有下面属性。

android:valueFrom
android:valueTo
android:duration
android:startOffset
android:repeatCount
android:repeatMode
android:valueType

定义了一组动画之后,我们怎么让它运行起来呢?
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
    R.anim.property_animator);
set.setTarget(myObject);//myObject表示作用的对象
set.start();

插值器和估值器

时间插值器(TimeInterpolator)的作用是依据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有LinearInterpolator(线性插值器:匀速动画),AccelerateDecelerateInterpolator(加速减速插值器:动画两头慢中间快)。DecelerateInterpolator(减速插值器:动画越来越慢)。



注:这里的插值器非常多。能够翻看我之前关于插值器的解说。


估值器(TypeEvaluator)的作用是依据当前属性改变的百分比来计算改变后的属性值。系统预置有IntEvaluator 、FloatEvaluator 、ArgbEvaluator。

举个简单的样例吧
public class IntEvaluator implements TypeEvaluator<Integer> {
 public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
     int startInt = startValue;
     return (int)(startInt + fraction * (endValue - startInt));
 }
}
上述代码就是计算当前属性所占总共的百分百。

插值器和估值器除了系统提供之外。我们还能够自己定义实现,自己定义插值器须要实现Interpolator或者TimeInterpolator;自己定义估值器算法须要实现TypeEvaluator。

属性动画监听器

属性动画监听器用于监听动画的播放过程,主要有两个接口:AnimatorUpdateListener和AnimatorListener 。
AnimatorListener 
public static interface AnimatorListener {
    void onAnimationStart(Animator animation); //动画開始
    void onAnimationEnd(Animator animation); //动画结束
    void onAnimationCancel(Animator animation); //动画取消
    void onAnimationRepeat(Animator animation); //动画反复播放
}
AnimatorUpdateListener
public static interface AnimatorUpdateListener {
    void onAnimationUpdate(ValueAnimator animator);
}

应用场景

这里我们先提一个问题:给Button加一个动画,让Button在2秒内将宽带从当前宽度添加到500dp,也行你会说。非常easy啊,直接用view动画就能够实现。view动画不是有个缩放动画,可是你能够试试,view动画是不支持对宽度和高度进行改变的。Button继承自TextView,setWidth是对TextView的,所以直接对Button做setWidth是不行的。那么要怎么做呢?
针对上面的问题,官网api给出了例如以下的方案:
  • 给你的对象加上get和set方法,假设你有权限的话
  • 用一个类来包装原始对象,间接提高get和set方法
  • 採用ValueAnimator,监听动画运行过程。实现属性的改变

有了上面的说明,我们大致明确了。要实现開始说的这个问题的效果,我们须要用一个间接的类来实现get和set方法或者自己实现一个ValueAnimator。
第一种,自己封装一个类实现get和set方法。这也是我们经常使用的。拓展性强

public class ViewWrapper {
 private View target;
 public ViewWrapper(View target) {
     this.target = target;
 }
 public int getWidth() {
     return target.getLayoutParams().width;
 }
 public void setWidth(int width) {
     target.getLayoutParams().width = width;
     target.requestLayout();
 }
}

另外一种,採用ValueAnimator。监听动画过程。

private void startValueAnimator(final View target, final int start, final int end) {
   ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
   valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
       private IntEvaluator mEvaluation = new IntEvaluator();//新建一个整形估值器作为暂时变量

       @Override
       public void onAnimationUpdate(ValueAnimator animation) {
           //获得当前动画的进度值 1~100之间
           int currentValue = (int) animation.getAnimatedValue();
           //获得当前进度占整个动画过程的比例,浮点型。0~1之间
           float fraction = animation.getAnimatedFraction();
           //调用估值器,通过比例计算出宽度 
           int targetWidth = mEvaluation.evaluate(fraction, start, end);
           target.getLayoutParams().width = targetWidth;
           //设置给作用的对象,刷新页面
           target.requestLayout();
       }
   });
}

属性动画的工作原理

属性动画的工作原理。主要是对作用的对象不断的调用get/set方法来改变初始值和终于值,然后set到动画属性上就可以。

然后通过消息机制(Handler(只是这里的Handler不是我们经常使用的handler。而是AnimationHandler,它事实上本质就是一个Runable)和Looper去将动画运行出来),通过代码我们发现它调了JNI的代码,只是这个我们不用关心。我们直接看ObjectAnimator.start()

private void start(boolean playBackwards) {
        if(Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        } else {
            this.mPlayingBackwards = playBackwards;
            this.mCurrentIteration = 0;
            this.mPlayingState = 0;
            this.mStarted = true;
            this.mStartedDelay = false;
            ((ArrayList)sPendingAnimations.get()).add(this);
            if(this.mStartDelay == 0L) {
                this.setCurrentPlayTime(this.getCurrentPlayTime());
                this.mPlayingState = 0;
                this.mRunning = true;
                if(this.mListeners != null) {
                    ArrayList animationHandler = (ArrayList)this.mListeners.clone();
                    int numListeners = animationHandler.size();

                    for(int i = 0; i < numListeners; ++i) {
                        ((AnimatorListener)animationHandler.get(i)).onAnimationStart(this);
                    }
                }
            }

            ValueAnimator.AnimationHandler var5 = (ValueAnimator.AnimationHandler)sAnimationHandler.get();
            if(var5 == null) {
                var5 = new ValueAnimator.AnimationHandler(null);
                sAnimationHandler.set(var5);
            }

            var5.sendEmptyMessage(0);
        }
    }

 private static final ThreadLocal<ArrayList<ValueAnimator>> sAnimations = new ThreadLocal() {
        protected ArrayList<ValueAnimator> initialValue() {
            return new ArrayList();
        }
    };
    private static final ThreadLocal<ArrayList<ValueAnimator>> sPendingAnimations = new ThreadLocal() {
        protected ArrayList<ValueAnimator> initialValue() {
            return new ArrayList();
        }
    };
    private static final ThreadLocal<ArrayList<ValueAnimator>> sDelayedAnims = new ThreadLocal() {
        protected ArrayList<ValueAnimator> initialValue() {
            return new ArrayList();
        }
    };
    private static final ThreadLocal<ArrayList<ValueAnimator>> sEndingAnims = new ThreadLocal() {
        protected ArrayList<ValueAnimator> initialValue() {
            return new ArrayList();
        }
    };
    private static final ThreadLocal<ArrayList<ValueAnimator>> sReadyAnims = new ThreadLocal() {
        protected ArrayList<ValueAnimator> initialValue() {
            return new ArrayList();
        }
    };

这里系统怎么计算每一帧的动画的呢。看看下面的代码
void animateValue(float fraction) {
        fraction = this.mInterpolator.getInterpolation(fraction);
        this.mCurrentFraction = fraction;
        int numValues = this.mValues.length;

        int numListeners;
        for(numListeners = 0; numListeners < numValues; ++numListeners) {
            this.mValues[numListeners].calculateValue(fraction);
        }

        if(this.mUpdateListeners != null) {
            numListeners = this.mUpdateListeners.size();

            for(int i = 0; i < numListeners; ++i) {
                ((ValueAnimator.AnimatorUpdateListener)this.mUpdateListeners.get(i)).onAnimationUpdate(this);
            }
        }

    }

只是我们知道要改变动画。一定调用了get/set方法,那我们重点看下这相关的代码。这段代码在setProperty方法里面
 public void setProperty(Property property) {
        if(this.mValues != null) {
            PropertyValuesHolder valuesHolder = this.mValues[0];
            String oldName = valuesHolder.getPropertyName();
            valuesHolder.setProperty(property);
            this.mValuesMap.remove(oldName);
            this.mValuesMap.put(this.mPropertyName, valuesHolder);
        }

        if(this.mProperty != null) {
            this.mPropertyName = property.getName();
        }

        this.mProperty = property;
        this.mInitialized = false;
    }
这里有一个PropertyValuesHolder,顾名思义这是一个操作数据的类,和我们的adapter的Holder几乎相同,该方法的get方法主要用到了反射。
 private void setupValue(Object target, Keyframe kf) {
        if(this.mProperty != null) {
            kf.setValue(this.mProperty.get(target));
        }

        try {
            if(this.mGetter == null) {
                Class e = target.getClass();
                this.setupGetter(e);
            }

            kf.setValue(this.mGetter.invoke(target, new Object[0]));
        } catch (InvocationTargetException var4) {
            Log.e("PropertyValuesHolder", var4.toString());
        } catch (IllegalAccessException var5) {
            Log.e("PropertyValuesHolder", var5.toString());
        }

    }

代码就看到这,有兴趣的能够去看下源代码



使用属性动画须要注意的事项

  • 使用帧动画时。当图片数量较多且图片分辨率较大的时候easy出现OOM,需注意,尽量避免使用帧动画。

  • 使用无限循环的属性动画时。在Activity退出时即使停止,否则将导致Activity无法释放从而造成内存泄露
  • View动画是对View的影像做动画,并非真正的改变了View的状态。因此有时候会出现动画完毕后View无法隐藏(setVisibility(View.GONE)失效),这时候调用view.clearAnimation()清理View动画就可以解决。

  • 不要使用px。使用px会导致不同设备上有不同的效果。
  • View动画是对View的影像做动画,View的真实位置没有变动,也就导致点击View动画后的位置触摸事件不会响应,属性动画不存在这个问题。
  • 使用动画的过程中,使用硬件加速能够提高动画的流畅度。

  • 动画在3.0下面的系统存在兼容性问题。特殊场景可能无法正常工作,需做好适配工作。












  • 查看全文
  • 相关阅读:
    Robotium 测试方法
    T-SQL—理解CTEs
    SQLServer复制(二)--事务代理作业
    数据库复制(一)--复制介绍
    小议如何使用APPLY
    优化SQLServer——表和分区索引(二)
    关于UNPIVOT 操作符
    XML 在SQLServer中的使用
    列存储索引1:初识列存储索引
    T-SQL性能调整(一)--编译和重新编译
  • 原文地址:https://www.cnblogs.com/slgkaifa/p/7388226.html
  • Copyright © 2011-2022 走看看