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 ListmImageViews = new ArrayList (); 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; } }
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>
设置了 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); } }
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; } }
另一个实例,定时器,将数值的变化体现在界面上。
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 }
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); } }
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); } }
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>
主要就是两个按钮。
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、硬件加速:使用过程中,建议开启硬件加速,提高动画的流畅性。