1、属性动画
使用 ObjectAnimator 的时候,有一点非常重要,那就是操纵的属性要具有 get,set 方法,不然ObjectAnimator 就无法起效。下面这些事一些常用的,可以直接使用属性动画的属性值。translationX ,translationY,scaleX,ScaleY,pivotX,pivotY,x,y,alpha。
但是,有些属性是没有 get set 方法的,那么难道我们就不能用了吗?不是的。
private static class WrapperView{
private View mTarget;
public WrapperView(View target){
mTarget = target;
}
public getWidth(){
return mTarget.getLayoutParams().width;
}
public void setWidth(int width){
mTarget.getLayoutParams().width = width;
mTarget.requestLayput();
}
}
// 通过以上代码给属性包了一层,并给它提供了 get set 的方法。使用如下:
WrapperView wrapper = new WrapperView(mButton);
ObjectAnimator.ofInt(wrapper,"width",500).setDuration(5000).start();
类似的,如果想实现视图动画中的AnimationSet,在属性动画中可以使用 PropertyValuesHolder 来实现。
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationX",300f);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleX",1f, 0, 1f);
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleY",1f, 0, 1f);
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationX",1f, 0, 1f);
ObjectAnimator.ofPropertyValuesHolder(view, pvh1, pvh2, pvh3).setDuration(1000).start();
实例:
只展示 activity 中的代码。点击之后,自身变淡,四个方向上弹出四个图标。再点击一次,收回四个图标。
public class PropertyTest extends Activity implements View.OnClickListener {
private int[] mRes = {R.id.imageView_a, R.id.imageView_b, R.id.imageView_c,
R.id.imageView_d, R.id.imageView_e};
private List<ImageView> mImageViews = new ArrayList<ImageView>();
private boolean mFlag = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.property);
for (int i = 0; i < mRes.length; i++) {
ImageView imageView = (ImageView) findViewById(mRes[i]);
imageView.setOnClickListener(this);
mImageViews.add(imageView);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.imageView_a:
if (mFlag) {
startAnim();
} else {
closeAnim();
}
break;
default:
Toast.makeText(PropertyTest.this, "" + v.getId(),
Toast.LENGTH_SHORT).show();
break;
}
}
private void closeAnim() {
ObjectAnimator animator0 = ObjectAnimator.ofFloat(mImageViews.get(0),
"alpha", 0.5F, 1F);
ObjectAnimator animator1 = ObjectAnimator.ofFloat(mImageViews.get(1),
"translationY", 200F, 0);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(mImageViews.get(2),
"translationX", 200F, 0);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(mImageViews.get(3),
"translationY", -200F, 0);
ObjectAnimator animator4 = ObjectAnimator.ofFloat(mImageViews.get(4),
"translationX", -200F, 0);
AnimatorSet set = new AnimatorSet();
set.setDuration(500);
set.setInterpolator(new BounceInterpolator());
set.playTogether(animator0, animator1, animator2, animator3, animator4);
set.start();
mFlag = true;
}
private void startAnim() {
ObjectAnimator animator0 = ObjectAnimator.ofFloat(
mImageViews.get(0),
"alpha",
1F,
0.5F);
ObjectAnimator animator1 = ObjectAnimator.ofFloat(
mImageViews.get(1),
"translationY",
200F);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(
mImageViews.get(2),
"translationX",
200F);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(
mImageViews.get(3),
"translationY",
-200F);
ObjectAnimator animator4 = ObjectAnimator.ofFloat(
mImageViews.get(4),
"translationX",
-200F);
AnimatorSet set = new AnimatorSet();
set.setDuration(500);
set.setInterpolator(new BounceInterpolator());
set.playTogether(
animator0,
animator1,
animator2,
animator3,
animator4);
set.start();
mFlag = false;
}
}
View Code
2、视图动画
视图动画主要有四种,平移,旋转,缩放,透明度四种变化。视图动画要避免交互,但是其效率高,使用方便。下面看一个具体的实例:
xml 文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:orientation="vertical"
android:gravity="center_horizontal"
tools:context=".MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Alpha"
android:layout_margin="10dp"
android:onClick="btnAlpha" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rotate"
android:layout_margin="10dp"
android:onClick="btnRotate" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rotate_self"
android:layout_margin="10dp"
android:onClick="btnRotateSelf" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Translate"
android:layout_margin="10dp"
android:onClick="btnTranslate" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Scale"
android:layout_margin="10dp"
android:onClick="btnScale" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Scale_Self"
android:layout_margin="10dp"
android:onClick="btnScaleSelf" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Anim Set"
android:layout_margin="10dp"
android:onClick="btnSet" />
</LinearLayout>
View Code
设置了 5 个按钮,每个按钮都设置了点击的方法。
activity 中的代码:注意点击方法传入的是 View,这样就可以直接处理啦。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void btnAlpha(View view) {
AlphaAnimation aa = new AlphaAnimation(0, 1);
aa.setDuration(1000);
view.startAnimation(aa);
}
public void btnRotate(View view) {
RotateAnimation ra = new RotateAnimation(0, 360, 100, 100);
ra.setDuration(1000);
view.startAnimation(ra);
}
public void btnRotateSelf(View view) {
RotateAnimation ra = new RotateAnimation(0, 360,
RotateAnimation.RELATIVE_TO_SELF, 0.5F,
RotateAnimation.RELATIVE_TO_SELF, 0.5F);
ra.setDuration(1000);
view.startAnimation(ra);
}
public void btnTranslate(View view) {
TranslateAnimation ta = new TranslateAnimation(0, 200, 0, 300);
ta.setDuration(1000);
view.startAnimation(ta);
}
public void btnScale(View view) {
ScaleAnimation sa = new ScaleAnimation(0, 2, 0, 2);
sa.setDuration(1000);
view.startAnimation(sa);
}
public void btnScaleSelf(View view) {
ScaleAnimation sa = new ScaleAnimation(0, 1, 0, 1,
Animation.RELATIVE_TO_SELF, 0.5F,
Animation.RELATIVE_TO_SELF, 0.5F);
sa.setDuration(1000);
view.startAnimation(sa);
}
public void btnSet(View view) {
AnimationSet as = new AnimationSet(true);
as.setDuration(1000);
AlphaAnimation aa = new AlphaAnimation(0, 1);
aa.setDuration(1000);
as.addAnimation(aa);
TranslateAnimation ta = new TranslateAnimation(0, 100, 0, 200);
ta.setDuration(1000);
as.addAnimation(ta);
view.startAnimation(as);
}
}
View Code
3、ValueAnimator
ValueAnimator 本身不提供任何动画效果,它更像一个数值发生器,用来产生具有一定规律的数字,从而让调用者来控制动画的实现。
一个完整的动画具有start, repeat, end, cancel 四个过程,通过Android 提供了接口,可以很方便的监听这四个事件,代码如下所示。
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha", 0.5f);
anim .addListener(new AnimatorListener(){
@Override
public void onAnimationStart(Animator animation){}
@Override
public void onAnimationRepeat(Animator animation){}
@Override
public void onAnimationEnd(Animator animation){}
@Override
public void onAnimationCancel(Animator animation){}
});
anim.start();
当然大部分时候,我们都只关心 onAnimationEnd 事件,所以 Android 也提供了一个 AnimatorListenerAdapter 来让我们选择必要的必要的事情进行监听,代码如下所示:
anim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
// TODO
}
});
实例:
点击之后,会弹出一个下拉 item,再点击一次就消失。主要就是通过 ValueAnimator 实现的。通过中间数值的变化,来设置其高度的变化,达到动画的效果。我们监听动画结束的时候,然后就设置让该 item 看不见。
package com.imooc.anim;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
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;
}
}
View Code
另一个实例,定时器,将数值的变化体现在界面上。
public class TimerTest extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.timer);
}
public void tvTimer(final View view) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 100);
valueAnimator.addUpdateListener(
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
((TextView) view).setText("$ " +
(Integer) animation.getAnimatedValue());
}
});
valueAnimator.setDuration(3000);
valueAnimator.start();
}
}
4、AnimatorSet
该属性能够精确的控制的发生的顺序。其他方法还有:animSet.play().with(), playSequentially(), playTogether(), before(), after()。
ObjectAnimator pvh1 = ObjectAnimator.ofFloat(view, "translationX",300f);
ObjectAnimator pvh2 = ObjectAnimator.ofFloat(view, "scaleX",1f, 0, 1f);
ObjectAnimator pvh3 = ObjectAnimator.ofFloat(view, "scaleY",1f, 0, 1f);
AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
set.playTogether(pvh1, pvh2, pvh3);
set.start();
5、使用 XML
一个是属性动画的XML,另一个是帧动画。
帧动画是顺序播放一组预先定义好的图片,类似于电影播放。不同于 视图动画,系统提供了另一个类 AnimationDrawable 来使用帧动画。帧动画比较简单,首先需要通过 XML 来定义一个 AnimationDrawable,如下所示。
文件地址:res/drawable/frame_test.xml
<?xml version="1.0" encoding="utf-8"?>
<animation-list
xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/ic_launcher" android:duration="500"></item>
<item android:drawable="@drawable/s1" android:duration="500"></item>
<item android:drawable="@drawable/s2" android:duration="500"></item>
</animation-list>
然后将上述的 Drawable 作为 View 的背景并通过 Drawable 来播放动画即可:
Button btn = (Button) findViewById(R.id.shen);
btn.setBackgroundResource(R.drawable.frame_test);
AnimationDrawable drawable = (AnimationDrawable) btn.getBackground();
drawable.start();
帧动画比较简单,但是容易引起 OOM (内存不足) , 所以在使用帧动画的时候,尽量避免使用过多尺寸较大的图片。
6、自定义动画
创建自定义动画非常简单,只需要实现它的 applyTransformation 的逻辑就可以啦。不过,通常情况下,还需要覆盖父类的 initialize 方法来实现一些初始化工作。
applyTransformation( float interpolatedTime, Transformation t)
applyTransformation 有两个参数,第一个是插值器的事件因子,取值范围 0-1。
第二个参数是矩阵的封装类,一般使用这个类来获得当前的矩阵对象。通过改变获得矩阵对象来将动画实现出来。
两个实例:一个是电视关闭的动画,一个是自定义的3D自定义动画效果。
在 3D 自定义动画效果中,我们使用了一个 camera,该类是 android.graphics.Camera中的 camera 类,它封装了 openGL 的 3D 动画,从而可以很方便的创建 3D 动画效果。
1 public class CustomAnim extends Animation {
2
3 private int mCenterWidth;
4 private int mCenterHeight;
5 private Camera mCamera = new Camera();
6 private float mRotateY = 0.0f;
7
8 @Override
9 public void initialize(int width,
10 int height,
11 int parentWidth,
12 int parentHeight) {
13
14 super.initialize(width, height, parentWidth, parentHeight);
15 // 设置默认时长
16 setDuration(2000);
17 // 动画结束后保留状态
18 setFillAfter(true);
19 // 设置默认插值器
20 setInterpolator(new BounceInterpolator());
21 mCenterWidth = width / 2;
22 mCenterHeight = height / 2;
23 }
24
25 // 暴露接口-设置旋转角度
26 public void setRotateY(float rotateY) {
27 mRotateY = rotateY;
28 }
29
30 @Override
31 protected void applyTransformation(
32 float interpolatedTime,
33 Transformation t) {
34 final Matrix matrix = t.getMatrix();
35 mCamera.save();
36 // 使用Camera设置旋转的角度
37 mCamera.rotateY(mRotateY * interpolatedTime);
38 // 将旋转变换作用到matrix上
39 mCamera.getMatrix(matrix);
40 mCamera.restore();
41 // 通过pre方法设置矩阵作用前的偏移量来改变旋转中心
42 matrix.preTranslate(mCenterWidth, mCenterHeight);
43 matrix.postTranslate(-mCenterWidth, -mCenterHeight);
44 }
45 }
View Code
public class CustomTV 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(1000);
// 动画结束后保留状态
setFillAfter(true);
// 设置默认插值器
setInterpolator(new AccelerateInterpolator());
mCenterWidth = width / 2;
mCenterHeight = height / 2;
}
// 暴露接口-设置旋转角度
public void setRotateY(float rorateY) {
mRotateY = rorateY;
}
@Override
protected void applyTransformation(
float interpolatedTime,
Transformation t) {
final Matrix matrix = t.getMatrix();
matrix.preScale(1,
1 - interpolatedTime,
mCenterWidth,
mCenterHeight);
}
}
View Code
activity 文件的内容:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void btnAnim(View view) {
CustomAnim customAnim = new CustomAnim();
customAnim.setRotateY(30);
view.startAnimation(customAnim);
}
public void imgClose(View view) {
CustomTV customTV = new CustomTV();
view.startAnimation(customTV);
}
}
View Code
XML文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<Button
android:text="@string/hello_world"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="btnAnim"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="40dp" />
<ImageView
android:layout_width="200dp"
android:layout_height="200dp"
android:id="@+id/imageView"
android:onClick="imgClose"
android:background="@mipmap/ic_launcher"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
View Code
主要就是两个按钮。
7、LayoutAnimation
LayoutAnimation 作用于 ViewGroup,为 ViewGroup 制定一个动画,这样当它的子元素出场时都会有这种动画效果。这种效果常常被用于 ListView 上。
LayoutAnimation 也是一个 视图动画,为了给 ViewGroup 子元素加上出场效果,要遵循如下几个步骤。
定义 LayoutAnimation,如下所示:
// res/anim/anim_layout.xml
<layoutAnimation
xmlns:android="http://schemas.android.com/apk/res/android"
android:delay="0.5"
android:animationOrder="reverse"
android:animation="@anim/anim_item"/>
android:delay 表示子动画的事件延迟。比如入场动画周期是300ms,那么 0.5 表示每个子元素都要延迟 150 ms 才能播放入场动画。
android:animationOrder 表示子动画播放顺序,有三种选项:normal(顺序播放), reverse(逆序播放)和 random (随机)。
android:animation 表示为子元素制定具体的入场动画。
// res/anim/anim_item.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:interpolator="@android:anim/accelerate_interpolator"
android:shareInterpolator="true" >
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0" />
<translate
android:fromXDelta="1500"
android:toXDelta="0" />
</set>
接下去为 viewGroup 指定 android:layoutAnimation 属性: android:layoutAnimation="@anim/anim_layout"。这样,它里面的子元素就会有出场动画了。如下所示:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layoutAnimation="@anim/anim_layout">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="shenjaiqi"
android:id="@+id/shen"/>
</RelativeLayout>
除了使用 xml 指定 LayoutAnimation 外,还可以通过 LayoutAnimationController 来实现,具体代码如下所示。
ListView listView = (ListView) layout.findViewById(R.id.list);
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);
8、Activity 的切换效果
Activity 有默认的切换效果,但是这个效果我们可以自定义的,主要用到
overridePendingTransition(int enterAnim, int exitAnim)这个方法,这个方法必须在
startActivity 或 finish() 之后被调用才能生效,它的参数含义如下:
enterAnim: Activity 被打开的时候,所需的动画资源 id;
exitanim:Activity 被关闭的时候,所需的动画资源 id;
当启动一个 Activity 的时候,可以按照下面方式添加自定义效果:
Intent intent = new Intent(this, TestActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
当 Activity 推出的时候,也可以按照下面的方式添加自定义切换效果:
@Override
public void finish() {
super.finish();
overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
}
使用动画的注意事项:
1、OOM问题:主要出现在帧动画中,图片较多且大的话极易出现OOM;
2、内存泄漏:属性动画中,有一类无限循环的动画,要在 Activity 退出时及时停止;视图动画一般不存在此类问题;
3、兼容性问题:动画在 3.0 一下的系统有兼容性问题,要做好适配工作;
4、View 动画是对 View 的影响做动画,并不是真正的改变 View 的状态,因此有时候会出现动画完成后View 无法隐藏的现象,即 setVisibility(View.GONE) 失效了,这个时候只要调用 view.clearAnimation() 清除 View 动画即可解决此问题。
5、不要使用px: 尽量使用 dp,
6、动画元素的交互:在 3.0 以下的系统,动画完成后,仍是在老位置触发单机事件;3.0 开始,属性动画的淡季事件触发为移动后的位置,View 动画不变。
7、硬件加速:使用过程中,建议开启硬件加速,提高动画的流畅性。