之前提到Android提供了三种动画类型:
- View Animation
- Drawable Animation
- Property Animation
本文介绍第三种
Property Animation(属性动画)
概述
- 属性动画是在Android 3.0的(API级别11)引入的。
- 属性动画系统可以制作动画的任何对象的属性,它允许你动画几乎所有的东西。例如一个对象在屏幕中的位置,要动画多久,和动画之间的距值。
- 属性动画系统是首选的动画使用方法,因为它更灵活,并提供更多功能(特性)。
偷一张图,出处:http://www.runoob.com/w3cnote/android-tutorial-valueanimator.html
顾名思义,属性动画操作若干属性,通常我们要操作的属性为:
- rotationX、rotationY——旋转
- scaleX、scaleY——缩放
- translationX、translationY——平移
- X、Y——坐标
- alpha——透明度
属性动画又可以分为两种:
- ObjectAnimator
- ValueAnimator
ObjectAnimator
引言
ObjectAnimator继承自ValueAnimator,所以ValueAnimator的方法在ObjectAnimator都能用,可以考虑跳过这部分先看下面的ValueAnimator。前文中提到的“属性动画系统可以制作动画的任何对象的属性,它允许你动画几乎所有的东西”,实际上应该是描述ObjectAnimator才对,因为ValueAnimator主要操作“值的过渡”,而后者更接近本质。
旋转动画案例
- 布局,添加一个Imageview,并添加点击事件,保险起见设置了clickable="true"
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/timg"
android:onClick="click"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:clickable="true"/>
public void click(View v) {
//第一个参数:哪个组件 第二个参数:哪个属性 第三个参数(float类型); 起始和结束 第四个参数:时间
ObjectAnimator.ofFloat(v, "rotationX", 0f, 360f).setDuration(500).start();
//rotation x 0.0->360旋转三百六十度
}
- 效果如👇
组合多个动画
- 主要通过PropertyValuesHolder来添加独立动画,然后通过ofPropertyValuesHolder进行调用,步骤非常简单。
- 完整代码如👇,布局文件还是和👆一样
package com.example.a4_22animations;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View v) {
//第一个参数:哪个组件 第二个参数:哪个属性 第三个参数(float类型); 起始和结束 第四个参数:时间
//ObjectAnimator.ofFloat(v, "rotationX", 0f, 360f).setDuration(500).start();
//rotation x 0.0->360旋转三百六十度
//组合多个动画 相当于缩放效果
PropertyValuesHolder p1 = PropertyValuesHolder.ofFloat("alpha", 1f, 0f, 1f);
PropertyValuesHolder p2 = PropertyValuesHolder.ofFloat("scaleX", 1f, 0f, 1f);
PropertyValuesHolder p3 = PropertyValuesHolder.ofFloat("scaleY", 1f, 0f, 1f);
ObjectAnimator.ofPropertyValuesHolder(v, p1, p2, p3).setDuration(3000).start();
}
}
- 效果如👇
ValueAnimator
步骤
- 调用ValueAnimator的ofInt(),ofFloat()或ofObject()静态方法创建ValueAnimator实例
- 调用实例的setXxx方法设置动画持续时间,插值方式,重复次数等
- 调用实例的addUpdateListener添加AnimatorUpdateListener***,在该***中 可以获得ValueAnimator计算出来的值,你可以值应用到指定对象上~
- 调用实例的start()方法开启动画! 另外我们可以看到ofInt和ofFloat都有个这样的参数:float/int... values代表可以多个值!
(👆出处也是最开始偷的图片的出处 orz)
自由落体动画案例
- 布局还是没改,接着在原来点击事件代码上写。 (去掉v.getY()参数将不会再返回)
public void click(View v) {
/**************************ObjectAnimator**************************/
//第一个参数:哪个组件 第二个参数:哪个属性 第三个参数(float类型); 起始和结束 第四个参数:时间
//ObjectAnimator.ofFloat(v, "rotationX", 0f, 360f).setDuration(500).start();
//rotation x 0.0->360旋转三百六十度
//组合多个动画 相当于缩放效果
/*
PropertyValuesHolder p1 = PropertyValuesHolder.ofFloat("alpha", 1f, 0f, 1f);
PropertyValuesHolder p2 = PropertyValuesHolder.ofFloat("scaleX", 1f, 0f, 1f);
PropertyValuesHolder p3 = PropertyValuesHolder.ofFloat("scaleY", 1f, 0f, 1f);
ObjectAnimator.ofPropertyValuesHolder(v, p1, p2, p3).setDuration(3000).start();
*/
/**************************ValueAnimator**************************/
//自由落体实例
final View view = v;
//拿到屏幕高度
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
//定义一个动画 ofFloat返回一个ValueAnimator
ValueAnimator va = ValueAnimator.ofFloat(v.getY(), dm.heightPixels, v.getY()).setDuration(500);
//监听动画的每个动作
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
view.setTranslationY((Float) animation.getAnimatedValue());
}
});
va.start();
}
- 效果如👇
监听动画事件
- 实际上在👆的案例已经用了一个监听,相当于监听图片运动情况,然后调用方法。
- 而这里是监听动画事件主要指的是监听动画的状态(比方说动画结束)
public void click(View v) {
final View view = v;
//监听动画事件
ObjectAnimator oa = ObjectAnimator.ofFloat(v, "alpha", 1f, 0f).setDuration(1000);
oa.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
//动画开始时调用
}
@Override
public void onAnimationEnd(Animator animation) {
//动画结束时调用
ViewGroup viewGroup = (ViewGroup) view.getParent();
if (viewGroup != null) {
viewGroup.removeView(view);
System.out.println("图片已删除");
}
}
@Override
public void onAnimationCancel(Animator animation) {
//取消动画时调用
}
@Override
public void onAnimationRepeat(Animator animation) {
//重复动画时调用
}
});
oa.start();
}
- 效果如下👇
- 👆面的方法不免产生一些冗余代码,所以Android其实提供了更加简洁的办法👇
- addListener参数可以直接new一个AnimatorListenerAdapter,其他完全不变,但是可以选择自己需要的方法仅需重写,而不必全部都重写
public void click(View v) {
final View view = v;
//监听动画事件,图片移除,写法2
ObjectAnimator oa = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).setDuration(1000);
oa.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//动画结束
ViewGroup viewGroup = (ViewGroup) view.getParent();
if (viewGroup != null) {
viewGroup.removeView(view);
System.out.println("图片已删除");
}
}
});
oa.start();
}
AnimatorSet
引言
之前介绍了一种组合动画的方式——PropertyValuesHolder。实际上通过AnimatorSet也能实现组合动画的效果。它的定制性更强,PropertyValuesHolder组合的动画基本上就一起播放了,而AnimatorSetw完全可以定制组合动画中每个独立动画的播放次序。
案例
public void click(View v) {
ObjectAnimator a1 = ObjectAnimator.ofFloat(v, "translationX", 0f, 200f);
ObjectAnimator a2 = ObjectAnimator.ofFloat(v, "translationY", 0f, 200f);
ObjectAnimator a3 = ObjectAnimator.ofFloat(v, "rotation", 0f, 360f);
AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
set.playTogether(a1, a2, a3);//三个动画同时执行
// set.setStartDelay(300);//延迟执行
// set.playSequentially(a1,a2,a3);//顺序执行
//自定义顺序写法参考,先a1和a2一起执行,最后a3
// set.play(a1).with(a2);
// set.play(a3).after(a2);
set.start();
}
- 效果如👇
注意:
animSet.play().with();
虽然支持链式编程,但是:
animSet.play(a1).with(a2).before(a3).before(a5);
👆这样是不行的,系统不会根据你写的这一长串来决定先后。
Interpolators
发现一篇博文,可以参考下
即插值器,简单理解就是提供在动画中进行的插补操作,在本文提到的几种动画类型中皆可适用。
例如:
set.setInterpolator(new BounceInterpolator());
下面是支持的lnterpolator
- AccelerateDeceleratelnterpolator:插补, 其变化率慢慢开始和结束,但通过中间加速。
- Accelerateinterpolator:插补, 其变化率开始缓慢,然后加快。
- Anticipatelnterpolator:内插的变化 开始落后,然后向前甩。
- AnticipateOvershootinterpolator:内插的变化, 开始落后,甩向前过冲目标值,然后终于可以追溯到最终值。
- Bouncelnterpolator:插补, 其变化在最后反弹。
- Cyclelnterpolator:内插动画 重复指定的周期数。
- Decelerateinterpolator:插补, 其变化的速度开始很快,然后减速。
- Linearlnterpolator:插补, 其变化率是恒定的
- Overshootinterpolator:内插的变化甩向前和过冲的最后一个值,然后回来。
- Timelnterpolator:一个接口,使您实现自己的插补。
用XML文件创建属性动画
- 在res资源文件下创建animator资源文件夹
- 然后就可以创建动画文件了,例如建一个test.xml,插值器也是可以用的。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator android:duration="3000"
android:propertyName="alpha"
android:valueFrom="1.0"
android:valueTo="0.0"/>
</set>
- 最后使用也非常简单
public void click(View v) {
//使用XML定义的动画
Animator a = AnimatorInflater.loadAnimator(this, R.animator.test);
a.setTarget(v);
a.start();
}
- 效果
- XML文件里进行多个动画组合也是可以的,只需要修改XML文件
- 加载(设置了一下相对位置)
动画菜单
新建一个MenuActivity,修改其布局文件,考虑到菜单选项一开始应该是摞在一起的,所以选择FrameLayout
然后素材自备...
布局如👇
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MenuActivity">
<ImageView
android:id="@+id/imageView8"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="left|top"
android:src="@mipmap/img8"
android:tag="imageView8"/>
<ImageView
android:id="@+id/imageView7"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="left|top"
android:src="@mipmap/img7"
android:tag="imageView7"/>
<ImageView
android:id="@+id/imageView6"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="left|top"
android:src="@mipmap/img6"
android:tag="imageView6"/>
<ImageView
android:id="@+id/imageView5"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="left|top"
android:src="@mipmap/img5"
android:tag="imageView5"/>
<ImageView
android:id="@+id/imageView4"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="left|top"
android:src="@mipmap/img4"
android:tag="imageView4"/>
<ImageView
android:id="@+id/imageView3"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="left|top"
android:src="@mipmap/img3"
android:tag="imageView3"/>
<ImageView
android:id="@+id/imageView2"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="left|top"
android:src="@mipmap/img2"
android:tag="imageView2" />
<ImageView
android:id="@+id/imageView1"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="left|top"
android:src="@mipmap/img1"
android:tag="imageView1"/>
</FrameLayout>
完整代码如👇
package com.example.a4_22animations;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.BounceInterpolator;
import android.widget.ImageView;
import android.widget.Toast;
import java.util.ArrayList;
//监听事件
public class MenuActivity extends AppCompatActivity implements View.OnClickListener {
private int[] res = {
R.id.imageView1,
R.id.imageView2,
R.id.imageView3,
R.id.imageView4,
R.id.imageView5,
R.id.imageView6,
R.id.imageView7,
R.id.imageView8
};
private ArrayList<ImageView> list = new ArrayList<>();
//菜单展开状态标志
private boolean isOpen = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_menu);
//遍历图片资源数组
for (int i = 0; i < res.length; i++) {
ImageView imageView = findViewById(res[i]);
imageView.setOnClickListener(this);
//添加到list里
list.add(imageView);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.imageView1:
if (isOpen) {
closeMenu();
isOpen = false;
} else {
openMenu();
isOpen = true;
}
break;
default:
//点击其他的就显示tag
Toast.makeText(this, v.getTag().toString(), Toast.LENGTH_SHORT).show();
break;
}
}
//展开菜单
private void openMenu() {
for (int i = 1; i < res.length; i++) {
ObjectAnimator a1 = ObjectAnimator.ofFloat(list.get(i), "translationX", 0, 100 * i);
a1.setInterpolator(new BounceInterpolator());
ObjectAnimator a2 = ObjectAnimator.ofFloat(list.get(i), "translationY", 0, 100 * i);
a2.setInterpolator(new BounceInterpolator());
AnimatorSet set = new AnimatorSet();
set.setDuration(300);
set.playTogether(a1, a2);
set.start();
}
}
//收回菜单
private void closeMenu() {
for (int i = 1; i < res.length; i++) {
ObjectAnimator a1 = ObjectAnimator.ofFloat(list.get(i), "translationX", 100 * i, 0);
a1.setInterpolator(new BounceInterpolator());
ObjectAnimator a2 = ObjectAnimator.ofFloat(list.get(i), "translationY", 100 * i, 0);
a2.setInterpolator(new BounceInterpolator());
AnimatorSet set = new AnimatorSet();
set.setDuration(300);
set.playTogether(a1, a2);
set.start();
}
}
}
测试效果如👇