博主声明:

转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。

本文首发于此   博主:威威喵  |  博客主页:https://blog.csdn.net/smile_running

今天在手机上看到这样的一种加载效果,看起来还挺炫酷的,好几个小圆转啊转,颜色还不同,转完了之后消失了,然后内容就加载出来了。用语言描述大概就是这样了,我们直接看效果吧。

看到这个效果的时候,我的脑壳里面就在想,这个圆它要怎么转起来呢,绘制一周的圆倒是比较简单,但是要让它转起来,肯定是坐标值发生的改变,那究竟如何做出相应的改变呢,我们接下来一步一步分析和实现该效果。

首先,看到这个效果的时候,我们能做的第一步,就是把外面这 6 个小圆给绘制出来,多的不说,我们先绘制固定的 6 个小圆就好了,如何动起来再考虑。

要想绘制这 6 个静态的小圆,其实并不难。小圆的轨迹其实就是一个圆的圆弧吧,那么这个轨迹所围成的必定是一个大圆,所以呢,我们先将里面这个大圆给绘制出来。

接着考虑小圆的绘制,既然我们已经把大圆绘制出来了,肯定直到大圆的圆心和半径了,那么每一个小圆就是根据不同的角度绘制不同的位置了,那么 6 个小圆平均一下,每个之间的间隔就是 60° 的差值。好吧,之间来看下面我画的草图就一目了然了

根据上面我画的那张图,我们看到大圆一周共有 6 个小圆,我们拿一个圆为例子, 绿色小圆的圆心 P 点就是我们所要求的坐标点。因为我们的 6 个小圆对应的角度都是均分的,所以我们也就知道了 a 的角度,通过三角函数公式可以计算出 x,y 的边长,这样就得出了 P 点的坐标啦。

上面的效果就是这样计算的,下面我们来看看代码应该怎么写。

    private void drawCircles(Canvas canvas) {for (int i = 0; i < mCircleCount; i++) {mPaint.setColor(mColors[i]);double diff = mAngleDiff + mPercentAngle * i;float x = mCenterX + (float) Math.sin(diff) * mCenterRadius;float y = mCenterY - (float) Math.cos(diff) * mCenterRadius;canvas.drawCircle(x, y, mSmallCircleRadius, mPaint);}}

没错,代码就是这么简单就可以计算出 6 个小圆的 x , y 值,接着用 canvas 画出来就形成下面的效果了

接下来,要想让它旋转起来的话,需要属性动画进行配合处理每一个小圆所运动时变化的角度差量值,因为每个小圆的差量值都是一样的,这个差量值相当于变化的小圆旋转一周得到的角 a 的一个差量值,拿到这个值就可以配合三角函数公式计算出每一个小圆的 x ,y 坐标了。最后,属性动画所改变的差值,通过重新绘制,就能使小圆旋转起来了。代码如下:

    private void drawCenterCircle(Canvas canvas) {if (mValueCirclesAnimator == null) {//从 0 变到 2pi 就是每一个小圆所旋转的差量mValueCirclesAnimator = ObjectAnimator.ofFloat(0f, 2 * (float) Math.PI);mValueCirclesAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mAngleDiff = (float) animation.getAnimatedValue();invalidate();}});mValueCirclesAnimator.setRepeatCount(-1);mValueCirclesAnimator.setDuration(5000);mValueCirclesAnimator.setInterpolator(new LinearInterpolator());mValueCirclesAnimator.start();}drawCircles(canvas);}

小圆的旋转效果如下

好了,到这一步,我们的小圆就可以顺利的旋转起来了。接下来再去实现汇聚的动画效果,等小圆的旋转动画结束之后呢,每个小圆都会开始汇聚到中心点,下面我们来看一张图。

每一条粉红色的线代表着中心位置与小圆的距离,也就是大圆的半径,它们的值都是一样的。要想汇聚到中心的话,那必然要改变大圆的半径,直到半径缩为 0 ,既每一个小圆就重合了。

所以根据上面的分析,我们在旋转动画结束以后,要对大圆的半径进行缩小,并且要重新绘制一下,代码如下:

    private void startConvergeAnimator() {if (mConvergeAnimator == null) {mConvergeAnimator = ObjectAnimator.ofFloat(mCenterRadius, 0);mConvergeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mCenterRadius = (float) animation.getAnimatedValue();invalidate();}});mConvergeAnimator.setDuration(2000);mConvergeAnimator.setInterpolator(new AnticipateInterpolator(3f));mConvergeAnimator.start();}}

