动画效果一直是人机交互中非常重要的部分,与死板、突兀的显示效果不同,动画效果的加入,让交互变得更加友好,特别是在提示、引导类的场景中,合理地使用动画能让用户获得更加愉悦的使用体验【分类】
帧动画、补间动画、属性动画、过度动画 关键:其中插值器的使用、布局动画的使用
一、Android View动画框架
Animation框架定义了透明度、旋转、缩放、位移等几种常见的动画实现原理:
每次绘制View时,ViewGroup中的drawChild函数获取该view的Animation的Transformation值,然后调用canvas.concat(transformToApply.getMatrix())通过矩阵运算完成帧动画,如果动画没有完成,就继续调用invalidate() 函数,启动下次绘制来驱动动画,从而完成整个动画的绘制。
二、帧动画
帧动画就是一张张图片不同的切换,形成的动画效果。一般手机的开机动画,应用的等待动画等都是帧动画,因为只需要几张图片轮播,极其节省资源,如果真的设计成动画,那么是很耗费资源的事。在res目录下新建一个drawable文件夹并定义xml文件,子节点为 animation-list,在这里定义要显示的图片和每张图片的显示时长。
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/g1" android:duration="200" />
<item android:drawable="@drawable/g2" android:duration="200" />
<item android:drawable="@drawable/g3" android:duration="200" />
<item android:drawable="@drawable/g4" android:duration="200" />
<item android:drawable="@drawable/g5" android:duration="200" />
<item android:drawable="@drawable/g6" android:duration="300" />
<item android:drawable="@drawable/g7" android:duration="400" />
<item android:drawable="@drawable/g8" android:duration="500" />
<item android:drawable="@drawable/g9" android:duration="200" />
<item android:drawable="@drawable/g10" android:duration="200" />
<item android:drawable="@drawable/g11" android:duration="200" />
</animation-list>
在屏幕上播放帧动画,需要布局文件有一个ImageView来显示动画图片
public
class MainActivity extends Activity {
@Override
protected void onCreate(
Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(
R.layout.activity_main);
ImageView iv = (
ImageView) findViewById(
R.id.iv);
iv.setBackgroundResource(
R.drawable.frameanimation);
AnimationDrawable ad = (
AnimationDrawable) iv.getBackground();
ad.start();
}
}
三、补间动画(视图动画)
组件由原始状态向终极状态转变时,为了让过渡更自然,而自动生成的动画叫做补间动画。主要是在
Android 3.0之前最大的缺陷就是不具备交互性位移、旋转、缩放、透明
public class MainActivity extends Activity {
private ImageView iv;
private TranslateAnimation ta;
private RotateAnimation ra;
private ScaleAnimation sa;
private AlphaAnimation aa;
@
Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv);
}
public void translate(View v){
ta =
new TranslateAnimation(Animation.RELATIVE_TO_SELF,
0.5f, Animation.RELATIVE_TO_SELF,
2,
Animation.RELATIVE_TO_SELF,
0.5f, Animation.RELATIVE_TO_SELF,
2);
ta.setDuration(
2000);
ta.setRepeatCount(
1);
ta.setRepeatMode(Animation.REVERSE);
ta.setFillAfter(
true);
iv.startAnimation(ta);
}
public void rotate(View v){
ra =
new RotateAnimation(
20,
360,
Animation.RELATIVE_TO_SELF,
0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
ra.setDuration(
2000);
ra.setRepeatCount(
1);
ra.setRepeatMode(Animation.REVERSE);
iv.startAnimation(ra);
}
public void scale(View v){
sa =
new ScaleAnimation(
0.5f,
2,
0.1f,
3,
Animation.RELATIVE_TO_SELF,
0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
sa.setDuration(
2000);
sa.setRepeatCount(
1);
sa.setRepeatMode(Animation.REVERSE);
iv.startAnimation(sa);
}
public void alpha(View v){
aa =
new AlphaAnimation(
0,
1);
aa.setDuration(
2000);
aa.setRepeatCount(
1);
aa.setRepeatMode(Animation.REVERSE);
iv.startAnimation(aa);
}
public void fly(View v){
AnimationSet
set =
new AnimationSet(
false);
set.addAnimation(aa);
set.addAnimation(ra);
set.addAnimation(sa);
set.addAnimation(ta);
iv.startAnimation(
set);
}
}
四、属性动画
补间动画,只是一个动画效果,组件其实还在原来的位置上,xy没有改变。属性动画是组件的位置发生了真实的改变,而且在动画的过程中组件的位置是实时改变的,可以相应组件事件。使用最多的就是 AnimatorSet和ObjectAnimator配合:
使用ObjectAnimator进行更精细化控制,只控制一个对象的一个属性值使多个ObjectAnimator组合到AnimatorSet形成一个动画 ObjectAnimator可以自动驱动:
调用setFrameDelay()设置动画帧之间的间隙时间,调整帧率,减少动画绘制过程中频繁绘制,在不影响动画效果的情况下减少CPU资源消耗 属性动画基本可以实现左右动画但是View的该属性一定要具有 set和get 方法
1.ObjectAnimator
内部是通过反射机制实现的,所以该属性一定要具有 set和get方法
一些常用的可以直接使用的属性:
translationX 和translationY: 控制View从父容器的左上角的偏移rotation、rotationX、rotationY : 控制View围绕 “支点”进行2D和3D旋转scaleX、scaleY : 控制View围绕 “支点”进行2D缩放pivotX、pivotY : 控制View的“支点”位置,默认为View的中心点x、y : 这两个属性描述了View对象在父容器中的最终位置,它是最初左上角坐标和translationX 、translationY值的累计和alpha : 透明度
public class MainActivity extends Activity {
private ImageView iv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv = (ImageView) findViewById(R.id.iv);
iv.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.
this,
"点不到我",
0).show();
}
});
}
public void translate(View v){
ObjectAnimator oa = ObjectAnimator.ofFloat(iv,
"translationX",
10,
70,
20,
100);
oa.setDuration(
2000);
oa.setRepeatCount(
1);
oa.setRepeatMode(ValueAnimator.REVERSE);
oa.start();
}
public void scale(View v){
ObjectAnimator oa = ObjectAnimator.ofFloat(iv,
"scaleX",
1,
1.6f,
1.2f,
2);
oa.setDuration(
2000);
oa.start();
}
public void alpha(View v){
ObjectAnimator oa = ObjectAnimator.ofFloat(iv,
"alpha",
0,
0.6f,
0.2f,
1);
oa.setDuration(
2000);
oa.start();
}
public void rotate(View v){
ObjectAnimator oa = ObjectAnimator.ofFloat(iv,
"rotationY",
0,
180,
90,
360);
oa.setDuration(
2000);
oa.setRepeatCount(
1);
oa.setRepeatMode(ValueAnimator.REVERSE);
oa.start();
}
public void fly(View v){
AnimatorSet set =
new AnimatorSet();
ObjectAnimator oa1 = ObjectAnimator.ofFloat(iv,
"translationX",
10,
70,
20,
100);
oa1.setDuration(
2000);
oa1.setRepeatCount(
1);
oa1.setRepeatMode(ValueAnimator.REVERSE);
ObjectAnimator oa2 = ObjectAnimator.ofFloat(iv,
"translationY",
10,
70,
20,
100);
oa2.setDuration(
2000);
oa2.setRepeatCount(
1);
oa2.setRepeatMode(ValueAnimator.REVERSE);
ObjectAnimator oa3 = ObjectAnimator.ofFloat(iv,
"scaleX",
1,
1.6f,
1.2f,
2);
oa3.setDuration(
2000);
oa3.setRepeatCount(
1);
oa3.setRepeatMode(ValueAnimator.REVERSE);
ObjectAnimator oa4 = ObjectAnimator.ofFloat(iv,
"rotation",
0,
180,
90,
360);
oa4.setDuration(
2000);
oa4.setRepeatCount(
1);
oa4.setRepeatMode(ValueAnimator.REVERSE);
set.playTogether(oa1, oa2, oa3, oa4);
set.start();
}
public void xml(View v){
Animator animator = AnimatorInflater.loadAnimator(
this, R.animator.objanimator);
animator.setTarget(iv);
animator.start();
}
}
可以用xml配置属性动画只需要在res目录下创建一个property animator属性动画文件
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
android:propertyName="translationX"
android:duration="200"
android:repeatCount="1"
android:repeatMode="reverse"
android:valueFrom="-100"
android:valueTo="100"
>
</objectAnimator>
</set>
2. 如果属性没有set 和 get方法的解决方法
方案一: 通过自定义一个属性类或者包装类,类间接的给这个属性增加 get、set方法
public class WrapperView {
private View mTarget;
public WrapperView(View target) {
this.mTarget = target;
}
public int getWidth(){
return mTarget.getLayoutParams().width;
}
public void setWidth(int width){
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
public void use(){
WrapperView wrapper =
new WrapperView(mButton);
ObjectAnimator.ofInt(wrapper,
"width",
500).setDuration(
5000).start();
}
}
方案二: 通过ValueAnimator实现
3. ValueAnimator(值动画)
ObjectAnimator 也是集成自ValueAnimatorValueAnimator本身不提供任何动画效果,它更像一个数值发生器,用来产生具有一定规律的数字,调用者通过这个过程来执行自己特定的动画逻辑
ValueAnimator animator = ValueAnimator.ofInt(
0,
100);
animator.setTarget(view);
animator.setDuration(
1000).start();
animator.addUpdateListener(
new ValueAnimator.AnimatorUpdateListener() {
@
Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = animation.getAnimatedFraction();
}
});
4. 动画监听
监听动画的过程执行一些操作
ObjectAnimator anim = ObjectAnimator.ofFloat(view,
"alpha",
0.5f);
anim.addListener(
new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
anim.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationEnd(animation);
}
});
这里边有一个重要的思想,如果一个接口需要实现的方法比较多,而通常时候又不需要实现那么多方法,导致代码乱乱的,这个时候,可以写一个抽象类来实现这个接口的所有方法,由于接口的实现类也是接口类型,所以使用的时候就可以只复写这个抽象类中感兴趣的方法就好了
5. View的animate方法
在3.0之后,Google给View增加了animate方法来直接驱动属性动画
view.animate()
.alpha(
0)
.scaleX(
1)
.x(
300)
.y(
200)
.setDuration(
1000)
.withStartAction(
new Runnable() {
@Override
public void run() {
}
})
.withEndAction(
new Runnable() {
@Override
public void run() {
runOnUiThread(
new Runnable() {
@Override
public void run() {
}
});
}
})
.start();
6. Android 布局动画
布局动画是指作用在ViewGroup上,给ViewGroup增加View时添加一个动画过渡效果最简单的布局动画是在ViewGroup的xml中使用如下属性打开布局动画
android:animateLayoutChanges="true"但是这个默认的动画效果无法替换 使用LayoutAnimationController类来自定义一个过渡效果
LinearLayout ll = (LinearLayout)findViewById(R.id.ll);
ScaleAnimation sa =
new ScaleAnimation(
0,
1,
0,
1);
sa.setDuration(
2000);
LayoutAnimationController lac =
new LayoutAnimationController(sa,
0.5f);
lac.setOrder(LayoutAnimationController.ORDER_NORMAL);
ll.setLayoutAnimation(lac);
7. Interpolators(插值器 )
插值器是动画中一个非常重要的概念,通过插值器可以定义动画变换速率,其作用主要是控制目标变量的变化值进行对应的变化AccelerateDecelerateInterpolator开始与结束的地方速率改变比较慢,在中间的时候加速 AccelerateInterpolator开始的地方速率改变比较慢,然后开始加速 AnticipateInterpolator开始的时候向后然后向前甩 AnticipateOvershootInterpolator开始的时候向后然后向前甩一定值后返回最后的值 BounceInterpolator动画结束的时候弹起 CycleInterpolator循环播放特定的次数,速率改变沿着正弦曲线 DecelerateInterpolator在开始的地方快然后慢 创建的时候,可以传factor值,如DecelerateInterpolator(2f): LinearInterpolator以常量速率改变 OvershootInterpolator向前甩一定值后再回到原来位置 创建的时候,可以传tension值,OvershootInterpolator(0.8f):
五、自定义动画
就是现有的透明度,旋转,平移,缩放等行为组合起来仍然不能满足你的话,可以自定义一些更炫的动画
public class CustomAnim extends Animation {
private int mCenterWidth;
private int mCenterHeight;
private Camera mCamera =
new Camera();
private float mRotateY =
0.0f;
@Override
public void initialize(int width,
int height,
int parentWidth,
int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
setDuration(
2000);
setFillAfter(
true);
setInterpolator(
new BounceInterpolator());
mCenterWidth = width /
2;
mCenterHeight = height /
2;
}
public void setRotateY(float rotateY) {
mRotateY = rotateY;
}
@Override
protected void applyTransformation(
float interpolatedTime,
Transformation t) {
final Matrix matrix = t.getMatrix();
mCamera.save();
mCamera.rotateY(mRotateY * interpolatedTime);
mCamera.getMatrix(matrix);
mCamera.restore();
matrix.preTranslate(mCenterWidth, mCenterHeight);
matrix.postTranslate(-mCenterWidth, -mCenterHeight);
}
}
六、Android 5.X SVG 矢量动画机制
可伸缩矢量图形(Scalable Vector Graphics)定义用于网络的基于矢量的图形使用XML格式定义图形图像在放大或改变尺寸的情况下其图形质量不会有损失与Bitmap对比,SVG最大的优点就是方法不失真,而且不需要为不同分辨率设计多套图标
七、点击view显示隐藏其他View带动画的一个小例子
public class DropTest extends Activity {
private LinearLayout mHiddenView;
private float mDensity;
private int mHiddenViewMeasuredHeight;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.drop);
mHiddenView = (LinearLayout) findViewById(R.id.hidden_view);
mDensity = getResources().getDisplayMetrics().density;
mHiddenViewMeasuredHeight = (
int) (mDensity *
40 +
0.5);
}
public void llClick(View view) {
if (mHiddenView.getVisibility() == View.GONE) {
animateOpen(mHiddenView);
}
else {
animateClose(mHiddenView);
}
}
private void animateOpen(final View view) {
view.setVisibility(View.VISIBLE);
ValueAnimator animator = createDropAnimator(
view,
0,
mHiddenViewMeasuredHeight);
animator.start();
}
private void animateClose(final View view) {
int origHeight = view.getHeight();
ValueAnimator animator = createDropAnimator(view, origHeight,
0);
animator.addListener(
new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
view.setVisibility(View.GONE);
}
});
animator.start();
}
private ValueAnimator createDropAnimator(
final View view, int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams =
view.getLayoutParams();
layoutParams.height = value;
view.setLayoutParams(layoutParams);
}
});
return animator;
}
}