app开发者平台在数字化时代的重要性与发展趋势解析
1058
2022-11-17
属性动画详解
今天跟着blog详细了解属性动画。简书学习
1、属性动画出现的原因
而属性动画的出现能够解决上述全部问题。
2、属性动画的工作原理
3、具体的使用
3.1、ValueAnimator类:
属性动画中最核心的一个类,通过不断控制值的变化,在不断手动赋值给对象的属性,从而实现动画效果:
从上图的ValueAnimator可以看出有三个很重要的方法:
ValueAnimator.ofInt(int values)ValueAnimator.ofFloat(float values)ValueAnimator.ofObject(int values)
下面将逐一介绍:
3.1.1、ValueAnimator.ofInt(int values): 其工作原理就是和第一张图一样。本质上是一种对值的操作。
下面可以举一个实例,如何将一个值从0到3平滑的过渡。操作方式为xml/java代码中实现。
(建议实际多用java代码实现,因为很多属性值是一开始不知道的,要动态的去获取)通过java代码实现:
// 步骤1:设置动画属性的初始值 & 结束值ValueAnimator anim = ValueAnimator.ofInt(0, 3); // ofInt()作用有两个 // 1. 创建动画实例 // 2. 将传入的多个Int参数进行平滑过渡:此处传入0和1,表示将值从0平滑过渡到1 // 如果传入了3个Int参数 a,b,c ,则是先从a平滑过渡到b,再从b平滑过渡到C,以此类推 // ValueAnimator.ofInt()内置了整型估值器,直接采用默认的.不需要设置,即默认设置了如何从初始值 过渡到 结束值 // 关于自定义插值器我将在下节进行讲解 // 下面看看ofInt()的源码分析 ->>关注1 // 步骤2:设置动画的播放各种属性 anim.setDuration(500); // 设置动画运行的时长 anim.setStartDelay(500); // 设置动画延迟播放时间 anim.setRepeatCount(0); // 设置动画重复播放次数 = 重放次数+1 // 动画播放次数 = infinite时,动画无限重复 anim.setRepeatMode(ValueAnimator.RESTART); // 设置重复播放动画模式 // ValueAnimator.RESTART(默认):正序重放 // ValueAnimator.REVERSE:倒序回放 // 步骤3:将改变的值手动赋值给对象的属性值:通过动画的更新- // 设置 值的更新- // 即:值每次改变、变化一次,该方法就会被调用一次 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int currentValue = (Integer) animation.getAnimatedValue(); // 获得改变后的值 System.out.println(currentValue); // 输出改变后的值 // 步骤4:将改变后的值赋给对象的属性值,下面会详细说明 View.setproperty(currentValue); // 步骤5:刷新视图,即重新绘制,从而实现动画效果 View.requestLayout(); } }); anim.start(); // 启动动画 }// 关注1:ofInt()源码分析 public static ValueAnimator ofInt(int... values) { // 允许传入一个或多个Int参数 // 1. 输入一个的情况(如a):从0过渡到a; // 2. 输入多个的情况(如a,b,c):先从a平滑过渡到b,再从b平滑过渡到C ValueAnimator anim = new ValueAnimator(); // 创建动画对象 anim.setIntValues(values); // 将传入的值赋值给动画对象 return anim; }
值从初始值过渡到结束值如下:
通过XML实现:
现在res/animator的文件创建相应的动画.xml文件,再在这个文件设置参数:
// ValueAnimator采用
最后在java中启动这个动画:
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation); // 载入XML动画animator.setTarget(view); // 设置动画对象animator.start(); // 启动动画
效果和java代码实现的那个一样。
实例:对一个宽度为150px的botton做一个宽度边长到500的动画效果:
Button mButton = (Button) findViewById(R.id.Button); // 创建动画作用对象:此处以Button为例// 步骤1:设置属性数值的初始值 & 结束值 ValueAnimator valueAnimator = ValueAnimator.ofInt(mButton.getLayoutParams().width, 500); // 初始值 = 当前按钮的宽度,此处在xml文件中设置为150 // 结束值 = 500 // ValueAnimator.ofInt()内置了整型估值器,直接采用默认的.不需要设置 // 即默认设置了如何从初始值150 过渡到 结束值500// 步骤2:设置动画的播放各种属性 valueAnimator.setDuration(2000); // 设置动画运行时长:1s// 步骤3:将属性数值手动赋值给对象的属性:此处是将 值 赋给 按钮的宽度 // 设置更新-:即数值每次变化更新都会调用该方法 valueAnimator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animator) { int currentValue = (Integer) animator.getAnimatedValue(); // 获得每次变化后的属性值 System.out.println(currentValue); // 输出每次变化后的属性值进行查看 mButton.getLayoutParams().width = currentValue; // 每次值变化时,将值手动赋值给对象的属性 // 即将每次变化后的值 赋 给按钮的宽度,这样就实现了按钮宽度属性的动态变化// 步骤4:刷新视图,即重新绘制,从而实现动画效果 mButton.requestLayout(); } }); valueAnimator.start(); // 启动动画 }
效果如下:
3.1.2、ValueAnimator.ofFloat(float values) 和ofInt不同的是,它是将初始值以浮点数的形式过渡到结束值。工作流程和ofInt一样。同样也可以通过xml设置和java代码完成动画。 java代码就不做阐述,下面看一下xml文件中的设置:
// ValueAnimator采用
接着在java启动,最后效果图如下:
可以看出ofInt和ofFloat的差别仅仅是在估值器上的区别,ofInt采用默认的整型估值器(IntEvaluator),ofFloat采用默认的浮点型估值器(FloatEvaluator)
3.1.3、VlueAnimator.ofObject() 作用是将初始值以对象的形式过渡到结束值,流程上面图有说到,有默认的插值器,没有默认的估值器,只能自己自定义实现。
具体使用为:
// 创建初始动画时的对象 & 结束动画时的对象myObject object1 = new myObject(); myObject object2 = new myObject(); ValueAnimator anim = ValueAnimator.ofObject(new myObjectEvaluator(), object1, object2); // 创建动画对象 & 设置参数// 参数说明// 参数1:自定义的估值器对象(TypeEvaluator 类型参数) - 下面会详细介绍// 参数2:初始动画的对象// 参数3:结束动画的对象anim.setDuration(5000); anim.start();
在ofObject的使用中,我们要去手写一个估值器对象。下面介绍一下估值器:
估值器的介绍 作用是设置动画 初始值 到 结束值 时的逻辑。 插值器(Interpolator)可以决定值的变化模式(匀速、变速) 估值器(TypeEvaluator)可以决定值的具体变化竖直。
之前可以看到ofFloat中,实现了将初始值以浮点数的形式过渡到结束值的逻辑,而这个逻辑就是由FloatEvaluator这个默认估值器实现的,我们来看下它的代码:
public class FloatEvaluator implements TypeEvaluator { // FloatEvaluator实现了TypeEvaluator接口// 重写evaluate() public Object evaluate(float fraction, Object startValue, Object endValue) { // 参数说明// fraction:表示动画完成度(根据它来计算当前动画的值)// startValue、endValue:动画的初始值和结束值 float startFloat = ((Number) startValue).floatValue(); return startFloat + fraction * (((Number) endValue).floatValue() - startFloat); // 初始值 过渡 到结束值 的算法是: // 1. 用结束值减去初始值,算出它们之间的差值 // 2. 用上述差值乘以fraction系数 // 3. 再加上初始值,就得到当前动画的值 } }
上述就是系统默认的估值器,而对于ofObject,系统没法默认实现,因为其中的值是对象,而对象复杂多样,所以要求我们自己去实现。 自定义一个估值器去实现TypeEvaluator接口的实现如下:
// 实现TypeEvaluator接口public class ObjectEvaluator implements TypeEvaluator{ // 复写evaluate()// 在evaluate()里写入对象动画过渡的逻辑 @Override public Object evaluate(float fraction, Object startValue, Object endValue) { // 参数说明 // fraction:表示动画完成度(根据它来计算当前动画的值) // startValue、endValue:动画的初始值和结束值 ... // 写入对象动画过渡的逻辑 return value; // 返回对象动画过渡的逻辑计算后的值 }
下面来实现一个实例:使一个圆从一个点移动到另一个点:
工程目录如图:
因为ofObject是面向对象操作的,所以我们要自己去自定义对象类。
本例需要操作的是对象的圆的坐标。
步骤1:Point.java 代码:
public class Point { // 设置两个变量用于记录坐标的位置 private float x; private float y; // 构造方法用于设置坐标 public Point(float x, float y) { this.x = x; this.y = y; } // get方法用于获取坐标 public float getX() { return x; } public float getY() { return y; }}
步骤2:根据需求实现TypeEvaluator接口: 本例是实现一个从左上角到右下角移动的逻辑,编写PointEvaluator.java:
// 实现TypeEvaluator接口public class PointEvaluator implements TypeEvaluator { // 复写evaluate() // 在evaluate()里写入对象动画过渡的逻辑 @Override public Object evaluate(float fraction, Object startValue, Object endValue) { // 将动画初始值startValue 和 动画结束值endValue 强制类型转换成Point对象 Point startPoint = (Point) startValue; Point endPoint = (Point) endValue; // 根据fraction来计算当前动画的x和y的值 float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX()); float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY()); // 将计算后的坐标封装到一个新的Point对象中并返回 Point point = new Point(x, y); return point; }}
步骤3:将属性动画作用到自定义view中: MyView.java:
public class MyView extends View { // 设置需要用到的变量 public static final float RADIUS = 70f;// 圆的半径 = 70 private Point currentPoint;// 当前点坐标 private Paint mPaint;// 绘图画笔 // 构造方法(初始化画笔) public MyView(Context context, AttributeSet attrs) { super(context, attrs); // 初始化画笔 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.BLUE); } // 复写onDraw()从而实现绘制逻辑 // 绘制逻辑:先在初始点画圆,通过监听当前坐标值(currentPoint)的变化,每次变化都调用onDraw()重新绘制圆,从而实现圆的平移动画效果 @Override protected void onDraw(Canvas canvas) { // 如果当前点坐标为空(即第一次) if (currentPoint == null) { currentPoint = new Point(RADIUS, RADIUS); // 创建一个点对象(坐标是(70,70)) // 在该点画一个圆:圆心 = (70,70),半径 = 70 float x = currentPoint.getX(); float y = currentPoint.getY(); canvas.drawCircle(x, y, RADIUS, mPaint); // (重点关注)将属性动画作用到View中 // 步骤1:创建初始动画时的对象点 & 结束动画时的对象点 Point startPoint = new Point(RADIUS, RADIUS);// 初始点为圆心(70,70) Point endPoint = new Point(700, 1000);// 结束点为(700,1000) // 步骤2:创建动画对象 & 设置初始值 和 结束值 ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint); // 参数说明 // 参数1:TypeEvaluator 类型参数 - 使用自定义的PointEvaluator(实现了TypeEvaluator接口) // 参数2:初始动画的对象点 // 参数3:结束动画的对象点 // 步骤3:设置动画参数 anim.setDuration(5000); // 设置动画时长// 步骤3:通过 值 的更新-,将改变的对象手动赋值给当前对象// 此处是将 改变后的坐标值对象 赋给 当前的坐标值对象 // 设置 值的更新- // 即每当坐标值(Point对象)更新一次,该方法就会被调用一次 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { currentPoint = (Point) animation.getAnimatedValue(); // 将每次变化后的坐标值(估值器PointEvaluator中evaluate()返回的Piont对象值)到当前坐标值对象(currentPoint) // 从而更新当前坐标值(currentPoint)// 步骤4:每次赋值后就重新绘制,从而实现动画效果 invalidate(); // 调用invalidate()后,就会刷新View,即才能看到重新绘制的界面,即onDraw()会被重新调用一次 // 所以坐标值每改变一次,就会调用onDraw()一次 } }); anim.start(); // 启动动画 } else { // 如果坐标值不为0,则画圆 // 所以坐标值每改变一次,就会调用onDraw()一次,就会画一次圆,从而实现动画效果 // 在该点画一个圆:圆心 = (30,30),半径 = 30 float x = currentPoint.getX(); float y = currentPoint.getY(); canvas.drawCircle(x, y, RADIUS, mPaint); } }}
步骤4:在布局文件中加入自定义view
最后启动Activity动画效果出来了。
至此,属性动画最核心的ValueAnimator类已经讲解完毕。接下来讲解另一个重要的类。
3.2、ObjectAnimator类
直接对对象的属性值进行改变,从而实现动画效果。 比如直接改变一个view的alpha属性从而实现透明的动画效果 ObjectAnimator是继承ValueAnimator类,所以其底层实现还是基于ValueAnimator类。
本质是 通过不断的变化值, 自动的 赋值给对象属性。
工作流程如图:
从图中可以看到ObjectAnimator和ValueAnimator的区别:
ValueAnimator是先通过估值器改变值,然后手动赋值给对象属性从而实现动画;是间接对对象属性进行操作。ObjectAnimator是先改变值,然后自动赋值给对象的属性从而实现动画。是直接对对象属性进行操作。
具体使用:(可以用xml和java) java设置:
ObjectAnimator animator = ObjectAnimator.ofFloat(Object object, String property, float ....values); // ofFloat()作用有两个// 1. 创建动画实例// 2. 参数设置:参数说明如下// Object object:需要操作的对象// String property:需要操作的对象的属性// float ....values:动画初始值 & 结束值(不固定长度)// 若是两个参数a,b,则动画效果则是从属性的a值到b值// 若是三个参数a,b,c,则则动画效果则是从属性的a值到b值再到c值// 以此类推// 至于如何从初始值 过渡到 结束值,同样是由估值器决定,此处ObjectAnimator.ofFloat()是有系统内置的浮点型估值器FloatEvaluator,同ValueAnimator讲解anim.setDuration(500); // 设置动画运行的时长 anim.setStartDelay(500); // 设置动画延迟播放时间 anim.setRepeatCount(0); // 设置动画重复播放次数 = 重放次数+1 // 动画播放次数 = infinite时,动画无限重复 anim.setRepeatMode(ValueAnimator.RESTART); // 设置重复播放动画模式 // ValueAnimator.RESTART(默认):正序重放 // ValueAnimator.REVERSE:倒序回放animator.start(); // 启动动画
在xml中设置:
// ObjectAnimator 采用
然后在java中设置target等然后通过start开启一个动画。
通过在ofFloat中传入property,可以实现4个动画:透明度变化、缩放、平移、旋转,为:
“alpha”:((1,0,1)从不透明到透明再到不透明)“translationX/Y” :平移到指定的x或者y坐标“scaleX/Y”:(scale是倍数,可以缩放横向和纵向长度为原来的几倍)“rotation”:((0,360)转一圈)
当然了,除了这一些属性,还可以传入别的属性!当然这些属性在对应的类要有对应的get和set方法。
比如对一个button的alpha进行操作,而button中就有 getAlpha方法和setAlpha(float values)。就是对这两个方法进行操作。而这些属性view都有,继承了view属性都能得到这些get和set方法。
而至于如何进行赋值的,我们来看下源码:
// 使用方法ObjectAnimator animator = ObjectAnimator.ofFloat(Object object, String property, float ....values); anim.setDuration(500);animator.start(); // 启动动画,源码分析就直接从start()开始<-- start() -->@Override public void start() { AnimationHandler handler = sAnimationHandler.get(); if (handler != null) { // 判断等待动画(Pending)中是否有和当前动画相同的动画,如果有就把相同的动画给取消掉 numAnims = handler.mPendingAnimations.size(); for (int i = numAnims - 1; i >= 0; i--) { if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) { ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i); if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) { anim.cancel(); } } } // 判断延迟动画(Delay)中是否有和当前动画相同的动画,如果有就把相同的动画给取消掉 numAnims = handler.mDelayedAnims.size(); for (int i = numAnims - 1; i >= 0; i--) { if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) { ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i); if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) { anim.cancel(); } } } } super.start(); // 调用父类的start() // 因为ObjectAnimator类继承ValueAnimator类,所以调用的是ValueAnimator的start() // 经过层层调用,最终会调用到 自动赋值给对象属性值的方法 // 下面就直接看该部分的方法} <-- 自动赋值给对象属性值的逻辑方法 ->>// 步骤1:初始化动画值private void setupValue(Object target, Keyframe kf) { if (mProperty != null) { kf.setValue(mProperty.get(target)); // 初始化时,如果属性的初始值没有提供,则调用属性的get()进行取值 } kf.setValue(mGetter.invoke(target)); } } // 步骤2:更新动画值// 当动画下一帧来时(即动画更新的时候),setAnimatedValue()都会被调用void setAnimatedValue(Object target) { if (mProperty != null) { mProperty.set(target, getAnimatedValue()); // 内部调用对象该属性的set()方法,从而从而将新的属性值设置给对象属性 } }
可以看出自动化赋值的逻辑:
初始化时,如果属性的初始值没有提供,则通过 调用属性的get()方法进行取值。当值变化时,用该对象的属性set()方法,从而将新值设置给对象属性。
总结:
ObjectAnimator针对的是任意对象/任意属性值,并不单单是针对整个view如果要采用ObjectAnimator实现某个对象某个属性的动画效果,则该属性必须实现get和set方法。
3.3、通过自定义对象属性实现动画效果 我们通过为对象的属性设置get和set方法,还有实现TypeEvaluator类定义逻辑上的变化来做一个动画。 该动画实现颜色渐变。
自定义的动画工作逻辑如下:
步骤1:设置对象属性的get和set方法
在这里,有两种可以设置get/set方法的方法:
通过继承原始类,直接给类属性加上get和set。通过包装原始动画对象,间接给对象加上该属性的get和set,即用一个类来包装原始对象。
第二种方法之后再讲,先用第一个方法实现一个 MyView2:
public class MyView2 extends View { // 设置需要用到的变量 public static final float RADIUS = 100f;// 圆的半径 = 100 private Paint mPaint;// 绘图画笔 private String color; // 设置背景颜色属性 // 设置背景颜色的get() & set()方法 public String getColor() { return color; } public void setColor(String color) { this.color = color; mPaint.setColor(Color.parseColor(color)); // 将画笔的颜色设置成方法参数传入的颜色 invalidate(); // 调用了invalidate()方法,即画笔颜色每次改变都会刷新视图,然后调用onDraw()方法重新绘制圆 // 而因为每次调用onDraw()方法时画笔的颜色都会改变,所以圆的颜色也会改变 } // 构造方法(初始化画笔) public MyView2(Context context, AttributeSet attrs) { super(context, attrs); // 初始化画笔 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.BLUE); } // 复写onDraw()从而实现绘制逻辑 @Override protected void onDraw(Canvas canvas) { canvas.drawCircle(500, 500, RADIUS, mPaint); }}
步骤2:在布局文件中加入这个view:
步骤3:根据需求实现TypeEvaluator接口: ColorEvaluator(实现颜色变化)
public class ColorEvaluator implements TypeEvaluator { // 实现TypeEvaluator接口 private int mCurrentRed; private int mCurrentGreen ; private int mCurrentBlue ; // 复写evaluate() // 在evaluate()里写入对象动画过渡的逻辑:此处是写颜色过渡的逻辑 @Override public Object evaluate(float fraction, Object startValue, Object endValue) { // 获取到颜色的初始值和结束值 String startColor = (String) startValue; String endColor = (String) endValue; // 通过字符串截取的方式将初始化颜色分为RGB三个部分,并将RGB的值转换成十进制数字 // 那么每个颜色的取值范围就是0-255 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); // 将初始化颜色的值定义为当前需要操作的颜色值 mCurrentRed = startRed; mCurrentGreen = startGreen; mCurrentBlue = startBlue; // 计算初始颜色和结束颜色之间的差值 // 该差值决定着颜色变化的快慢:初始颜色值和结束颜色值很相近,那么颜色变化就会比较缓慢;否则,变化则很快 // 具体如何根据差值来决定颜色变化快慢的逻辑写在getCurrentColor()里. 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); // getCurrentColor()决定如何根据差值来决定颜色变化的快慢 ->>关注1 } 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); // 由于我们计算出的颜色是十进制数字,所以需要转换成十六进制字符串:调用getHexString()->>关注2 // 最终将RGB颜色拼装起来,并作为最终的结果返回 return currentColor; } // 关注1:getCurrentColor() // 具体是根据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; } // 关注2:将10进制颜色值转换成16进制。 private String getHexString(int value) { String hexString = Integer.toHexString(value); if (hexString.length() == 1) { hexString = "0" + hexString; } return hexString; }}
步骤4:调用ObjectAnimator.ofObject方法:
public class MainActivity extends AppCompatActivity { MyView2 myView2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myView2 = (MyView2) findViewById(R.id.MyView2); ObjectAnimator anim = ObjectAnimator.ofObject(myView2, "color", new ColorEvaluator(), "#0000FF", "#FF0000"); // 设置自定义View对象、背景颜色属性值 & 颜色估值器 // 本质逻辑: // 步骤1:根据颜色估值器不断 改变 值 // 步骤2:调用set()设置背景颜色的属性值(实际上是通过画笔进行颜色设置) // 步骤3:调用invalidate()刷新视图,即调用onDraw()重新绘制,从而实现动画效果 anim.setDuration(8000); anim.start(); }}
3.4 特别注意:如何手动设置对象类属性的get和set
对象必须要提供属性a的set()方法(1)如果没有传递初始值,那么需要提供get方法,因为系统要去拿属性a的初始值(2)若该条件不满足,程序直接Crash对象提供的 属性a的set()方法 对 属性a的改变 必须通过某种方法反映出来(1)比如带来ui上的变化(2)若该条件不满足,动画无效,但不会Crash
上述条件中条件2容易满足,但是1有时候会出现,比如说: 由View的setWidth()并不是设置View的宽高,而是设置View的最大宽度和最小宽度,所以通过setWidth()无法改变空间的宽度,所以不能直接view的width属性做动画,没有效果。
那么这个有了set和get都不能实现动画的问题应该这么解决呢?那就是用到刚刚讲的:用一个类来包装原始对象。
解决方案:
手动设置set和get,比如textview的setWidth()不能实现直接改变宽度,那我们可以写一个 MyTextView来继承TextView然后重写set和get通过类包装
类包装的本质是Java设计模式中的装饰模式,即通过包装类从而扩展对象的功能
public class MainActivity extends AppCompatActivity { Button mButton; ViewWrapper wrapper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button) findViewById(R.id.Button); // 创建动画作用对象:此处以Button为例 wrapper = new ViewWrapper(mButton); // 创建包装类,并传入动画作用的对象 mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(3000).start(); // 设置动画的对象是包装类的对象 } }); } // 提供ViewWrapper类,用于包装View对象 // 本例:包装Button对象 private static class ViewWrapper { private View mTarget; // 构造方法:传入需要包装的对象 public ViewWrapper(View target) { mTarget = target; } // 为宽度设置get() & set() public int getWidth() { return mTarget.getLayoutParams().width; } public void setWidth(int width) { mTarget.getLayoutParams().width = width; mTarget.requestLayout(); } }}
综上,我们就能完成通过setWidth()方法对一个Button宽度进行更改。
总结:
ValueAnimator类和ObjectAnimator类都属于属性动画,本质上的流程都是:先改变值,然后赋值,最后刷新view区别在于,ValueAnimator是手动赋值间接操作属性,而ObjectAnimator自动赋值直接操作属性,后者相对更加智能和广泛。
4、额外的使用方法
4.1、组合动画 通过AnimatorSet实现组合动画的功能 具体使用:
AnimatorSet.play(Animator anim) :播放当前动画AnimatorSet.after(long delay) :将现有动画延迟x毫秒后执行AnimatorSet.with(Animator anim) :将现有动画和传入的动画同时执行AnimatorSet.after(Animator anim) :将现有动画插入到传入的动画之后执行AnimatorSet.before(Animator anim)
实例:主要实现平移,平移过程中伴随着旋转动画,平移完后进行透明度变化:
通过java代码实现:
// 步骤1:设置需要组合的动画效果ObjectAnimator translation = ObjectAnimator.ofFloat(mButton, "translationX", curTranslationX, 300,curTranslationX); // 平移动画ObjectAnimator rotate = ObjectAnimator.ofFloat(mButton, "rotation", 0f, 360f); // 旋转动画ObjectAnimator alpha = ObjectAnimator.ofFloat(mButton, "alpha", 1f, 0f, 1f); // 透明度动画// 步骤2:创建组合动画的对象AnimatorSet animSet = new AnimatorSet(); // 步骤3:根据需求组合动画animSet.play(translation).with(rotate).before(alpha); animSet.setDuration(5000); // 步骤4:启动动画animSet.start();
动画就不展示了…
XML设置:
然后在java中启动:
mButton = (Button) findViewById(R.id.Button); // 创建动画作用对象:此处以Button为例 AnimatorSet animator = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.set_animation);// 创建组合动画对象 & 加载XML动画 animator.setTarget(mButton); // 设置动画作用对象 animator.start(); // 启动动画
4.2、ViewPropertyAnimator用法 从上可以看出本质上是对属性的值进行修改。而Java是面向对象的,所以Google团队添加了面向对象操作的ViewPropertyAnimator类。 可以认为是属性动画的简写形式。 具体使用:
// 使用解析 View.animate().xxx().xxx(); // ViewPropertyAnimator的功能建立在animate()上 // 调用animate()方法返回值是一个ViewPropertyAnimator对象,之后的调用的所有方法都是通过该实例完成 // 调用该实例的各种方法来实现动画效果 // ViewPropertyAnimator所有接口方法都使用连缀语法来设计,每个方法的返回值都是它自身的实例 // 因此调用完一个方法后可直接连缀调用另一方法,即可通过一行代码就完成所有动画效果 // 以下是例子 mButton = (Button) findViewById(R.id.Button); // 创建动画作用对象:此处以Button为例 mButton.animate().alpha(0f); // 单个动画设置:将按钮变成透明状态 mButton.animate().alpha(0f).setDuration(5000).setInterpolator(new BounceInterpolator()); // 单个动画效果设置 & 参数设置 mButton.animate().alpha(0f).x(500).y(500); // 组合动画:将按钮变成透明状态再移动到(500,500)处 // 特别注意: // 动画自动启动,无需调用start()方法.因为新的接口中使用了隐式启动动画的功能,只要我们将动画定义完成后,动画就会自动启动 // 该机制对于组合动画也同样有效,只要不断地连缀新的方法,那么动画就不会立刻执行,等到所有在ViewPropertyAnimator上设置的方法都执行完毕后,动画就会自动启动 // 如果不想使用这一默认机制,也可以显式地调用start()方法来启动动画
4.3监听动画Animation类通过监听动画的开始、结束、重复、取消来进行一系列操作。 可以在Java代码里的addListener()里设置:
.addListener(new AnimatorListener() { @Override public void onAnimationStart(Animation animation) { //动画开始时执行 } @Override public void onAnimationRepeat(Animation animation) { //动画重复时执行 } @Override public void onAnimationCancel()(Animation animation) { //动画取消时执行 } @Override public void onAnimationEnd(Animation animation) { //动画结束时执行 } });// 特别注意:每次监听必须4个方法都重写。
所以可以得出Animator、ValueAnimator、ObjectAnimator、AnimatorSet有如下关系:
动画适配器 有时候我们不用监听动画的所有时刻,但是addListener必须重写那4个方法,那么为了解决代码累赘我们可以采用动画适配器,在addListener方法中传入一个AnimatorListenerAdapter。
anim.addListener(new AnimatorListenerAdapter() { // 向addListener()方法中传入适配器对象AnimatorListenerAdapter()// 由于AnimatorListenerAdapter中已经实现好每个接口// 所以这里不实现全部方法也不会报错 @Override public void onAnimationStart(Animator animation) { // 如想只想监听动画开始时刻,就只需要单独重写该方法就可以 } });
综上,关于属性动画已经总结完成!
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~