上一篇 Android动画分析(一) 介绍了 Android 常见的 3 种动画:视图动画、逐帧动画、属性动画,不熟悉的同学可以去上一篇看看。这篇开始介绍 Android 的布局动画、自定义动画、矢量动画机制等知识。
1. Android 的布局动画
所谓的布局动画是作用在 ViewGroup上的,当 ViewGroup 添加 子View 的时候,子View会按照 ViewGroup 设置的动画实现顺序、逆序或者随机的动画播放效果,实现 ViewGroup 添加 子View的过渡效果。
最简单的布局动画是在 ViewGroup 的XML中,添加允许布局变化播放动画的属性:
android:animateLayoutChanges="true"
通过设置这个属性,当 ViewGroup添加子View的时候,子View会呈现逐渐显示的过渡效果,这个效果是 Android 默认的,不能自定义改变如果想自定义 ViewGroup 的布局动画,就要使用 java 代码方式实现。
举个栗子
- 定义视图动画的 XML 文件(R.anim.view_animation)代码:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:shareInterpolator="true"
android:duration="3000"
android:fillAfter="true">
<alpha
android:fromAlpha="0.3"
android:toAlpha="1" />
<scale
android:fromXScale="0.5"
android:toXScale="1"
android:fromYScale="0.5"
android:toYScale="1"/>
<translate
android:fromXDelta="0"
android:toXDelta="10%"
android:fromYDelta="0"
android:toYDelta="10%" />
<rotate
android:fromDegrees="0"
android:toDegrees="15"
android:pivotX="0"
android:pivotY="0" />
</set>
- 在 Activity 中使用
layout = (RelativeLayout)findViewById(R.id.activity_property_animation);
//设置一连串动画
Animation a = AnimationUtils.loadAnimation(PropertyAnimationActivity.this,R.anim.view_animation);
//设置布局动画的显示属性
LayoutAnimationController controller = new LayoutAnimationController(a,0.5f);
//子View的显示顺序
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
// 为ViewGroup设置布局动画
layout.setLayoutAnimation(controller);
也可以为 LayoutAnimationController
设置单个动画:平移、旋转、缩放、透明度动画。
2. 自定义动画
自定义动画只需要继承 android.view.animation.Animation
类,重写 public void initialize(int width, int height, int parentWidth, int parentHeight)
方法和 protected void applyTransformation(float interpolatedTime, Transformation t)
方法即可。
举个栗子
下面是一个实现绕 Y 轴旋转的自定义动画 Demo
- 自定义的旋转动画
/**
* Android SDK Demo
*
* An animation that rotates the view on the Y axis between two specified angles.
* This animation also adds a translation on the Z axis (depth) to improve the effect.
*
* 自定义一个绕着Y轴旋转且在Z轴有平移的动画
*/
public class RotateAnimation extends Animation {
private final float mFromDegrees;
private final float mToDegrees;
private final float mCenterX;
private final float mCenterY;
private final float mDepthZ;
private final boolean mReverse; //控制Z轴移动方向,达到视觉远近移动导致的视图放大缩小效果。
private Camera mCamera;
/**
* 构造方法
*
* @param fromDegrees 3D旋转的起始角度
* @param toDegrees SD旋转后的角度
* @param centerX 旋转中心X
* @param centerY 旋转中心Y
* @param reverse true 反转, false 返回
*/
public RotateAnimation(float fromDegrees, float toDegrees,
float centerX, float centerY, float depthZ, boolean reverse) {
mFromDegrees = fromDegrees;
mToDegrees = toDegrees;
mCenterX = centerX;
mCenterY = centerY;
mDepthZ = depthZ;
mReverse = reverse;
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mCamera = new Camera();
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float fromDegrees = mFromDegrees;
float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
final float centerX = mCenterX;
final float centerY = mCenterY;
final Camera camera = mCamera;
final Matrix matrix = t.getMatrix();
Log.i("interpolatedTime", interpolatedTime+"");
camera.save();
if (mReverse) { //近————>远
camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
} else { //远————>近
camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
}
//沿着Y轴旋转
camera.rotateY(degrees);
// 将旋转变换作用到matrix上
camera.getMatrix(matrix);
camera.restore();
// 通过pre方法设置矩阵作用前的偏移量来改变旋转中心
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
}
- 界面布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:id="@+id/activity_custom_animation"
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"
tools:context="com.cn21.innovator.animationtest.CustomAnimationActivity">
<Button
android:id="@+id/reverse"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="打开名片"/>
<RelativeLayout
android:id="@+id/rl_content"
android:layout_width="match_parent"
android:layout_height="270dp"
android:layout_below="@+id/reverse"
android:layout_marginTop="10dp"
android:background="@android:color/black">
<ImageView
android:id="@+id/iv_logo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@null"
android:src="@drawable/b"
android:scaleType="fitXY"/>
<TextView
android:id="@+id/tv_desc"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:text="能成就大事的绝不是靠一个人,背后肯定有一个独一无二的团队\n\n简书号:innovatorCL\n\n怀揣技术的梦想,乘着大浪,扬帆起航。"
android:textColor="@android:color/white"
android:gravity="center"
android:textSize="18sp"
android:visibility="gone"/>
</RelativeLayout>
<ImageView
android:id="@+id/imageView"
android:layout_width="250dp"
android:layout_height="200dp"
android:src="@drawable/a"
android:layout_below="@+id/rl_content"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"/>
</RelativeLayout>
- 在Activity中调用
public class CustomAnimationActivity extends AppCompatActivity {
private RelativeLayout mContentRl;
private ImageView mScene;
private ImageView mImageView;
private TextView mDescTv;
private Button mReverseButton;
private int centerX;
private int centerY;
private int depthZ = 400;
private int duration = 600;
private RotateAnimation openAnimation;
private RotateAnimation closeAnimation;
private boolean isOpen = true; //打开文字
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_animation);
mScene = (ImageView)findViewById(R.id.iv_logo);
mDescTv = (TextView)findViewById(R.id.tv_desc);
mImageView = (ImageView)findViewById(R.id.imageView);
mContentRl = (RelativeLayout)findViewById(R.id.rl_content);
mReverseButton = (Button)findViewById(R.id.reverse);
mReverseButton.postDelayed(new Runnable() {
@Override
public void run() {
//onCreate()中视图还没绘好,获取的宽高为0,所以要延迟获取,以旋转对象的中心点为旋转中心点
centerX = mContentRl.getWidth()/2;
centerY = mContentRl.getHeight()/2;
}
},50);
mReverseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(openAnimation == null){
initOpenAnimation();
initCloseAnimation();
}
//动画正在进行
if(openAnimation.hasStarted() && !openAnimation.hasEnded()){
return;
}
if(closeAnimation.hasStarted() && !closeAnimation.hasEnded()){
return;
}
if(isOpen){
mContentRl.startAnimation(openAnimation); //打开文字
}else {
mContentRl.startAnimation(closeAnimation);
}
isOpen = !isOpen;
mReverseButton.setText(isOpen ? "关闭文字" : "打开文字");
}
});
//模拟电视关闭效果
mImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
private void initOpenAnimation(){
//近————> 远
//从0到90度,顺时针旋转视图,此时reverse参数为true,达到90度时动画结束时风景图变得不可见
openAnimation = new RotateAnimation(0,90,centerX,centerY,depthZ,true);
openAnimation.setDuration(duration);
openAnimation.setFillAfter(true);
openAnimation.setInterpolator(new AccelerateInterpolator()); //加速因子
openAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mScene.setVisibility(View.GONE);
mDescTv.setVisibility(View.VISIBLE);
//远————>近
//从270到360度,顺时针旋转视图,此时reverse参数为false,达到360度动画结束时文字变得可见
RotateAnimation rotateAnimation = new RotateAnimation(270,360,centerX,centerY,depthZ,false);
rotateAnimation.setDuration(duration);
rotateAnimation.setFillAfter(true);
rotateAnimation.setInterpolator(new DecelerateInterpolator()); //减速因子
mContentRl.startAnimation(rotateAnimation);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
private void initCloseAnimation(){
//近————> 远
//反转图片效果
closeAnimation = new RotateAnimation(360,270,centerX,centerY,depthZ,true);
closeAnimation.setDuration(duration);
closeAnimation.setFillAfter(true);
closeAnimation.setInterpolator(new AccelerateInterpolator());
closeAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mScene.setVisibility(View.VISIBLE);
mDescTv.setVisibility(View.GONE);
//远————>近
RotateAnimation rotateAnimation = new RotateAnimation(90,0,centerX,centerY,depthZ,false);
rotateAnimation.setDuration(duration);
rotateAnimation.setFillAfter(true);
rotateAnimation.setInterpolator(new DecelerateInterpolator());
mContentRl.startAnimation(rotateAnimation);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
- 效果