成功的事业离不开坚如磐石的意志。成功路上的坎坷,意志脆弱者是根本无法超越的。——富兰克林·罗斯福
老式电影胶片就是逐帧动画的工作原理,很简单,将一个完整的电影拆分成一张张单独的图片,然后再将它们连贯起来进行播放。所以我们总是看到一个轮子在不停的转圈,就是在拖动一张张图片。动画分为两类,视图动画 和 属性动画。视图动图包括补间动画和帧动画,属性动画包括ValueAnimator 和 ObjectAnimator。为什么在Android 3.0 引入属性动画?
补间动画有一个致命的缺陷,比如现在屏幕的左上角有一个按钮,我们通过补间动画将它移动到了屏幕的右下角,你可以去尝试点击一下这个按钮,点击事件是绝对不会触发,因为这个按钮还是停留在屏幕的左上角,只不过补间动画将这个按钮绘制到了屏幕的右下角而已。
下面简单看一下这个动画的代码实现
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="Start Anim" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#03A9F4"
android:padding="10dp"
android:text="Hello World"
android:textColor="@android:color/white" />
</LinearLayout>
接下来给Buttom 和 TextView 添加单击事件,当点击TextView时,弹出Toast提示,当点击Buttom,TextView 移动
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = findViewById(R.id.btn);
final TextView tv = findViewById(R.id.tv);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TranslateAnimation translateAnimation = new TranslateAnimation(Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 400,
Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 200);
translateAnimation.setFillAfter(true);
translateAnimation.setDuration(2000);
tv.startAnimation(translateAnimation);
}
});
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "go", Toast.LENGTH_SHORT).show();
}
});
}
}
补间动画还有一个缺陷,它只能够实现移动、缩放、旋转和淡入淡出这四种动画操作,那如果我们希望可以对View的背景色进行动态地改变呢?它实现不了,属性动画因为对控件的属性进行修改,它可以轻易实现背景颜色的动态改变。
ValueAnimator
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 400);
valueAnimator.setDuration(1000);
valueAnimator.start();
ValueAnimator的ofFloat()方法就可以构建出一个0到400的动画,从这段代码中可以出看,ValueAnimator没有跟任何控件想关联,动画时长是1秒,然后开始动画,这也正好说明ValueAnimator只对值进行动画运算,而不是针对控件的。我们需要监听动画过程来自己对控件进行操作。
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 400);
valueAnimator.setDuration(1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float curValue = (float) animation.getAnimatedValue();
Log.d("tag", "----curValue:" + curValue);
}
});
valueAnimator.start();
我们通过addUpdateListener()方法来添加一个动画的监听器,在监听传回的结果中,animator表达当前ValueAnimation实例,通过animation.getAnimatedValue()函数得到当前值,运行结果:
06-10 20:20:34.855 23008-23008/com.as.propertyanimator D/tag: ----curValue:0.0
06-10 20:20:35.063 23008-23008/com.as.propertyanimator D/tag: ----curValue:0.0
06-10 20:20:35.198 23008-23008/com.as.propertyanimator D/tag: ----curValue:1.822114
06-10 20:20:35.254 23008-23008/com.as.propertyanimator D/tag: ----curValue:7.4239135
06-10 20:20:35.267 23008-23008/com.as.propertyanimator D/tag: ----curValue:11.401903
06-10 20:20:35.280 23008-23008/com.as.propertyanimator D/tag: ----curValue:15.465462
06-10 20:20:35.299 23008-23008/com.as.propertyanimator D/tag: ----curValue:20.118963
06-10 20:20:35.317 23008-23008/com.as.propertyanimator D/tag: ----curValue:25.347496
06-10 20:20:35.335 23008-23008/com.as.propertyanimator D/tag: ----curValue:31.134438
06-10 20:20:36.110 23008-23008/com.as.propertyanimator D/tag: ----curValue:398.00473
06-10 20:20:36.128 23008-23008/com.as.propertyanimator D/tag: ----curValue:399.3332
06-10 20:20:36.146 23008-23008/com.as.propertyanimator D/tag: ----curValue:399.93683
06-10 20:20:36.165 23008-23008/com.as.propertyanimator D/tag: ----curValue:400.0
对指定值区间进行动画运算,我们对运算过程进行监听来自己操作控件,总而言之
- ValueAnimator 只负责对指定值区间进行动画运算
- 我们需要对运算过程进行监听,然后自己对控件执行动画操作
ValueAnimator 使用实例
下面使用ValueAnimator 来实现补间动画的单机区域问题中的例子,看看是否仍存在单机区域问题
布局代码与上一个例子相同
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="Start Anim" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#03A9F4"
android:padding="10dp"
android:id="@+id/tv"
android:text="Hello World"
android:textColor="@android:color/white" />
</LinearLayout>
分别给Buttom 和 TextView 添加单击事件,当点击TextView时,弹出Toast提示,当点击Buttom,TextView 开始移动。
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = findViewById(R.id.btn);
tv = findViewById(R.id.tv);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
translate();
}
});
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "go", Toast.LENGTH_SHORT).show();
}
});
}
//当点击Button 的时候调用translate 函数执行动画操作,在单击TextView 弹出Toast提示
private void translate() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 400);
valueAnimator.setDuration(2000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
//通过layout 函数改变TextView 的位置,而layout函数在改变控件位置时是永久的,即通过更改left ,top,right,bottom这四个点的坐标来更改坐标位置,而不仅仅是视觉上画在哪个位置上
tv.layout(curValue, curValue, tv.getWidth() + curValue, tv.getHeight() + curValue);
}
});
valueAnimator.start();
}
...
TextView 的运动轨迹从屏幕左上角(0,0)点运行到(400,400)点
在上面的列子中,我们使用了ofFloat 和 ofInt 函数,下面看下它的具体声明
public static ValueAnimator ofInt(int... values)
public static ValueAnimator ofFloat(float... values)
它们的参数类型都是可变参数,所以我们可以传入任何数量的值,传进入的列表就表示动画的变化方位,比如ofInt(100,400,200)就表示从数字100 变化到400在变化到数字200,所以我们传进去的数字越多,动画变化就越复杂。从参数类型中可以看出ofInt() 和 ofFloat() 唯一的区别就是传入的数字类型不同。
在上面例子的基础上,我们使用ofFloat() 函数来举一个列子
private void translateFloat() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 400f,100f,300f);
valueAnimator.setDuration(4000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Float curValueFloat = (Float) animation.getAnimatedValue();
int curValue = curValueFloat.intValue();
tv.layout(curValue, curValue, tv.getWidth() + curValue, tv.getHeight() + curValue);
}
});
valueAnimator.start();
}
在这个列子中,我们使用 ValueAnimator.ofFloat(0f, 400f,100f,300f) 构造了一个比较复杂的动画,值从0变到400,再回到100,最后变成300。
所以,在单机按钮之后,TextView 会从(0,0)点移动到(400,400)点,再运动到(100,100)点,最后运动到(300,300)点。
大家可能会疑问,为什么要转换成Float 类型,我们先来看看getAnimatorValue()函数声明
public Object getAnimatedValue()
它返回的是Object类型,那我们怎么知道要转换的类型呢?哎,我们在设置动画初始值使用的是ofFloat()函数,所以每个值的类型必定是Float类型,我们获取到的类型也必然是Float类型。在得到当前运动点后,通过layout() 函数将TextView 移动到指定位置即可。
常用函数
// 设置动画时长
public ValueAnimator setDuration(long duration)
// 获取当前运动点的值
public Object getAnimatedValue()
// 开始动画
void start()
// 设置循环次数,INFINITE 表示无限循环,0表示不循环
public void setRepeatCount(int value)
/**设置循环模式
*RESTART 表示正序重新开始
* REVERSE 表示倒序重新开始
*/
public void setRepeatMode(@RepeatMode int value)
//取消动画
void cancel()
下面举个例子
首先是布局代码,我们将两个按钮放到一列,将TextView 放到中间
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="Start Anim" />
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:background="#03A9F4"
android:padding="10dp"
android:text="Hello World"
android:textColor="@android:color/white" />
<Button
android:id="@+id/btnCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btnStart"
android:layout_marginTop="30dp"
android:padding="10dp"
android:text="Cancel Anim" />
</RelativeLayout>
下面看看两个按钮的操作
public class MainActivity extends AppCompatActivity {
private TextView tv;
private ValueAnimator valueAnimatorRepeat;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnStart = findViewById(R.id.btnStart);
Button btnCancel = findViewById(R.id.btnCancel);
tv = findViewById(R.id.tv);
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doRepeatAnimator();
}
});
btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
valueAnimatorRepeat.cancel();
}
});
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "go", Toast.LENGTH_SHORT).show();
}
});
}
private void doRepeatAnimator() {
valueAnimatorRepeat = ValueAnimator.ofInt(0, 400);
valueAnimatorRepeat.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
tv.layout(tv.getLeft(), curValue, tv.getRight(), tv.getHeight() + curValue);
}
});
valueAnimatorRepeat.setRepeatMode(ValueAnimator.REVERSE); //倒叙重新开始
valueAnimatorRepeat.setRepeatCount(ValueAnimator.INFINITE); // 无限循环
valueAnimatorRepeat.setDuration(2500);
valueAnimatorRepeat.start();
}
}
当点击btnStart时,调用doRepeatAnimator()函数,返回ValueAnimator对象,并将其赋给valueAnimatorRepeat,下面来看看这段代码
private void doRepeatAnimator() {
valueAnimatorRepeat = ValueAnimator.ofInt(0, 400);
valueAnimatorRepeat.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
tv.layout(tv.getLeft(), curValue, tv.getRight(), tv.getHeight() + curValue);
}
});
valueAnimatorRepeat.setRepeatMode(ValueAnimator.REVERSE); //倒叙重新开始
valueAnimatorRepeat.setRepeatCount(ValueAnimator.INFINITE); // 无限循环
valueAnimatorRepeat.setDuration(2500);
valueAnimatorRepeat.start();
}
在这里,我们构造一个ValueAnimator,动画方位时0到400,设置重复次数无限循环,重复模式为倒序。当活动结束的时候,必须调用cancel()函数取消动画,否则动画将会无限循环,导致View无法释放,进一步导致Activity无法释放,最终引起内存泄漏。
自定义插值器
我们通过ofInt(0,400)定义了动画的区间值 0 到400,然后通过添加AnimatorUpdateListener来监听动画的实时变化。那么问题来了,0到400之间的值是怎么变化的呢?像我们跑步,还有得快,有的慢,这个值是匀速变化的吗?如果是,想让他一直加入该怎么办呢?这就是插值器的作用。
插值器就是控制动画区间值如何被计算出来,比如LinearInterpolator插值器表示匀速返回区间内的值,等等其它插值器。
在自定义插值器之前,先看看系统自带的插值器是如何实现的,比如LinearInterpolator
public class LinearInterpolator implements Interpolator {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input;
}
}
LinearInterpolator 实现了Interpolator 接口,而Interpolator 接口直接继承TimeInterpolator,并且没有添加任何其它的方法。
package android.animation;
public interface TimeInterpolator {
float getInterpolation(float input);
}
参数input :它的取值方位0到1,表示当前动画的进度,0表示动画开始,1表示动画结束,0.5表示动画中间的位置。表示当前的动画参数是匀速增加的,动画进度就是动画在时间上的进度,随着时间推移,动画自然从0到1逐渐增加。input参数相当于时间的概念,我们通过setDuration()指定了动画时长。
返回值:表示当前实际想要显示的进度,取值可以超过1,表示超过目标值,小于0 表示小于开始值。
ValueAnimator valueAnimator = ValueAnimator.ofInt(100, 500);
valueAnimator.setDuration(1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
tv.layout(curValue, curValue, tv.getWidth() + curValue, tv.getHeight() + curValue);
}
});
valueAnimator.start();
animation.getAnimatedValue() 得到当前值是怎么来的?看下面的计算公式,目前可以这么理解
当前的值 = 100 + (400 - 100 ) * 显示进度
其中,100和400就是我们设置ofFloat (100,400)中的值
input 参数就表示当前动画进度,而返回值则表示当前动画的数值进度。
我们自定义插值器
package com.as.propertyanimator;
import android.animation.TimeInterpolator;
public class MyInterpolator implements TimeInterpolator {
@Override
public float getInterpolation(float input) {
return 1 - input;
}
}
在这个自定义插值器中我们将进度反转过来,当传入0的时候,让它的数值进度在完成的位置,当完成的时候,让它的数值进度在开始位置。
private void myInterpolator() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 300);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
tv.layout(tv.getLeft(), curValue, tv.getRight(), tv.getHeight() + curValue);
}
});
valueAnimator.setDuration(1000);
valueAnimator.setInterpolator(new MyInterpolator());
valueAnimator.start();
}
Evaluator(求值器)
上图讲述了从定义动画的数值区间到在AnimatorUpdateListener中得到当前动画所对应数值的整个过程。
这4个步骤的具体含义如下
(1)ofInt(0,400) 表示指定动画的数值区间,从0运动到400
(2)插值器:在动画开始后,返回当前动画进度所对应的数值进度,但这个数值进度是以小数表示的,如0.2。
(3)Evaluator:我们通过监听器拿到的是当前动画所对应的具体数值,而不是用小数表示的数值。那么必须有一个地方根据档期那数值进度转化为对应数值,这个地方就是Evaluator。Evaluator用于将插值器返回的数值进度转化为对应数值。
(4)监听器返回:我们通过AnimatorUpdateListener 监听器中使用animation.getAnimatedValue()函数拿到Evaluator中返回的数值。
讲了这么多,Evaluator其实就是一个转换器,能把小数进度转换成对应的数值。
在设置Evaluator时,是通过animator.setEvaluator()函数来实现的,比如:
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 300);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
tv.layout(tv.getLeft(), curValue, tv.getRight(), tv.getHeight() + curValue);
}
});
valueAnimator.setDuration(1000);
valueAnimator.setEvaluator(new IntEvaluator());
valueAnimator.setInterpolator(new BounceInterpolator());
valueAnimator.start();
我们设置了IntEvaluator,用来计算数值进度所对应的数值。但在此之前,我们在使用ofInt()函数时,从来没有定义过使用IntEvaluator来转换值,能正常运行是因为ofInt()和ofFloat()都是系统直接提供的函数,所以会有默认的插值器和Evaluator可供使用。下面看下IntEvaluator内部是怎么实现的。
package android.animation;
/**
* This evaluator can be used to perform type interpolation between <code>int</code> values.
*/
public class IntEvaluator implements TypeEvaluator<Integer> {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
- fraction 参数就是插值器中返回的值,表示当前动画的百分比进度。
- startValue 和 endValue 分别对应ofInt(int start,int end)函数中start 和 end值
- 返回值就是当前数值进度所对应的具体数值
假设当我们定义的动画ofInt(200,500)进行到数值进度10%的时候,我们来算下具体值
return (int)(startInt + fraction * (endValue - startInt));
当前值 = 200 + 0.1 * (500 - 200)
简单实现Evaluator
下面仿照一个IntEvaluator 的实现方法,自定义一个MyEvalutor
package com.as.propertyanimator;
import android.animation.TypeEvaluator;
public class MyEvaluator implements TypeEvaluator<Integer> {
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
return (int) (200 + startValue + fraction * (endValue - startValue));
}
}
我们在IntEvaluator的基础上修改了一下,让它返回时增加200。当我们定义一个ofInt(0,500)时,它的实际返回值区间时(200,700)。
很明显,TextView 的动画位置都向下移动了200px。
自定义插值器实现倒序输出
package com.as.propertyanimator;
import android.animation.TypeEvaluator;
/**
* 倒叙输出
*/
public class ReverseEvaluator implements TypeEvaluator<Integer> {
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
return (int) (endValue - fraction * (endValue - startValue));
}
}
fraction * (endValue - startValue) 表示动画的实际运动距离,我们用endValue 减去实际运动距离表示距离终点越来越远,也就实现了从终点出发,到起点的效果
private void myReverseEvaluator() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 300);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
tv.layout(tv.getLeft(), curValue, tv.getRight(), tv.getHeight() + curValue);
}
});
valueAnimator.setDuration(1000);
valueAnimator.setEvaluator(new ReverseEvaluator());
valueAnimator.start();
}
我们来实现颜色过渡转换
private void colorEvaluator() {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0xffffff00, 0xff0000ff);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
tv.setBackgroundColor(curValue);
}
});
valueAnimator.setDuration(5000);
valueAnimator.setEvaluator(new ArgbEvaluator());
valueAnimator.start();
}
我们将动画的定义为(0xffffff00, 0xff0000ff),即从黄色变为蓝色。在监听事件中,我们根据当前传回的颜色值,将其设置为TextView 的背景色。
这里需要注意的是,必须使用ofInt()函数来定义颜色的取值范围,并且颜色必须包含A,R,G,B 4个值,我们来简单看一下ArgbEvaluator的源码
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.animation;
import android.annotation.UnsupportedAppUsage;
/**
* This evaluator can be used to perform type interpolation between integer
* values that represent ARGB colors.
*/
public class ArgbEvaluator implements TypeEvaluator {
public Object evaluate(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
float startA = ((startInt >> 24) & 0xff) / 255.0f;
float startR = ((startInt >> 16) & 0xff) / 255.0f;
float startG = ((startInt >> 8) & 0xff) / 255.0f;
float startB = ( startInt & 0xff) / 255.0f;
int endInt = (Integer) endValue;
float endA = ((endInt >> 24) & 0xff) / 255.0f;
float endR = ((endInt >> 16) & 0xff) / 255.0f;
float endG = ((endInt >> 8) & 0xff) / 255.0f;
float endB = ( endInt & 0xff) / 255.0f;
// compute the interpolated color in linear space
float a = startA + fraction * (endA - startA); //当前进度的透明度
float r = startR + fraction * (endR - startR); //当前进度下的红色值
float g = startG + fraction * (endG - startG); //当前进度下的绿色值
float b = startB + fraction * (endB - startB); //当前进度下的蓝色值
return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b);
}
}
这段代码分为三部分,第一部分根据startValue 起初 A,R,G,B中各个色彩的初始值,第二部分根据endValue 求出 A,R,G,B 各个色彩的结束值,第三部分根据当前动画的百分比进度求出对应的数值,最后通过或运算把结果拼接到一个整型 4个字节中。
这段代码根据位移和与运算求出颜色中A,R,G,B 各个部分对应的值,
如果大家对位移和与运算及如何得到指定为不太了解的画,可以看我这篇博客算法之美,位运算。
ValueAnimator 进阶 ofObject
ofInt()函数只能传入Integer类型,ofFloat只能传入Float 类型,如果我们需要操作其它类型的变量该怎么办呢?其实,ValueAnimator还有一个函数ofObject(),可以传入任何类型的变量,该函数的定义如下:
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)
它有两个参数,第一个参数是自定义Evaluator;第二个参数是可变参数,属于Object类型。既然Object对象是我们自定义的,那么进度值的转换过程也由我们来做,否则系统不可能知道转换出来的具体值是什么。
下面我们尝试使用ofObject函数实现下面的效果,将TextView 中的字母从A变化到Z。
package com.as.propertyanimator;
import android.animation.TypeEvaluator;
/**
* 动画求值器从字母A到字母Z
*/
public class CharEvaluator implements TypeEvaluator<Character> {
@Override
public Character evaluate(float fraction, Character startValue, Character endValue) {
int startInt = startValue; //A = 65
int endInt = endValue; //Z = 90
return (char) (startInt + fraction * (endInt - startInt)); //当前字符
}
}
private void objectEvaluator() {
//我们要实现的动画效果是从字母A到字母Z
ValueAnimator valueAnimator = ValueAnimator.ofObject(new CharEvaluator(), 'A', 'Z');
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//获取当前动画的值==>获取到当前字母
char curValue = (char) animation.getAnimatedValue();
tv.setText(String.valueOf(curValue));
}
});
//动画匀速==>线性变化
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setDuration(5000);
valueAnimator.start();
}
我们自定义了一个CharEvaluator;在初始动画时,传入的是char对象,一个是字母A,另一个是字母Z。我们先来了解下ASCIi 码表中数字与字符的转换方法,每个字符都有一个对应的数字,字母A到Z对应的数字区间65到90,在程序中,我们可以将数字转换为字符,也可以将字符转化为数字。
数字转字符
char c = (char) 65; //得到的c 就是大写字母A
char temp = 'A';
int num = (int) temp; // 65
我们来实现一个小球落地的动画
先用shape 标签实现一个圆形drawable (circle.xml)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#03A9F4" />
</shape>
然后实现布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="Start Anim" />
<Button
android:id="@+id/btnCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btnStart"
android:layout_marginTop="30dp"
android:padding="10dp"
android:text="Cancel Anim" />
<TextView
android:id="@+id/tv"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_below="@+id/btnCancel"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:background="@drawable/circle"
android:padding="10dp"
android:textColor="@android:color/white" />
</RelativeLayout>
将上面的圆形shape 作为ImageView 的源文件显示出来,接着实现动画
package com.as.propertyanimator;
import android.animation.TypeEvaluator;
import android.graphics.Point;
/**
* 蹦蹦求 从空中落到地面上
*/
public class FallingBallEvaluator implements TypeEvaluator<Point> {
//蹦蹦求返回值
private Point mPoint = new Point();
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
int x = (int) (startValue.x + (fraction * (endValue.x - startValue.x)));
mPoint.x = x;
int y = (int) (startValue.y + (fraction * (endValue.y - startValue.y)));
mPoint.y = y;
return mPoint;
}
}
private void ballEvaluator() {
//定义了球的位置
ValueAnimator valueAnimator = ValueAnimator.ofObject(new FallingBallEvaluator(), new Point(0, 0), new Point(500, 500));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//获取当前动画的值==>获取到当前x 和 y 位置
Point curValue = (Point) animation.getAnimatedValue();
//将球移动到指定位置
tv.layout(curValue.x, curValue.y, curValue.x + tv.getWidth(), curValue.y + tv.getHeight());
}
});
// 弹跳插值器,模拟了控件自由落地后回弹的效果
valueAnimator.setInterpolator(new BounceInterpolator());
valueAnimator.setDuration(3000);
valueAnimator.start();
}