我正在参加 CSDN 2015博客之星评选 感恩分享活动,如果觉得文章还不错,请投个票鼓励下吧:http://vote.blog.csdn.net/blogstar2015/candidate?username=tianjian4592
本篇主要讲Animation 和 Property Animation的使用,最后会讲QQ管家桌面火箭作为例子:
在Android中开发动效有两套框架可以使用,分别为 Animation 和 Property Animation;
相对来说,Animator比Animation要强大太多,两者之间的主要区别在于:
Property Animation需要Android API level 11的支持,当然可以使用nineoldandroids.jar进行兼容,而View Animation则是最原始的版本。
Property Animation适用于所有的Object对象,而View Animation则只能应用于View对象。
区别三,实际改变不一样:Animation:改变的是view的绘制位置,而非实际属性;
Property Animation:改变的是view的实际属性;
如:用两套框架分别对一个button做位移动画,用animation处理之后的点击响应会在原处,而用Property Animation处理后的点击响应会在最终位置处;
一:Animation
总的来说可以分为:Tween Animation(补间动画) 和Frame Animation(帧动画)
帧动画(Frame Animation):
帧动画其实就是按照一定的时间间隔进行快速的图片切换,达到视觉上的动画效果,这种动画相对来说缺点较多,比如:
1.根据动画的复杂度需要切多张图,这样就不可避免的增大了包大小
2.由于是多张图,运行时需要加载到内存,那么又会增大运行时的内存大小
所以如果在动效上有别的方案选择,个人不太建议使用帧动画的实现方式,当然有时候不得不用,这时我们可以尽可能的在效果连贯的基础上减少图片张数;
帧动画的实现也相对简单,就举一个例子,但是例子之中有几条帧动画的使用规则,是我以前在使用的过程中遇到问题后总结出来的;
1.在drawable目录下xml中定义如下文件,节点为animation-list,oneshot代表是否执行一次,duration代表每张图对应展示时间:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true"> <item android:drawable="@drawable/rocket_thrust1" android:duration="200" /> <item android:drawable="@drawable/rocket_thrust2" android:duration="200" /> <item android:drawable="@drawable/rocket_thrust3" android:duration="200" /> </animation-list>
2.然后将该drawable设置给对应的imageview
3.在activity中animDrawable = (AnimationDrawable) imageView.getBackground();
4.在onWindowFocusChanged()中调用:
animDrawable.stop();
animDrawable.start();
如果不这么做,那么在性能比较差的机器上很可能就会出现没有播放的情况,因为只显示出了第一帧,问题在于动画没有和view完成关联,所以不要在onCreate中去调用启动,而需要在onWindowFocusChanged中进行调用;
在极特殊的情况下如果还无法播放,则可以mHandler.postDelay 200 毫秒作为终极杀手锏,当然只要按照我上面的步骤,应该不会出现问题;
好了,帧动画最大的问题是按照上面的步骤开发,接下来看补间动画(Tween Animation)
补间动画(Tween Animation):
Animation下有五个子类:AlphaAnimation(渐变),RotateAnimation(旋转),ScaleAnimation(缩放),TranslateAnimation(位移)
在实现原理上除了AlphaAnimation是动态的去改变view的alpha值,其他三个都是去改变里面的Matrix(矩阵,后面会专门讲);
在Animation框架里,主要的类主要有Animation和Transformation、Interpolator(插值器,后面也会专门讲)
Transformation里面主要对alpha和matrix进行了封装,而改变view的透明度就是改变alpha,移动、旋转、缩放甚至错切则都是改变matrix
Animation里有一个重要的方法applyTransformation,实现自定义Animation也主要是实现这个方法:
以AlphaAnimation为例:
/** * Changes the alpha property of the supplied {@link Transformation} */ @Override protected void applyTransformation(float interpolatedTime, Transformation t) { final float alpha = mFromAlpha; t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime)); }
渐变动画只需要在里面根据当前的interpolatedTime(已经由插值器转换后的值)动态计算出对应的alpha,重新设置到Transformation中即可;
Animation的使用也相对比较简单,可以通过xml配置,也可以通过代码生成:
1.xml配置:
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false" > <scale> <!-- 单次运行时间 --> android:duration="500" <!-- 运行完成后是否保持结束时的状态 --> android:fillAfter="true" <!-- 运行完成后是否回到开始时的状态 --> android:fillBefore="false" <!-- 初始时大小,1代表原大小,0代表无 --> android:fromXScale="1" android:fromYScale="1" <!-- 使用的插值器,控制运行过程中的速率 --> android:interpolator="@android:anim/accelerate_interpolator" <!-- 相对中心点,50%代表自身中心,50%p代表相对父view的中心 --> android:pivotX="50%" android:pivotY="50%" <!-- 重复的次数,infinite代表永久循环 --> android:repeatCount="infinite" <!-- 重复的模式, restart代表重新开始,reverse代表反转--> android:repeatMode="restart" <!-- 延迟多久后开始 --> android:startOffset="100" <!-- 要到达的缩放比例 --> android:toXScale="0" android:toYScale="0" /> </scale> <translate android:duration="550" <!-- 相对当前位置的像素距离 --> android:fromYDelta="300" android:interpolator="@android:anim/accelerate_interpolator" android:toYDelta="0" /> <alpha android:duration="550" android:fromAlpha="0" android:toAlpha="1" /> </set>
Animation animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.app_clean_animation); view.startAnimation(animation);
2.纯代码生成:
// false代表里面的子animation不共用一个插值器 AnimationSet animationSet = new AnimationSet(false); // 从alpha 完全透明变为完全不透明 AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1); alphaAnimation.setDuration(500); alphaAnimation.setFillAfter(true); // 运行完成后是否回到开始时的状态 alphaAnimation.setFillBefore(false); alphaAnimation.setInterpolator(new AccelerateInterpolator()); // 重复的次数,infinite代表永久循环 alphaAnimation.setRepeatCount(Animation.INFINITE); // 重复的模式, restart代表重新开始,reverse代表反转 alphaAnimation.setRepeatMode(Animation.RESTART); // 给动画设置对应的监听,可以在动画开始、结束或重复执行时做对应操作 alphaAnimation.setAnimationListener(new AnimationListener() { @Override public void onAnimationStart(Animation animation) { //开始时候回调 } @Override public void onAnimationRepeat(Animation animation) { //重复执行时回调 } @Override public void onAnimationEnd(Animation animation) { //一轮结束时回调 } }); RotateAnimation rotateAnimation = new RotateAnimation(0, 360); rotateAnimation.setDuration(500); animationSet.addAnimation(alphaAnimation); animationSet.addAnimation(rotateAnimation); view.startAnimation(animationSet);
好了,animation就讲到这里,上面的注释应该比较详尽了,主要是弄清楚其使用方式和使用场景!
二:Property Animation(属性动画)
属性动画,它更改的是对象的实际属性,在View Animation(Tween Animation)中,其改变的是View的绘制效果,真正的View的属性保持不变,比如无论你在对话中如何缩放Button的大小,Button的有效点击区域还是没有应用动画时的区域,其位置与大小都不变。而在Property Animation中,改变的是对象的实际属性,如Button的缩放,Button的位置与大小属性值都改变了。而且Property Animation不止可以应用于View,还可以应用于任何对象。Property Animation只是表示一个值在一段时间内的改变,当值改变时要做什么事情完全是你自己决定的。
在Property Animation中,可以对动画应用以下属性:
1.TimeInterpolator(插值器,和低版本的Interpolator一样):属性值的计算方式,如先快后慢
2.TypeEvaluator:根据属性的开始、结束值与TimeInterpolator计算出的因子计算出当前时间的属性值
3.Repeat Count and behavoir:重复次数与方式,如播放3次、5次、无限循环,可以此动画一直重复,或播放完时再反向播放
4.Animation sets:动画集合,即可以同时对一个对象应用几个动画,这些动画可以同时播放也可以对不同动画设置不同开始偏移
5.Frame refreash delay:多少时间刷新一次,即每隔多少时间计算一次属性值,默认为10ms,最终刷新时间还受系统进程调度与硬件的影响
上面都是些概念,但这些东西不必刻意去记,或去理解插值器这样的比较生涩的概念,我们只需要使用他最实用的部分,并熟悉动画的实现套路;所以对于属性动画主要带大家熟悉两个类,ValueAnimator和ObjectAnimator,通过这两个类我们平常遇到的动效大部分都能够加以解决;
1.ValueAnimator:
ValueAnimator包含了 Property Animation 动画的所有核心功能,如动画时间,开始、结束属性值,相应时间属性值计算方法等。
在我看来,使用 ValueAnimator 只是为我们创建了一个过程,我们可以用ValueAnimator.ofXXX()进行创建,然后通过各种setXXX()给其设定过程的时间,速率变化效果等,然后通过addUpdateListener()中去拿这个过程中回调回来的中间值,然后使用这些中间值改变view的属性形成动态效果;
上面这句话通过代码表现如下:
比如我们使用 ValueAnimator 在2S内将view横向拉长为2倍,纵向压缩为0:
// 在2S内将view横向拉长为2倍,纵向压缩为0 // 创建0-1的一个过程,任何复杂的过程都可以采用归一化,然后在addUpdateListener回调里去做自己想要的变化 ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); // 设置过程的时间为2S valueAnimator.setDuration(SCALE_ANIM_TIME); // 设置这个过程是速度不断变快的 valueAnimator.setInterpolator(new AccelerateInterpolator()); // 这个过程中不断执行的回调 valueAnimator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { // 不断回调的在0-1这个范围内,经过插值器插值之后的返回值 float value = (Float) animation.getAnimatedValue(); // ViewHelper可直接用于修改view属性 // 将宽在2S内放大一倍 ViewHelper.setScaleX(mTestImage, 1 + value); // 将高在2S内压缩为0 ViewHelper.setScaleY(mTestImage, 1 - value); } }); // AnimatorListenerAdapter是AnimatorListener的空实现,根据需要覆盖的方法自行选择 valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); Toast.makeText(getApplicationContext(), "onAnimationStart", Toast.LENGTH_SHORT) .show(); } @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); Toast.makeText(getApplicationContext(), "onAnimationEnd", Toast.LENGTH_SHORT) .show(); } @Override public void onAnimationCancel(Animator animation) { super.onAnimationCancel(animation); } @Override public void onAnimationRepeat(Animator animation) { super.onAnimationRepeat(animation); } }); valueAnimator.start();
动画其实就是在一个时间段内不断去改变view的一些属性值,这些属性值动态变化,不断重绘的过程,也就形成了我们所看到的动效;
那么基于此,我们可以知道这个过程都是通过时间来控制的,时间走过的比例肯定在 0-1 之间,如果我们直接用这个走过的时间比例去算当前属性值,那么整个过程则是匀速(线性)的;
那么在这个基础上,我们想我们的过程是非线性的,我们该怎么办呢,那么只需要对这个时间比例加以加工,具体请看下图:
横轴就是经过的时间比例,肯定是匀速的从0-1,纵轴则是时间比例经过加工后的插值,这个对应过程则是Interpolator(插值器)对应的过程;
减速线则对应DecelerateInterpolater,因为它的斜率越来越平,所以瞬时速度越来越小,则形成了减速效果;
其他的效果类似,目前android里提供的插值器有如下一些:
AnimatorSet animatorSet = new AnimatorSet(); // 将view在x方向上从原大小放大2倍 ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(mTestImage, "scaleX", 1, 2); scaleXAnimator.setDuration(SCALE_ANIM_TIME); // 将view在y方向上从原大小压缩为0 ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(mTestImage, "scaleY", 1, 0); scaleYAnimator.setDuration(SCALE_ANIM_TIME); // 设置加速模式 animatorSet.setInterpolator(new AccelerateInterpolator()); // 设置回调,当然也可以设置在单独的animator上,eg:scaleXAnimator animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); Toast.makeText(getApplicationContext(), "onAnimationStart", Toast.LENGTH_SHORT) .show(); } @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); Toast.makeText(getApplicationContext(), "onAnimationEnd", Toast.LENGTH_SHORT) .show(); } @Override public void onAnimationCancel(Animator animation) { super.onAnimationCancel(animation); } @Override public void onAnimationRepeat(Animator animation) { super.onAnimationRepeat(animation); } }); animatorSet.playTogether(scaleXAnimator, scaleYAnimator); animatorSet.start();ObjectAnimator 是ValueAnimator 的子类,可以直接改变Object的属性,目前可供改变的属性主要有:
// 使用WindowManager添加window需要android.permission.SYSTEM_ALERT_WINDOW权限 mWindowManager = (WindowManager) this.getSystemService(WINDOW_SERVICE); mIconLayoutParams = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, LayoutParams.TYPE_SYSTEM_ALERT, LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); mIconLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
private void moveIconContent(float x, float y) { mIconLayoutParams.x = (int) x; // y值要减去通知栏的高度 mIconLayoutParams.y = (int) y - mStatusBarHeight; fixLayoutParams(mIconLayoutParams); mWindowManager.updateViewLayout(mIconContent, mIconLayoutParams); }
float toX = 0; if (rawX < totalWidth / 2) { toX = 0; } else { toX = totalWidth - view.getWidth() / 2; } // 创建从当前x到目标x的过程 ValueAnimator mSlideToBoundaryAnim = ValueAnimator.ofFloat(rawX, toX); // 设置动画运行时间 mSlideToBoundaryAnim.setDuration(SLIDE_TO_BOUNDARY_TIME); // 减速效果,为了贴边的时候显得柔和 mSlideToBoundaryAnim.setInterpolator(new DecelerateInterpolator()); mSlideToBoundaryAnim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { // 拿出当前时刻回调的值 float currentValue = (Float) valueAnimator.getAnimatedValue(); // 将值不断设给浮动小火箭的layoutParams,从而形成动效 mIconLayoutParams.x = (int) currentValue; fixLayoutParams(mIconLayoutParams); // 改变WindowManager添加的view的位置 mWindowManager.updateViewLayout(mIconContent, mIconLayoutParams); } }); mSlideToBoundaryAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { // 在动画结束的时候把火箭重新设置回之前的小icon // 当然可以给两个icon加上更加柔和的切换动画,我这里就不加了 mIconImage.setImageDrawable(mCircleDrawable); } }); mSlideToBoundaryAnim.start();
// 如果在发射区,则震动 if (isInLaunchSpace(event)) { log("---In Launch Space"); mVibrator.vibrate(VIBRATOR_PATTERN, 0); } else { // 否则取消震动 log("---not In Launch Space"); if (mVibrator != null) mVibrator.cancel(); }
1.底座相关消失
2.底部烟雾出现
3.火箭向上飞,向上飞的时候烟雾线往上拉出
4.移除整个火箭页面
mRocketPad.setVisibility(View.INVISIBLE); mRocketFire.setVisibility(View.INVISIBLE); Animation alphaIn = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.alpha_in); alphaIn.setAnimationListener(new AnimationListenerAdapter() { @Override public void onAnimationEnd(Animation animation) { // 底部烟雾出现完成,飞机出现并执行火箭往上飞 mBottomRocket.setVisibility(View.VISIBLE); ObjectAnimator transY = ObjectAnimator.ofFloat(mBottomRocket, "translationY", 0, -mScreenHeight); transY.setDuration(1000); transY.setInterpolator(new DecelerateInterpolator()); // 向上飞的时候烟雾线往上拉出 ObjectAnimator smokeTopAnim = ObjectAnimator.ofFloat(mSmoketop, "scaleY", 0, 1); // 设置缩放基准点为 ViewHelper.setPivotX(mSmoketop, mSmoketop.getMeasuredWidth() * 0.5f); ViewHelper.setPivotY(mSmoketop, mSmoketop.getMeasuredHeight()); smokeTopAnim.setDuration(700); ObjectAnimator smokeAlphaAnim = ObjectAnimator.ofFloat(mSmoketop, "alpha", 0, 1); smokeTopAnim.setInterpolator(new DecelerateInterpolator()); smokeTopAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { mSmoketop.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animator animation) { // 移除火箭页面,添加icon if (mIsRocketContentAdded) { mWindowManager.removeView(mRocketContent); mIsRocketContentAdded = false; } if (!mIsIconContentAdded) { if (mIconLayoutParams.x < mScreenWidth / 2) { mIconLayoutParams.x = 0; } else { mIconLayoutParams.x = mScreenWidth - mIconWidth / 2; } mIconImage.setImageDrawable(mCircleDrawable); mWindowManager.addView(mIconContent, mIconLayoutParams); mIsIconContentAdded = true; } // 将view的显示状态重置 mSmokePad.setVisibility(View.INVISIBLE); mSmoketop.setVisibility(View.INVISIBLE); mRocketPad.setVisibility(View.VISIBLE); mRocketFire.setVisibility(View.VISIBLE); } }); AnimatorSet animatorSet = new AnimatorSet(); // 同时执行上面两个效果 animatorSet.playTogether(transY, smokeTopAnim, smokeAlphaAnim); animatorSet.start(); } }); mSmokePad.setVisibility(View.VISIBLE); mSmokePad.startAnimation(alphaIn);
源码下载地址:http://download.csdn.net/download/tianjian4592/8496705