这个动画的执行是在旋转动画之后开始的,所以要监听旋转动画的结束事件,并且结束时,取消旋转动画,代码如下:

        mRotateAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {mRotateAnimator.cancel();//旋转动画结束后,播放汇聚动画startConvergeAnimator();}});

这样就会看到如下的效果:

来到这一步,我们就剩下最后一个圆的扩散效果,圆的扩散是从汇聚之后的那个中心点开始的,首先它是一个空心的圆,随着半径慢慢的变大,然后把要显示的内容揭开,视觉效果很强。

在这一步可能会有一点难度,因为你可能会搞混。我们的半径其实是不断的从 0 变到对角线的长度的,这毫无疑意。那么既然是空心的圆,画笔要设置为空心。这样我们就可以反过来这样想:

比如我就绘制一个空心的圆,它的大小固定是对角线的长度,在开始时设置它的画笔宽度为对角线的长度,伴随着半径不断的增大,那么画笔宽度也就随着减小,那就会是一个圆从中间扩散的效果。看下面的草图

黄色的线长度,既是我们要设置的画笔宽度,它的值应该是对角线减去半径。代码如下:

    private void drawSpreadCircle(Canvas canvas) {mPaint.setColor(mBackGround);mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeWidth(mDiagonal - mSpreadRadius);canvas.drawCircle(mCenterX, mCenterY, mDiagonal, mPaint);}

然后一个半径变化的属性动画代码

    private void startSpreadAnimator() {isSpread = true;if (mSpreadAnimator == null) {mSpreadAnimator = ObjectAnimator.ofFloat(0, mDiagonal);mSpreadAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mSpreadRadius = (float) animation.getAnimatedValue();invalidate();}});mSpreadAnimator.setDuration(5000);mSpreadAnimator.start();}}

哈哈,最后的一步,我们就实现了这样的效果了。

最后放出大招,本文效果的最终全部代码:

