前言:总结之前学习的关于属性动画的笔记和学习了郭霖大神的属性动画高级用法相关的博客之后的记录
1.在文章的开始首先贴上郭霖 大神关于 属性动画高级用法相关的博客地址:
博客Url: http://blog.csdn.net/sinyu890807/article/details/43816093
2.学习loading…
2.1补间动画 PK 属性动画
- 在篇1(http://blog.csdn.net/yk377657321/article/details/52683094)已经列举了大致的用法;
- 补间动画只能对View对象进行动画操作,而属性动画则可以对任意对象进行动画操作;
- 补间动画只是改变了View的显示效果,而不会去改变View的属性;
2.2开始学习
1.首先认识下属性动画中非常核心的一个类,就是FloatEvaluator类,它主要告诉动画系统如何从初始值过渡到结束值,它是系统默认的TypeEvaluator的实现类.
public class FloatEvaluator implements TypeEvaluator {
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}2.具体的代码步骤详解可以查看上面的博客链接,这里就直接代码走起
/**
* Created by wyk on 2016/9/25.
* 描述:告诉动画系统从初始值过渡到结束值,属性动画在系统中有个默认的TypeEvaluator,它就是
* FloatEvaluator,继承自TypeEvaluator,属性动画的高级用法中最有技术含量的也就是如何编写出
* 一个合适的TypeEvaluator
*
* 作用:根据fraction的变换得到圆球的中点,装载在Point对象里,最后将其返回
*/
public class PointEvaluator implements TypeEvaluator{
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
return new Point(x,y);
}
}
具体介绍 在代码中的注释中已详细说明
/**
* Created by wyk on 2016/9/25.
* 描述:记录圆球的中心在x轴y轴上的值
* 作用:1.供PointEvaluator去记载当前位置
* 2.在CircleView类中可以根据Point的xy绘制圆球
*/
public class Point {
private float x;
private float y;
public Point(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public float getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
}
自定义View,实现圆球的动画效果
/**
* Created by wyk on 2016/9/25.
* 描述:自定义View,实现圆球的动画效果
* 作用:1.学习属性动画的核心类;
* 2.学习ObjectAnimator内部的工作机制,主要通过寻找对象特定属性的get和set方法,
* 然后通过方法不断地对特定属性值进行改变,从而实现动画效果
* 3.温故而知新,重新学习下属性动画的api
* 4.熟悉自定义控件中自定义属性的声明以及应用
*
*/
public class CircleView extends View {
private static final float RADIUS = 50F;
private Paint mPaint;
private Point currenPoint;
private int color;
private float radius;
private float delay;
private ValueAnimator vAnimator;
private ObjectAnimator colorAnimator;
public CircleView(Context context) {
this(context,null);
}
public CircleView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context,attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
if(color != 0){
mPaint.setColor(color);
return;
}
mPaint.setColor(Color.BLUE);
}
/**读取自定义属性,使用者可以直接在布局文件中使用该View和其定义的属性*/
private void initView(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable
.circle_view_style);
color = typedArray.getColor(R.styleable.circle_view_style_circle_color,getResources()
.getColor(R.color.colorAccent));
radius = typedArray.getFloat(R.styleable.circle_view_style_circle_radius, 50);
delay = typedArray.getFloat(R.styleable.circle_view_style_circle_start_delay, 500);
typedArray.recycle(); //note
}
@Override
protected void onDraw(Canvas canvas) {
if(currenPoint == null){
if(radius == 0){
currenPoint = new Point(RADIUS,RADIUS);
}else{
currenPoint = new Point(radius,radius);
}
drawCircle(canvas);
startAnimator(); //开启动画
}else{
drawCircle(canvas);
}
}
private void drawCircle(Canvas canvas) { //除了第一次View自身绘制会进来该方法,
// 之后都是由于ValueAnimator的监听改变而调用View的onDraw方法,从而调用该方法
float x = currenPoint.getX();
float y = currenPoint.getY();
canvas.drawCircle(x,y,radius,mPaint);
}
private void startAnimator() {
Point startPoint = new Point(RADIUS,RADIUS);
Point endPoint = new Point(getWidth() - RADIUS , getHeight() - RADIUS);
vAnimator = ValueAnimator.ofObject(new PointEvaluator(),startPoint,endPoint);
vAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currenPoint = (Point)animation.getAnimatedValue();
invalidate(); //会调用onDraw方法,不断重绘
}
});
colorAnimator = ObjectAnimator.ofObject(this, "circleColor", new
ColorEvaluator(), "#0000ff", "#ff0000");
vAnimator.setRepeatCount(ValueAnimator.INFINITE);
vAnimator.setRepeatMode(ValueAnimator.REVERSE);
AnimatorSet anSet = new AnimatorSet();
anSet.play(vAnimator).with(colorAnimator);
anSet.setDuration(2000);
anSet.start();
/* vAnimator.setDuration(2000);
vAnimator.setStartDelay((long) delay);
vAnimator.setRepeatCount(100);
vAnimator.setRepeatMode(ValueAnimator.REVERSE);
vAnimator.setInterpolator(new OvershootInterpolator());
vAnimator.start();*/
}
public String circleColor;
/**ObjectAnimator动画的核心*/
public String getCircleColor() {
return circleColor;
}
public void setCircleColor(String circleColor) {
this.circleColor = circleColor;
mPaint.setColor(Color.parseColor(circleColor));
invalidate();
}
}
/**
* Created by wyk on 2016/9/25.
* 描述:告诉动画系统从初始值过渡到结束值,属性动画在系统中有个默认的TypeEvaluator,
* 它就是FloatEvaluator,继承自TypeEvaluator
* 属性动画的高级用法中最有技术含量的也就是如何编写出一个合适的TypeEvaluator
*
* 作用:该类主要进行计算当前的颜色值
*/
public class ColorEvaluator implements TypeEvaluator {
private int mCurrentRed = -1;
private int mCurrentGreen = -1;
private int mCurrentBlue = -1;
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
String startColor = (String)startValue;
String endColor = (String)endValue;
int startRed = Integer.parseInt(startColor.substring(1,3),16); //16进制转换成10进制
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,// 更是小于startColorl;
currentColor = endColor;
}
}else{
currentColor = (int) (startColor + (fraction * colorDiff - offset));
if(currentColor > endColor){
currentColor = endColor;
}
}
//上面主要是保证 currentColor主要在 [startColor,endColor] 这个区间内
return currentColor;
}
//将10进制转换成16进制
private String getHexString(int value){
String hexString = Integer.toHexString(value);
if(hexString.length() == 1){
hexString = "0" + hexString; //转换成16进制的时候,可能只有0-9,abcdef这些,即只有一位,故前边补0
}
return hexString;
}
}
- 3.最后贴上MainActivity的代码和Layout布局
/**
* Created by wyk on 2016/9/25.
* 作用:展示CircleView的动画效果
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
// setContentView(R.layout.activity_main); //学习顺序01
setContentView(R.layout.activity_main01); //学习顺序02
}
}
/**
*activity_main.xml布局
*/
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:circle="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--如果你想要在屏幕上展示不少于一个圆球,那么你就得限定CircleView的空间大小,
否则只会显示一个圆球,其他圆球皆被覆盖-->
<LinearLayout
android:layout_width="100dp"
android:layout_height="100dp">
<com.hy.myview.CircleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
circle:circle_radius= "60"
circle:circle_color="#00ff00"
circle:circle_start_delay="1000"/>
</LinearLayout>
<LinearLayout
android:layout_width="140dp"
android:layout_height="0dp"
android:layout_weight="1">
<com.hy.myview.CircleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
circle:circle_radius="80"
circle:circle_color ="#ff0000"
circle:circle_start_delay="2000"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="180dp"
android:orientation="vertical">
<com.hy.myview.CircleView
android:layout_width="60dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
circle:circle_color="#0000ff"
circle:circle_start_delay="3000"/>
</LinearLayout>
</LinearLayout>
/**
*activity_main01.xml布局
*/
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:circle="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.hy.myview.CircleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
circle:circle_radius= "60"
circle:circle_color="#00ff00"
circle:circle_start_delay="1000"/>
</LinearLayout>
- 4.对了,还有一件重要的事情还没做,自定义属性部分的code,罪过罪过!
/**
*在res/values目录下创建attrs.xml
*具体声明 看下面
*/
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="circle_view_style">
<attr name="circle_radius" format="float"></attr>
<attr name="circle_color" format="color"></attr>
<attr name="circle_start_delay" format="float"></attr>
</declare-styleable>
</resources>
3.在上面的CirlceView类的startAnimator 方法中加了如下两句:
vAnimator.setRepeatCount(ValueAnimator.INFINITE);
vAnimator.setRepeatMode(ValueAnimator.REVERSE);
主要作用是让动画可以重复执行,那么就出现这么一个问题:
圆球走到右下角后,回到左上角,再依次执行的过程中,颜色并无改变;
看到大神的博客评论区有如下评论
针对上面评论所说的解决方法是可行的,前提是基于大神的博客,效果是 “圆球从左上角移动到右下角,这一过程循环执行”,由于本份代码设置了动画的模式为 “ValueAnimator.REVERSE”,所以效果是 “圆球从左上角移动到右下角,接着从右下角移动回左上角,这一过程循环执行”,所以设置 “ValueAnimator.INFINITE” 并不起作用.
解决方法(直接上代码,代码里有详细的注释)
/**
* CircleView类
*/
private void startAnimator() {
Point startPoint = new Point(RADIUS,RADIUS);
Point endPoint = new Point(getWidth() - RADIUS , getHeight() - RADIUS);
vAnimator = ValueAnimator.ofObject(new PointEvaluator(),startPoint,endPoint);
vAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currenPoint = (Point)animation.getAnimatedValue();
invalidate(); //会调用onDraw方法,不断重绘
}
});
colorAnimator = ObjectAnimator.ofObject(this, "circleColor", new
ColorEvaluator(), "#0000ff", "#ff0000");
vAnimator.setRepeatCount(ValueAnimator.INFINITE);
vAnimator.setRepeatMode(ValueAnimator.REVERSE);
anSet = new AnimatorSet();
anSet.play(vAnimator).with(colorAnimator);
anSet.setDuration(5000);
anSet.start();
vAnimator.addListener(new AnimatorListenerAdapter() {
//AnimatorListenerAdapter是监听执行操作的动画适配器类,实现于Animator.AnimatorListener,
// 当然我们也可以在这里创建AnimatorListener类,但是由于实现该接口必须重写里面的6个方法,
// 而AnimatorListenerAdapter实现于Animator.AnimatorListener,通过重写里面的6个方法,方法体为空,
//我们可以更简便的挑选所需要的接口来使用;
@Override
public void onAnimationRepeat(Animator animation) { //动画重复执行时,会被调用,例如小球回到左上角/右下角都会被调用
//在达到底部的时候,如果不想圆球的颜色由红色直接变成蓝色,这里可以设置Tag进行标识
//当tag为0 的时候,颜色由#ff0000" ---过渡--> "#0000ff;
// 当tag为1 的时候,颜色由#0000ff" ---过渡--> "#ff0000;关于改变颜色突变的效果的代码就不写了
colorAnimator = ObjectAnimator.ofObject(CircleView.this, "circleColor", new
ColorEvaluator(), "#0000ff", "#ff0000");
colorAnimator.setDuration(5000);
colorAnimator.start();
}
});
}
4.总结:
1.属性动画不同于补间动画,补间动画主要改变的是显示效果,其属性根本就木有被改变;例如举个简单的小例子,我们对一个设置了点击后show Toast的按钮执行补间动画,从位置A移动到位置B后,点击位置B的按钮,并无任何反应,再点击位置A,A位置可是没有按钮额,可是就有show Toast的效果;
2.属性动画的扩展性很强大,除了对View进行动画操作,还可以对任意对象进行动画操作,其核心是TypeEvaluator,主要告诉动画系统如何从初始值过渡到结束值,在其evaluate method中进行计算得到当前值 然后返回;
3.ObjectAnimator继承自ValueAnimator,其工作机制是通过特定属性的get/set方法对属性值进行改变,从而实现动画效果;
敲了这么多行代码,怎能少了演示效果,这里由于手机录屏软件由于格式导致录制不成功,直接借鉴郭霖 大神的效果图,如有侵权,请告知删除:
属性动画类别的的博文:
- 属性动画与补间动画总结篇(篇1): http://blog.csdn.net/yk377657321/article/details/52683094
- Android 属性动画进阶篇(篇2): http://blog.csdn.net/yk377657321/article/details/52746328
- Android 属性动画进阶总结篇(篇3): http://blog.csdn.net/yk377657321/article/details/52761211
- Android 属性动画进阶总结篇(篇4):http://blog.csdn.net/yk377657321/article/details/52761453