在上一次【https://www.cnblogs.com/webor2006/p/9044535.html】已经学习了补间动画的使用了,今天来学习一下属性动画,用这来实现有个官方loading【https://github.com/webor2006/AVLoadingIndicatorView】效果中的两种效果,如下:
其中这次实现的效果是它:
绘制静态圆:
先不考虑动画效果,将静态圆给绘制出来,如下:
然后在xml中应用它:
要绘制圆先要定好圆心和着么,而圆心则以控件View的中心为准,半径取控件View宽高较少值的一半既可,具体如下:
编译运行:
圆的缩放和透明度改变:
接着给圆加上动画,这里采用属性动画的ValueAnimator值变化来实现,先来实现圆的缩放,也就是此时圆的半径值需要写成活的了,如下:
而原初始化半径为5,需要通过动画来不断的来增加半径的值,下面来初始化一个动画:
应该是到View较少宽度的一半,因为这个变化指的是圆的半径,所以需要用一个变量来记录其宽高的较小值:
/** * 缩放圆效果--圆的缩放和透明度改变 */ public class BallScaleView extends View { private Paint paint; private int viewWidth, viewHeight; /* 圆的半径,缩放主要是来控制它的变化 */ private float radius; /* View宽高的较小值 */ private int length; public BallScaleView(Context context) { this(context, null); } public BallScaleView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public BallScaleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); paint.setColor(Color.WHITE); paint.setAntiAlias(true); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); viewWidth = w; viewHeight = h; length = Math.min(viewWidth, viewHeight); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(viewWidth / 2, viewHeight / 2, radius, paint); } private void prepareAnimators() { //值动画:可以处理一个值到另一个值的变化,处理圆的缩放 ValueAnimator scaleValueAnimator = ValueAnimator.ofFloat(5, length / 2); scaleValueAnimator.setDuration(2000); scaleValueAnimator.setRepeatCount(ValueAnimator.INFINITE);//设置动画重复无限次 scaleValueAnimator.start(); scaleValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { radius = (float) animation.getAnimatedValue(); invalidate(); } }); } }
然后需要调用一下动画的执行方法,这里放到获得控件宽高的回调方法最合适,如下:
编译运行:
由于截屏的原因看着不太顺畅,实际效果是没有这个问题的,另外在显示上需要做一个小优化,就是目前放大最大时紧贴控制边缘了不太好看,如下:
所以最大半径应该得往回减一点,如下:
目前只加了一个放大效果,还需加一个透明渐变的效果,同样的实现逻辑,看下面:
/** * 缩放圆效果--圆的缩放和透明度改变 */ public class BallScaleView extends View { private Paint paint; private int viewWidth, viewHeight; /* 圆的半径,缩放主要是来控制它的变化 */ private float radius; /* View宽高的较小值 */ private int length; /* 透明度 */ private int alpha; public BallScaleView(Context context) { this(context, null); } public BallScaleView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public BallScaleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); paint.setColor(Color.WHITE); paint.setAntiAlias(true); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); viewWidth = w; viewHeight = h; length = Math.min(viewWidth, viewHeight); prepareAnimators(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); paint.setAlpha(alpha); canvas.drawCircle(viewWidth / 2, viewHeight / 2, radius, paint); } private void prepareAnimators() { //值动画:可以处理一个值到另一个值的变化,处理圆的缩放 ValueAnimator scaleValueAnimator = ValueAnimator.ofFloat(5, (length / 2) - 5); scaleValueAnimator.setDuration(2000); scaleValueAnimator.setRepeatCount(ValueAnimator.INFINITE);//设置动画重复无限次 scaleValueAnimator.start(); scaleValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { radius = (float) animation.getAnimatedValue(); invalidate(); } }); //控制圓的透明度 ValueAnimator alphaValueAnimator = ValueAnimator.ofInt(255, 0); alphaValueAnimator.setDuration(2000); alphaValueAnimator.setRepeatCount(ValueAnimator.INFINITE); alphaValueAnimator.start(); alphaValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { alpha = (int) animation.getAnimatedValue(); invalidate(); } }); } }
编译运行:
多个圆的缩放渐变处理:
单个圆的效果实现了,接下来效果就得进一步,来实现它了:
很明显是由多个圆组成的,那倒底是几个圆了貌似用肉眼无法得知,这里不卖关子了,实际是由3个圆按一定的时间顺序进行相同动作的执行来达到此效果的,所以绘制需要放到循环当中,如下:
那想一想如何来实现多个圆的放大效果呢?其实思路也不难,绘制需要的alpha、radius两个信息应该针对不同的圆进行相应的设置,另外三个圆动画执行是有一个先后顺序的,所以这里先定义一个三个元素的数组用来存放动画执行的延时时间,如下:
然后一个圆对应一个动画,所以也需要放到循环当中:
然后需要给动画加一个延时,如下:
接着需要像延时一样,将radius初始化的半径也定义到数组当中,如下:
/** * 缩放圆效果--多个圆的缩放渐变处理 */ public class BallScaleView extends View { private Paint paint; private int viewWidth, viewHeight; /* 圆的半径,缩放主要是来控制它的变化,每个圆的半径都从5开始 */ private static final float[] RADIUS = new float[]{5, 5, 5}; /* View宽高的较小值 */ private int length; /* 透明度 */ private int alpha; /* 通过延迟来执行绽放来达到多个圆的缩放渐变效果 */ private long[] DELAYS = new long[]{0, 200, 400}; public BallScaleView(Context context) { this(context, null); } public BallScaleView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public BallScaleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); paint.setColor(Color.WHITE); paint.setAntiAlias(true); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); viewWidth = w; viewHeight = h; length = Math.min(viewWidth, viewHeight); prepareAnimators(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); for (int i = 0; i < 3; i++) { paint.setAlpha(alpha); canvas.drawCircle(viewWidth / 2, viewHeight / 2, RADIUS[i], paint); } } private void prepareAnimators() { for (int i = 0; i < 3; i++) { //值动画:可以处理一个值到另一个值的变化,处理圆的缩放 ValueAnimator scaleValueAnimator = ValueAnimator.ofFloat(5, (length / 2) - 5); scaleValueAnimator.setDuration(2000); scaleValueAnimator.setRepeatCount(ValueAnimator.INFINITE);//设置动画重复无限次 scaleValueAnimator.setStartDelay(DELAYS[i]); scaleValueAnimator.start(); final int index = i; scaleValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { RADIUS[index] = (float) animation.getAnimatedValue(); invalidate(); } }); //控制圓的透明度 ValueAnimator alphaValueAnimator = ValueAnimator.ofInt(255, 0); alphaValueAnimator.setDuration(2000); alphaValueAnimator.setRepeatCount(ValueAnimator.INFINITE); alphaValueAnimator.setStartDelay(DELAYS[i]); alphaValueAnimator.start(); alphaValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { alpha = (int) animation.getAnimatedValue(); invalidate(); } }); } } }
同样的对于alpha也需要定义到数组当中,其目的就是各个圆的属性都是单独处理的,不会因为其它圆的动画播放影响到当前圆的播入,所以:
/** * 缩放圆效果--多个圆的缩放渐变处理 */ public class BallScaleView extends View { //constants /* 圆的半径,缩放主要是来控制它的变化,每个圆的半径都从5开始 */ private static final float[] RADIUS = new float[]{5, 5, 5}; /* 透明度,每个圆的透明度都从255开始 */ private int[] ALPHAS = new int[]{255, 255, 255}; /* 通过延迟来执行绽放来达到多个圆的缩放渐变效果 */ private long[] DELAYS = new long[]{0, 200, 400}; //variables private Paint paint; private int viewWidth, viewHeight; /* View宽高的较小值 */ private int length; public BallScaleView(Context context) { this(context, null); } public BallScaleView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public BallScaleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); paint.setColor(Color.WHITE); paint.setAntiAlias(true); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); viewWidth = w; viewHeight = h; length = Math.min(viewWidth, viewHeight); prepareAnimators(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); for (int i = 0; i < 3; i++) { paint.setAlpha(ALPHAS[i]); canvas.drawCircle(viewWidth / 2, viewHeight / 2, RADIUS[i], paint); } } private void prepareAnimators() { for (int i = 0; i < 3; i++) { //值动画:可以处理一个值到另一个值的变化,处理圆的缩放 ValueAnimator scaleValueAnimator = ValueAnimator.ofFloat(5, (length / 2) - 5); scaleValueAnimator.setDuration(2000); scaleValueAnimator.setRepeatCount(ValueAnimator.INFINITE);//设置动画重复无限次 scaleValueAnimator.setStartDelay(DELAYS[i]); scaleValueAnimator.start(); final int index = i; scaleValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { RADIUS[index] = (float) animation.getAnimatedValue(); invalidate(); } }); //控制圓的透明度 ValueAnimator alphaValueAnimator = ValueAnimator.ofInt(255, 0); alphaValueAnimator.setDuration(2000); alphaValueAnimator.setRepeatCount(ValueAnimator.INFINITE); alphaValueAnimator.setStartDelay(DELAYS[i]); alphaValueAnimator.start(); alphaValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { ALPHAS[index] = (int) animation.getAnimatedValue(); invalidate(); } }); } } }
编译运行:
嗯~~效果不错,不过既然现在半径和透明度都可以单独控制,那每个圆的颜色那也可以单独控制呀,那试着给每个圆定义不同的颜色来看一下效果有没有酷炫炸呢?
编译运行:
我去!!颜色亮瞎眼~~
单个圆空心效果:
好,接下来实现这个效果:
实现起来相当之easy,只要更改画笔的样式既可,回到单个圆效果代码,修改如下:
/** * 缩放圆效果--单个圆空心效果 */ public class BallScaleView extends View { private Paint paint; private int viewWidth, viewHeight; /* View宽高的较小值 */ private int length; /* 圆的半径,缩放主要是来控制它的变化 */ private float radius; /* 透明度 */ private int alpha; public BallScaleView(Context context) { this(context, null); } public BallScaleView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public BallScaleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); paint.setColor(Color.WHITE); paint.setAntiAlias(true); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(2); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); viewWidth = w; viewHeight = h; length = Math.min(viewWidth, viewHeight); prepareAnimators(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); paint.setAlpha(alpha); canvas.drawCircle(viewWidth / 2, viewHeight / 2, radius, paint); } private void prepareAnimators() { //值动画:可以处理一个值到另一个值的变化,处理圆的缩放 ValueAnimator scaleValueAnimator = ValueAnimator.ofFloat(5, (length / 2) - 5); scaleValueAnimator.setDuration(2000); scaleValueAnimator.setRepeatCount(ValueAnimator.INFINITE); scaleValueAnimator.start(); scaleValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { radius = (float) animation.getAnimatedValue(); invalidate(); } }); //控制圓的透明度 ValueAnimator alphaValueAnimator = ValueAnimator.ofInt(255, 0); alphaValueAnimator.setDuration(2000); alphaValueAnimator.setRepeatCount(ValueAnimator.INFINITE); alphaValueAnimator.start(); alphaValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { alpha = (int) animation.getAnimatedValue(); invalidate(); } }); } }
编译运行:
多个圆空心效果:
最后再来实现多个圆的空心效果:
同样的方法,也只需要修改画笔的样式既可,回到多个圆效果代码,修改如下:
/** * 缩放圆效果--多个圆空心效果 */ public class BallScaleView extends View { //constants /* 圆的半径,缩放主要是来控制它的变化,每个圆的半径都从5开始 */ private static final float[] RADIUS = new float[]{5, 5, 5}; /* 透明度,每个圆的透明度都从255开始 */ private int[] ALPHAS = new int[]{255, 255, 255}; /* 通过延迟来执行绽放来达到多个圆的缩放渐变效果 */ private long[] DELAYS = new long[]{0, 200, 400}; /* 给不同的圆着上不同的颜色使其酷炫炸 */ private int[] COLORS = new int[]{Color.RED, Color.GREEN, Color.BLUE}; //variables private Paint paint; private int viewWidth, viewHeight; /* View宽高的较小值 */ private int length; public BallScaleView(Context context) { this(context, null); } public BallScaleView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public BallScaleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); paint.setColor(Color.WHITE); paint.setAntiAlias(true); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(2); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); viewWidth = w; viewHeight = h; length = Math.min(viewWidth, viewHeight); prepareAnimators(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); for (int i = 0; i < 3; i++) { paint.setAlpha(ALPHAS[i]); paint.setColor(COLORS[i]); canvas.drawCircle(viewWidth / 2, viewHeight / 2, RADIUS[i], paint); } } private void prepareAnimators() { for (int i = 0; i < 3; i++) { //值动画:可以处理一个值到另一个值的变化,处理圆的缩放 ValueAnimator scaleValueAnimator = ValueAnimator.ofFloat(5, (length / 2) - 5); scaleValueAnimator.setDuration(2000); scaleValueAnimator.setRepeatCount(ValueAnimator.INFINITE); scaleValueAnimator.setStartDelay(DELAYS[i]); scaleValueAnimator.start(); final int index = i; scaleValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { RADIUS[index] = (float) animation.getAnimatedValue(); invalidate(); } }); //控制圓的透明度 ValueAnimator alphaValueAnimator = ValueAnimator.ofInt(255, 0); alphaValueAnimator.setDuration(2000); alphaValueAnimator.setRepeatCount(ValueAnimator.INFINITE); alphaValueAnimator.setStartDelay(DELAYS[i]); alphaValueAnimator.start(); alphaValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { ALPHAS[index] = (int) animation.getAnimatedValue(); invalidate(); } }); } } }
編译运行:
到此效果完美实现,关于属性动画目前咱们只使用了ValueAnimator,在未来还会接触到其它的一些属性动画滴,慢慢来~