package nd.no.xww.qqmessagedragview;import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.LinearInterpolator;/*** @author xww* @desciption : 一个炫酷的加载动画集合效果* @date 2019/8/3* @time 17:20* 博主:威威喵* 博客:https://blog.csdn.net/smile_Running*/
public class LoaderAnimationView extends View {private Paint mPaint;private int mWidth;private int mHeight;private float mCenterRadius;private int mCenterX;private int mCenterY;private float mSmallCircleRadius;private int mCircleCount;//角度差private float mAngleDiff = 0F;//每一份圆的角度占比private double mPercentAngle;//扩散半径private float mSpreadRadius;//屏幕对角线private float mDiagonal;// 旋转动画private ValueAnimator mRotateAnimator;// 汇聚动画private ValueAnimator mConvergeAnimator;// 圆的扩散动画private ValueAnimator mSpreadAnimator;private boolean isSpread = false;private int mBackGround;//6种颜色,绘制6个不同颜色的小圆private int[] mColors = new int[]{getResources().getColor(android.R.color.holo_red_dark),getResources().getColor(android.R.color.holo_orange_dark),getResources().getColor(android.R.color.holo_blue_dark),getResources().getColor(android.R.color.holo_green_dark),getResources().getColor(android.R.color.holo_purple),getResources().getColor(android.R.color.darker_gray)};private void init() {mPaint = new Paint();mPaint.setDither(true);mPaint.setAntiAlias(true);mBackGround = Color.parseColor("#ffffff");mCircleCount = mColors.length;//计算每一个小圆对应的角度mPercentAngle = 2 * Math.PI / mCircleCount;mWidth = getResources().getDisplayMetrics().widthPixels;mHeight = getResources().getDisplayMetrics().heightPixels - mWidth / 4;mCenterRadius = mWidth / 4;mSmallCircleRadius = mCenterRadius / 8;mCenterX = mWidth / 2;mCenterY = mHeight / 2;mDiagonal = (float) Math.sqrt(Math.pow(mCenterX, 2) + Math.pow(mCenterY, 2));}public LoaderAnimationView(Context context) {this(context, null);}public LoaderAnimationView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public LoaderAnimationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}@Overrideprotected void onDraw(Canvas canvas) {//开始旋转动画startRotateAnimator();if (!isSpread) {canvas.drawColor(mBackGround);drawCircles(canvas);} else {drawSpreadCircle(canvas);}}private void startRotateAnimator() {//从 0 变到 2pi 就是每一个小圆所旋转的差量if (mRotateAnimator == null) {mRotateAnimator = ObjectAnimator.ofFloat(0, 2 * (float) Math.PI);mRotateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mAngleDiff = (float) animation.getAnimatedValue();invalidate();}});mRotateAnimator.setDuration(3000);mRotateAnimator.setInterpolator(new LinearInterpolator());mRotateAnimator.start();//监听动画结束事件mRotateAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {mRotateAnimator.cancel();//旋转动画结束后,播放汇聚动画startConvergeAnimator();}});}}private void startConvergeAnimator() {if (mConvergeAnimator == null) {mConvergeAnimator = ObjectAnimator.ofFloat(mCenterRadius, 0);mConvergeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mCenterRadius = (float) animation.getAnimatedValue();invalidate();}});mConvergeAnimator.setDuration(2000);mConvergeAnimator.setInterpolator(new AnticipateInterpolator(3f));mConvergeAnimator.start();mConvergeAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {mConvergeAnimator.cancel();//开始扩散动画startSpreadAnimator();}});}}private void startSpreadAnimator() {isSpread = true;if (mSpreadAnimator == null) {mSpreadAnimator = ObjectAnimator.ofFloat(0, mDiagonal);mSpreadAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {mSpreadRadius = (float) animation.getAnimatedValue();invalidate();}});mSpreadAnimator.setDuration(5000);mSpreadAnimator.start();}}private void drawCircles(Canvas canvas) {for (int i = 0; i < mCircleCount; i++) {mPaint.setColor(mColors[i]);double diff = mAngleDiff + mPercentAngle * i;float x = mCenterX + (float) Math.sin(diff) * mCenterRadius;float y = mCenterY - (float) Math.cos(diff) * mCenterRadius;canvas.drawCircle(x, y, mSmallCircleRadius, mPaint);}}private void drawSpreadCircle(Canvas canvas) {mPaint.setColor(mBackGround);mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeWidth(mDiagonal - mSpreadRadius);canvas.drawCircle(mCenterX, mCenterY, mDiagonal, mPaint);}}

怎么样,效果很不错吧,特地把图片换成了我的博客的头像,好喜欢这只小猫咪,一只敲可爱的小猫咪,awsl,哇,就这样吧,结束。

自定义 View 之雅虎新闻视差动画相关推荐

  1. 自定义 View 之 QQ 个人主页视差动画效果

    博主声明: 转载请在开头附加本文链接及作者信息,并标记为转载.本文由博主 威威喵 原创,请多支持与指教. 本文首发于此   博主:威威喵  |  博客主页:https://blog.csdn.net/ ...

  2. android+清除循环动画,android自定义View之(4)-一键清除动画

    android自定义View之(四)------一键清除动画 1.前言: 自己也是参考别人的一些自定义view例子,学习了一些基本的自定义view的方法.今天,我参考了一些资料,再结合自已的一些理解, ...

  3. android自定义View之(四)------一键清除动画

    1.前言: 自己也是参考别人的一些自定义view例子,学习了一些基本的自定义view的方法.今天,我参考了一些资料,再结合自已的一些理解,做了一个一键清除的动画.当年,我实现这个是用了几张图片,采用F ...

  4. Android 自定义view画带指针带动画的上半圆弧刻度盘

    之前工作中有用到环形进度条等的,为了赶进度都是在网上找到相似的效果的然后再进行修改.一直都想自己画一个,今天就和大家一起来学习刻度盘的绘制. 先看一下截图: 效果演示请看 刻度盘演示 代码下载:Cal ...

