Android动画(二)–属性动画
属性动画
借助于插值器(Interpolator)和估值器(TypeEvaluator),按照一定变化率对属性值进行操作的机制,变化率就是依赖Interpolator控制,而值操作则是TypeEvaluator控制。
与视图动画的区别:
- 属性动画作用的对象可以是任何一个Object对象,也就是说我们完全可以给任意Object对象设置属性动画,而这个对象可以不是一个View组件,也不管这个对象是否是可见的,而视图动画的作用对象只能是一个View对象,这是最大的不同;
- 视图动画的一个致命缺陷就是,通过视图动画将一个View对象(比如一个TextView,Button)位置改编后,该对象的触摸事件的焦点依然在原位置,而属性动画就很好的解决了这一缺陷;
- 属性动画可以控制动画执行过程中的任意时刻的任意属性值;视图动画从本质上来说是一种补间动画,他只对动画的起始值和结束值进行赋值,而动画中间执行过程中的属性值则是系统帮我们计算的。我们用自定义估值器控制动画执行过程中的属性值;
ObjectAnimator继承自ValueAnimator,是属性动画中非常重要的一个实现类,通过ObjectAnimator类的静态工厂方法来创建ObjectAnimator对象,这些静态工厂方法包括:ObjectAnimator.ofFloat(),ObjectAnimator.ofInt()等等,当然最为重要的一个静态工厂方法是ObjectAnimator.ofObject(),可以接收一个Object对象并为其设置属性动画。
ObjectAnimator mObjectAnimator = ObjectAnimator.ofFloat(view, "translationX", 200);
静态工厂方法接收的参数分别是:
- 要设置动画的目标对象;
- 动画的属性类型;
- 一个或多个属性值;当只指定一个属性值,系统默认此值为结束值;当指定两个属性值,系统默认分别为起始值和结束值;当指定三个或三个以上时,系统默认线性插值;
注:在使用ObjectAnimator的时候,要操作的属性必须要有get和set方法,不然
ObjectAnimator就无法生效。如果一个属性没有get、set方法,也可以通过自定义一个属性类或包装类来间接地给这个属性增加get和set方法。
在xml文件中使用
//引用XML定义的属性动画
Animator animator = AnimatorInflater.loadAnimator(this, R.animator.scale);
ValueAnimator 是整个属性动画机制当中最核心的一个类,前面我们已经提到了属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的,ValueAnimator对过渡动画值的计算依靠一个时间因子fraction,而这个时间因子fraction是系统由setDuration()方法设置的动画执行时间通过计算得来的,所以ValueAnimator还负责管理动画的持续时间、播放次数、播放模式、以及对动画设置监听器等。(ValueAnimator它本身并不会作用与任何一个属性,它本身也不会提供任何一种动画)
通常情况下,在ValueAnimator的AnimatorUpdateListener中监听数值的变化,从而完成动画的变换
//创建了一个值从0到400的动画
ValueAnimator animator = ValueAnimator.ofFloat(0,100);
//设置动画对象
animator.setTarget(view);
animator.setDuration(1000);
animator.start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Float curValue = (Float)animation.getAnimatedValue();
}
});
影子效果:
把四张相同的图片设置了半透明效果,并同时向四个不同的方向做位移变换。布局很简单,就是在根布局RelativeLayout中放置五个ImageView,src值都引用同一个图片的资源id,这样五张图片就会重叠在一起
/**
* 属性动画PropertyAni
*
* 常用的属性动画的属性值有:
* - translationX、translationY----控制view对象相对其左上角坐标在X、Y轴上偏移的距离
* - rotation、rotationX、rotationY----控制view对象绕支点进行2D和3D旋转
* - scaleX、scaleY----控制view对象绕支点进行2D缩放
* - pivotX、pivotY----控制view对象的支点位置,这个位置一般就是view对象的中心点。围绕这个支点可以进行旋转和缩放处理
* - x、y----描述view对象在容器中的最终位置,是最初的左上角坐标和translationX、translationY值的累计和
* - alpha----表示view对象的透明度。默认值是1(完全透明)、0(不透明)
*
* Created by wondertwo on 2016/3/11.
*/
public class EffectAni extends AppCompatActivity implements View.OnClickListener {
// ImageView组件id数组
private int[] mRes = new int[]{R.id.iv_a, R.id.iv_b, R.id.iv_c, R.id.iv_d, R.id.iv_e};
// ImageView对象集合
private ArrayList<ImageView> mImViews = new ArrayList<>();
private boolean flag = true;// 启动动画、关闭动画的标记位
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_effect);
// for循环创建ImageView对象,并添加到集合中
for (int i = 0; i < mRes.length; i++) {
ImageView iv_a = (ImageView) findViewById(mRes[i]);
iv_a.setOnClickListener(this);
mImViews.add(iv_a);
}
}
// 按钮点击事件
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.iv_a:
if (flag) {
startAnim();
} else {
closeAnim();
}
break;
default:
Toast.makeText(EffectAni.this, "" + v.getId(), Toast.LENGTH_SHORT).show();
break;
}
}
// 关闭动画
private void closeAnim() {
// 创建ObjectAnimator属性对象,参数分别是动画要设置的View对象、动画属性、属性值
ObjectAnimator animator0 = ObjectAnimator.ofFloat(mImViews.get(0),
"alpha",
0.5F,
1F);
ObjectAnimator animator1 = ObjectAnimator.ofFloat(mImViews.get(1),
"translationY",
200F,
0);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(mImViews.get(2),
"translationX",
200F,
0);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(mImViews.get(3),
"translationY",
-200F,
0);
ObjectAnimator animator4 = ObjectAnimator.ofFloat(mImViews.get(4),
"translationX",
-200F,
0);
AnimatorSet aniSet = new AnimatorSet();
aniSet.setDuration(4000);
aniSet.setInterpolator(new BounceInterpolator());// 弹跳效果的插值器
aniSet.playTogether(animator0,
animator1,
animator2,
animator3,
animator4);// 同时启动5个动画
aniSet.start();
// 重置标记位
flag = true;
}
// 启动动画
private void startAnim() {
// 创建ObjectAnimator属性对象,参数分别是动画要设置的View对象、动画属性、属性值
ObjectAnimator animator0 = ObjectAnimator.ofFloat(mImViews.get(0),"alpha", 1f, 0.5f);
ObjectAnimator animator1 = ObjectAnimator.ofFloat(mImViews.get(1),"translationY", 200f);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(mImViews.get(2),"translationX",200f);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(mImViews.get(3),"translationY",-200f);
ObjectAnimator animator4 = ObjectAnimator.ofFloat(mImViews.get(4),"translationX",-200f);
AnimatorSet aniSet = new AnimatorSet();
aniSet.setDuration(4000);
aniSet.setInterpolator(new BounceInterpolator());// 弹跳效果的插值器
aniSet.playTogether(animator0,animator1,animator2,animator3,animator4);// 同时启动5个动画
aniSet.start();
// 重置标记位
flag = false;
}
}
ValueAnimator和属性动画的监听
属性动画需要不断改变对象的某个属性值,从而达到动画的效果,要求定义的属性必须有setter、getter方法,就算没有getter方法在某些特殊的情况下是允许的,但是所有情况下setter方法必须要有,如果系统没有提供那就需要我们自己动手去写setter方法,有两种方式来不断得到这些值:
- 属性动画的监听可通过new Animator.AnimatorListener()也可通过new AnimatorListenterAdaper()
- 为ValueAnimator对象设置动画监听,代码如下所示:valueAnimator.addUpdateListener(),需要传入一个AnimatorUpdateListener对象,一般我们传入的是AnimatorUpdateListener的匿名对象,即:valueAnimator.addUpdateListener(new AnimatorUpdateListener(){…}),需要重写它的onAnimationUpdate()方法,那么上述值的计算逻辑就放在onAnimationUpdate()方法体内
- 重写TypeEvaluator,TypeEvaluator这个词直译过来就是类型值算法,也被译作估值器,我觉得这个叫法很形象,因为他就是用来计算属性动画某个时刻的属性值的具体值的
颜色渐变:
代码:
/**
* BuleToRed实现目标对象背景色的渐变
* Created by wondertwo on 2016/3/23.
*/
public class BuleToRed extends Activity {
private Button targetView;
private int mCurrentRed = -1;
private int mCurrentGreen = -1;
private int mCurrentBlue = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_blue_to_red);
targetView = (Button) findViewById(R.id.tv_color_backgroound);
/**
* 注册点击事件,展示效果
*/
targetView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
displayResult(targetView, "#0000ff", "#ff0000");
}
});
}
/**
* displayResult()展示结果
*/
private void displayResult(final View target, final String start, final String end) {
// 创建ValueAnimator对象,实现颜色渐变
ValueAnimator valueAnimator = ValueAnimator.ofFloat(1f, 100f);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 获取当前动画的进度值,1~100
float currentValue = (float) animation.getAnimatedValue();
Log.d("当前动画值", "current value : " + currentValue);
// 获取动画当前时间流逝的百分比,范围在0~1之间
float fraction = animation.getAnimatedFraction();
// 直接调用evaluateForColor()方法,通过百分比计算出对应的颜色值
String colorResult = evaluateForColor(fraction, start, end);
/**
* 通过Color.parseColor(colorResult)解析字符串颜色值,传给ColorDrawable,创建ColorDrawable对象
*/
/*LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) target.getLayoutParams();*/
ColorDrawable colorDrawable = new ColorDrawable(Color.parseColor(colorResult));
// 把ColorDrawable对象设置为target的背景
target.setBackground(colorDrawable);
target.invalidate();
}
});
valueAnimator.setDuration(6 * 1000);
// 组装缩放动画
ValueAnimator animator_1 = ObjectAnimator.ofFloat(target, "scaleX", 1f, 0.5f);
ValueAnimator animator_2 = ObjectAnimator.ofFloat(target, "scaleY", 1f, 0.5f);
ValueAnimator animator_3 = ObjectAnimator.ofFloat(target, "scaleX", 0.5f, 1f);
ValueAnimator animator_4 = ObjectAnimator.ofFloat(target, "scaleY", 0.5f, 1f);
AnimatorSet set_1 = new AnimatorSet();
set_1.play(animator_1).with(animator_2);
AnimatorSet set_2 = new AnimatorSet();
set_2.play(animator_3).with(animator_4);
AnimatorSet set_3 = new AnimatorSet();
set_3.play(set_1).before(set_2);
set_3.setDuration(3 * 1000);
// 组装颜色动画和缩放动画,并启动动画
AnimatorSet set_4 = new AnimatorSet();
set_4.play(valueAnimator).with(set_3);
set_4.start();
}
/**
* evaluateForColor()计算颜色值并返回
*/
private String evaluateForColor(float fraction, String startValue, String endValue) {
String startColor = startValue;
String endColor = endValue;
int startRed = Integer.parseInt(startColor.substring(1, 3), 16);
int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);
int startBlue = Integer.parseInt(startColor.substring(5, 7), 16);
int endRed = Integer.parseInt(endColor.substring(1, 3), 16);
int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);
int endBlue = Integer.parseInt(endColor.substring(5, 7), 16);
// 初始化颜色的值
if (mCurrentRed == -1) {
mCurrentRed = startRed;
}
if (mCurrentGreen == -1) {
mCurrentGreen = startGreen;
}
if (mCurrentBlue == -1) {
mCurrentBlue = startBlue;
}
// 计算初始颜色和结束颜色之间的差值
int redDiff = Math.abs(startRed - endRed);
int greenDiff = Math.abs(startGreen - endGreen);
int blueDiff = Math.abs(startBlue - endBlue);
int colorDiff = redDiff + greenDiff + blueDiff;
if (mCurrentRed != endRed) {
mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0, fraction);
} else if (mCurrentGreen != endGreen) {
mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff, redDiff, fraction);
} else if (mCurrentBlue != endBlue) {
mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,
redDiff + greenDiff, fraction);
}
// 将计算出的当前颜色的值组装返回
String currentColor = "#" + getHexString(mCurrentRed)
+ getHexString(mCurrentGreen) + getHexString(mCurrentBlue);
return currentColor;
}
/**
* 根据fraction值来计算当前的颜色。
*/
private int getCurrentColor(int startColor, int endColor, int colorDiff,
int offset, float fraction) {
int currentColor;
if (startColor > endColor) {
currentColor = (int) (startColor - (fraction * colorDiff - offset));
if (currentColor < endColor) {
currentColor = endColor;
}
} else {
currentColor = (int) (startColor + (fraction * colorDiff - offset));
if (currentColor > endColor) {
currentColor = endColor;
}
}
return currentColor;
}
/**
* 将10进制颜色值转换成16进制。
*/
private String getHexString(int value) {
String hexString = Integer.toHexString(value);
if (hexString.length() == 1) {
hexString = "0" + hexString;
}
return hexString;
}
}