  5. Android自定义View实现打钩签到动画

    效果图 实现原理 我们看实现的动画效果,其实是分为 1. 绘制未选中状态图形(圆弧和对号) 2. 绘制选中状态圆弧的旋转的动画 3. 绘制选中状态圆弧向中心收缩铺满动画 4. 绘制选中状态对号 5. ...

  6. android绘图拖影效果,Android自定义View绘图实现拖影动画

    前几天在"Android绘图之渐隐动画"一文中通过画线实现了渐隐动画,但里面有个问题,画笔较粗(大于1)时线段之间会有裂隙,我又改进了一下.这次效果好多了. 先看效果吧: 然后我们 ...

  7. android 烟花动画效果图,Android 自定义 View 新年烟花、横幅动画

    新年了,项目中要作个动画,如下效果图: 整体要求实现:彩带乱飞,烟花冲天而起,烟花缩放,小鸡换图,小鸡飘移,横幅裁剪.展开等动画效果, 全局大量使用了属性动画来实现. 我在实现过程中,横幅的裁剪计算, ...

  8. Android 自定义View 新年烟花、横幅动画

    新年了,项目中要作个动画,如下效果图: 整体要求实现:彩带乱飞,烟花冲天而起,烟花缩放,小鸡换图,小鸡飘移,横幅裁剪.展开等动画效果, 全局大量使用了属性动画来实现. 我在实现过程中,横幅的裁剪计算, ...

  9. 这可能是第二好的自定义 View 教程之属性动画

    上期文章镇楼: 这可能是第二好的自定义 View 教程之绘制 凯哥的文章确实写的细而好呀,这不,活生生把 面试系列 先放一放,继续讲解我们的动画. 为啥是第二好? 一看就是没看 前面的文章 的.这里就 ...

  10. 有趣的自定义view —《聆雨》· 上下滑动面板

    自定义可滑动面板效果如下: "且听细雨,勿湿衣襟"  - 聆雨      一.效果要求 1)面板随手指上下滑动,要求流畅性: 2)面板下滑时,手指松开,回到原位: 3)面板上滑,距 ...

最新文章

  1. 百度AI快车道—企业深度学习实战营,推荐系统主题专场即将开课
  2. linux(ubuntu) 查看系统设备信息 命令
  3. lda 吗 样本中心化 需要_PCA 与 LDA 分析
  4. Swaks-smtp瑞士军刀(smtp邮件)
  5. 基于nginx实现缓存功能及uptream模块详细使用方法
  6. [链接]最短路径的几种算法[迪杰斯特拉算法][Floyd算法]
  7. ERROR! The server quit without updating PID file解决办法
  8. tree工具类 TreeUtils.java
  9. 吃是为了肉体,喝是为了灵魂
  10. java模板导出excel_POI导出excel模板三种方式
  11. python shell怎么调字体大小_如何更改在Python Shell字体大小
  12. 2020.11.23Junit详解
  13. 环保数采仪环境污染在线监控设备 上传监控平台
  14. 艾永亮:百果园的商业模式是什么?打造超级产品引领生鲜电商行业
  15. 中国IT工作者35岁后的发展出路调查报告
  16. 制造业必须应用RPA的十大环节
  17. 2022-2027年中国移动音乐行业市场调研及未来发展趋势预测报告
  18. 【UCOSii源码解析】任务管理
  19. Java基础练习项目【飞机大战】
  20. 网上书店平台---设计活动

热门文章

  1. Facebook反爬虫注册策略分析及养号实战
  2. 苏宁易购,淘宝网,京东商城,百万级价格数据海量抓取
  3. 阿尔山自驾游(2010年8月中旬,北京出发)
  4. 华为云 如何使用华为云提供的Ubuntu镜像源
  5. Vue传递数组对象报property path is neither an array nor a List nor a Map
  6. plotyy函数_Matlab plotyy函数的使用及问题总结
  7. matlab的零极点分布图,matlab零极点分布图
  8. c语言程序设计典型题目分析
  9. 【Unity项目优化宝典】Unity3D手游开发客户端开发经验总结
  10. Synopsys工具安装之二【SCL License】