Android粘性菊花—-粘性loadingView你所知道的一切

前沿

今天先看看我们要做的效果图。

我们需要做的就是这样的一个带有粘性的loading控件,可以看到里面有两种方式可以切换,一种是直线粘性loading另外一种是菊花形状的粘性控件。

准备知识

要做这样的一个效果我们主要需要了解以下几个方面的知识。

  • 如求两个圆的共切线
  • 贝塞尔曲线的画法

这里我将详细解释并一步一步的分享我们的LINE这总状态下是如果画出来的,其实CIRCLE这种状态无非也就是把我们的圆的初始化位置改变了,其他的没有什么变化。

原理讲解

我们先来看一下这一张图。

把我们的屏幕分成这么宽的几个部分,然后摆放我们的圆,首先是画5个静态的圆在我们的中央,然后我们的一个动态圆在两侧留出位置,那么我们的屏幕宽度。
width = 2r*2+2R*5+6*2d
绘制好了我们几个静态圆就事实更新我们的动态圆就是我们的小圆的位置就行了,这个时候就有这种效果了。

然后我们在根据它是否相交做个放大的效果。

下面就是难点了,也是这次的重点。

贝塞尔曲线

我们先给出一个定义,贝塞尔曲线其实就是给定任意个点,通过这任意个点,可以画出一条光滑的曲线。
这里我们先看一个解释。

我们有三个起始点A、B、C 现在我在AB上取一点A1在BC上取一点B1使得AA1:AB=BB1:BC链接A1B1在上面取一点D,使得A1D:A1B1=AA1:AB=BB1:BC,这样经过所有路线的点D最终绘制出来的线,叫做贝塞尔曲线(这里举得例子是二阶贝塞尔),二阶贝塞尔就需要确定两个点,最终的图就是。

那么同理四阶的贝尔曲线最后的样子就是

理解了贝塞尔曲线,那么接下来我们就要做我们的粘贴性部分了,如下图所示。

A,D,B,C这几个点就是我们要计算的切点,当我们知道O,F点的坐标和小圆和大圆的半径的时候,我们要求这几个点,所需要求的其实就是我途中标出来的OffestY,和offsetX大圆小圆同时都需要求,但是方法都相同,所以根据几何知识我们可以得到offsetY=DF×sin∠b,offsetx=DF×cos∠b,而∠b又可以通过tan∠b=OX/XF得到。所以这个时候我们的offsetY和offsetx就求出来了,我们就只需要绘制贝赛尔曲线就行了。

实际操作

我们新建一个工程,继承一个View,取名叫loadingView.

然后在类里面写一个内部类,用来记录我们的圆位置。

class Circle
{public Circle(int mRaduis, float mx, float my){super();this.mRaduis = mRaduis;this.mx = mx;this.my = my;}int mRaduis;float mx;float my;
}

然后就可以声明我们的变量。

public class LoadingView extends View{Path mPath = new Path(); //封闭空间的path
Paint mPaint = new Paint();//画笔
Circle mStaticCircles[]; //静态圆
Circle mDynamCircle;//动态圆
int mCircleSpace = 20;
private int mCirclesRadius = 20; //静态半径
private int mDynamCirlcRadius = (int) (mCirclesRadius *0.5); //动态半径
private boolean mIsAnimationRunning = false;
...}

然后我们写一个初始化的方法,取名叫init

private void init()
{int wid = getMeasuredWidth();int height = getMeasuredHeight();mStaticCircles = new Circle[5];// 求出间距mCircleSpace = (wid - mCirclesRadius * (mStaticCircles.length + 2) * 2) / ((mStaticCircles.length + 2) * 2);mDynamCircle = new Circle(mDynamCirlcRadius, mDynamCirlcRadius + mCircleSpace, height / 2);for (int i = 0; i < mStaticCircles.length; i++){// 计算的时候把 i+1mStaticCircles[i] = new Circle(mCirclesRadius, ((i + 2) * 2 - 1) * (mCirclesRadius + mCircleSpace), height / 2);}}

但是我们需要在哪里去调用我们这个方法呢?显然在构造的时候调用是不行的,那么我们就重写一下onMeasure方法。把我们的初始化赋值放在里面。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{// TODO Auto-generated method stubsuper.onMeasure(widthMeasureSpec, heightMeasureSpec);init();
}

初始化成功了那么就应该画我们的圆形了。我们同时也写一个方法来绘制我们的圆形,取名字叫drawCircle

private void drawCircle(Canvas canvas)
{for (Circle circles : mStaticCircles){canvas.drawCircle(circles.mx, circles.my, circles.mRaduis, mPaint);}if (mIsAnimationRunning)canvas.drawCircle(mDynamCircle.mx, mDynamCircle.my, mDynamCircle.mRaduis, mPaint);
}

里面实现也很简单就是绘制我们的圆。既然圆形都绘制了那么就需要绘制我们的贝塞尔曲线了。我们取名一个函数叫drawBersal用这个函数来绘制我们的贝塞尔区域让这个控件变得有粘性起来。

private void drawBersal(Canvas canvas)
{for (int i = 0; i < mStaticCircles.length; i++){// 两个圆之间的距离int length = (int) Math.sqrt((mStaticCircles[i].mx - mDynamCircle.mx) * (mStaticCircles[i].mx - mDynamCircle.mx) + (mStaticCircles[i].my - mDynamCircle.my)* (mStaticCircles[i].my - mDynamCircle.my));// 距离阀值int corssLength = mDynamCircle.mRaduis + mStaticCircles[i].mRaduis + mCircleSpace;if (length < corssLength){// 计算两个圆相切的点float offsetX = (float) (mDynamCirlcRadius * Math.sin(Math.atan((mStaticCircles[i].my - mDynamCircle.my) / (mStaticCircles[i].mx - mDynamCircle.mx))));float offsetY = (float) (mDynamCirlcRadius * Math.cos(Math.atan((mStaticCircles[i].my - mDynamCircle.my) / (mStaticCircles[i].mx - mDynamCircle.mx))));int startX = (int) (mDynamCircle.mx + offsetX);int startY = (int) (mDynamCircle.my - offsetY);int endX = (int) (mDynamCircle.mx - offsetX);int endY = (int) (mDynamCircle.my + offsetY);float offsetstaticX = (float) (mStaticCircles[i].mRaduis * Math.sin(Math.atan((mStaticCircles[i].my - mDynamCircle.my) / (mStaticCircles[i].mx - mDynamCircle.mx))));float offsetstaticY = (float) (mStaticCircles[i].mRaduis * Math.cos(Math.atan((mStaticCircles[i].my - mDynamCircle.my) / (mStaticCircles[i].mx - mDynamCircle.mx))));int startStaticX = (int) (mStaticCircles[i].mx + offsetstaticX);int startStaticY = (int) (mStaticCircles[i].my - offsetstaticY);int endStaticX = (int) (mStaticCircles[i].mx - offsetstaticX);int endStaticY = (int) (mStaticCircles[i].my + offsetstaticY);int anchorX = (int) ((mStaticCircles[i].mx + mDynamCircle.mx) / 2);int anchorY = (int) ((mStaticCircles[i].my + mDynamCircle.my) / 2);mPath.reset();mPath.moveTo(startX, startY);mPath.quadTo(anchorX, anchorY, startStaticX, startStaticY);mPath.lineTo(endStaticX, endStaticY);mPath.quadTo(anchorX, anchorY, endX, endY);mPath.lineTo(startX, startY);canvas.drawPath(mPath, mPaint);return;}}

我们看到我们其实是绘制了两条贝塞尔曲线通过path去封闭,贝塞尔曲线都共用了一个点那就是我取的两个圆的中点,这里我简单说一下贝塞尔封闭区域的绘制过程,还是使用上面讲解的那个图,我们先把原点移动到了A,然后确定D点和两个圆心中点,有了封闭图形的半边,然后调用LineTo绘制直线到C点同样的再一次调用贝塞尔函数绘制到B点,最后LinTo绘制到A点封口,通过path绘制封闭的区域,这个时候我们的贝塞尔封闭区域就画好了,但是请注意这里我写了判断是两个圆圆心的距离小于某个值才去做粘性的这个绘制。

既然绘制都写好了那么就应该上屏了,重写我们的onDraw方法。

@Override
protected void onDraw(Canvas canvas)
{super.onDraw(canvas);drawCircle(canvas);drawBersal(canvas);
}

最后一步就是让我们的loading动起来,这里我们直接使用一个ValueAnimator来计算我们的值就好了。我们对外写一个startAnimation方法。

ValueAnimator animator = null;
public void startAnimation()
{if (animator != null){animator.removeAllListeners();animator.cancel();animator = null;init();}animator = ValueAnimator.ofInt((int) mDynamCircle.mx, (mCircleSpace + mCirclesRadius) * (2 * (mStaticCircles.length + 2) - 1));animator.setInterpolator(new AccelerateDecelerateInterpolator());animator.addUpdateListener(new AnimatorUpdateListener(){@Overridepublic void onAnimationUpdate(ValueAnimator animation){int vaule = (int) animation.getAnimatedValue();mDynamCircle.mx = vaule;caclScale();postInvalidate();}});animator.addListener(new AnimatorListener(){@Overridepublic void onAnimationStart(Animator paramAnimator){mIsAnimationRunning = true;}@Overridepublic void onAnimationRepeat(Animator paramAnimator){}@Overridepublic void onAnimationEnd(Animator paramAnimator){mIsAnimationRunning = false;}@Overridepublic void onAnimationCancel(Animator paramAnimator){mIsAnimationRunning = false;}});animator.setRepeatMode(Animation.REVERSE);animator.setRepeatCount(Animation.INFINITE);animator.setDuration(3000);animator.start();
}

这样通过属性动画直接帮我们计算我们的值,我们直接去刷新界面就行了,但是注意到我有个caclScale的方法这个方法是来判断当前是否需要缩放。

 private void caclScale()
{
for (int i = 0; i < mStaticCircles.length; i++){// 两个圆之间的距离int length = (int) Math.sqrt((mStaticCircles[i].mx - mDynamCircle.mx) * (mStaticCircles[i].mx - mDynamCircle.mx) + (mStaticCircles[i].my - mDynamCircle.my)* (mStaticCircles[i].my - mDynamCircle.my));// 交叉的距离int corssLength = mDynamCircle.mRaduis + mStaticCircles[i].mRaduis + mCircleSpace / 2;if (length < corssLength){// 已经开始交叉float scale = 1.0f * length / corssLength;// mDynamCircle.mRaduis = (int) (mRadius/2f * (1 + scale));mStaticCircles[i].mRaduis = (int) (mCirclesRadius * (1 + mDynamCircle.mRaduis * 1f / mCirclesRadius * (1 - scale)));//              return;}else{mStaticCircles[i].mRaduis = mCirclesRadius;mDynamCircle.mRaduis = mDynamCirlcRadius;}}
}

到这里基本上LINE这种状态的loading也写完了。相信大家也应该理解得差不多了。

CIRCLE类型

这里我就不重复招轮子了,懂了原理的人,应该很好理解第二种是怎么实现的,固定圆的位置更新动态圆的位置都是同一套理论,同时使用这个理论还是实现很多效果,这里就不论述了,大家有空多尝试吧。
这里给出代码的下载位置:传送门

Android粘性菊花—-粘性LoadingView你所知道的一切相关推荐

  1. android+水滴粘性动画,Android水滴,小球粘性控件生成.

    前几天学了Python相关的知识,然后昨天看了一下Skype的Loading非常好看,就想要自己做一个看看.然后网上搜集了一些资料. 需要用bezier去画圆.画了圆后慢慢的拓展右边的点的位置逐渐形成 ...

  2. android 开源 高斯模糊_Android高斯模糊你所不知道的坑

    原标题:Android高斯模糊你所不知道的坑 本文作者 作者:mandypig 链接: https://www.jianshu.com/p/d29841b1a4d5 本文由作者授权发布. 如果你想了解 ...

  3. Android Context完全解析,你所不知道的Context的各种细节

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/47028975 前几篇文章,我也是费劲心思写了一个ListView系列的三部曲,虽然 ...

  4. Android实现菊花loading动画

    在一些网络请求中,用户操作中,我们往往需要一些耗时等待的动画,一开始本来用一个比较酷炫的三方加载动画,后来因为嫌弃太丑,不得不切换使用原始的菊花加载动画,可谁知UI给出一系列的菊花动画图片, 虽然动画 ...

  5. android ontouchevent 坐标,onTouchEvent(一) 你所必须知道的坐标详解

    前言 本篇文章是一位读者的学习笔记,我很喜欢这样的文章,知识点写在我的书上并不能让你提高,转化为你自己的知识,才是提高的唯一途径,所以这次我破例在公众号发布这篇不是我写的文章,同时也希望Suma能够继 ...

  6. android如何改变微信ui,你所不知的微信秘籍!微信UI变身安卓风

    微信可谓是安卓上装机量最高的App之一了,但讽刺的是,这并非是一个典型的安卓App--它并没有使用安卓风格的设计.安卓上的微信,界面和iOS上的微信几乎如出一辙,这使得安卓版微信和原生安卓界面契合度很 ...

  7. android 自定义顶部,Android自定义实现顶部粘性下拉刷新效果

    本文实例为大家分享了Android实现顶部粘性下拉刷新效果的具体代码,供大家参考,具体内容如下 activity_view_mv代码 xmlns:android="http://schema ...

  8. Android复习系列③之《Android筑基》

    1.Android系统架构 应用层 应用框架层(Framwork) 系统运行库层 Linux内核层 2.四大组件 1. Activity 1.1 生命周期 下面这张图一定要仔细看看,并能理解每一个步骤 ...

  9. android系统休眠发广播,Android - BroadcastReceiver

    BroadcastReceiver BroadcastReceiver,广播接收者,用来接收系统和应用的广播,并做出相应的处理,如电量过低时提示用户充电等: BroadcastReceiver 是 A ...

最新文章

  1. C++ 中multiset 的使用
  2. HardwareSoftwareTutorial
  3. ionic4 手机启动页进入首页慢问题修改
  4. 温习下C语言一些函数
  5. 左右伸缩_OPPO概念机将至!横向卷轴+左右伸缩,你期待吗
  6. HDU1976 Software Version【水题】
  7. 收藏啦~ Github上 10 个开源免费且优秀的后台控制面板
  8. GDI+中的图片处理类Image或Bitmap
  9. iOS:childViewController和view的声明周期及其原理
  10. 资源 | NJUPT-Yellow-Page 南邮黄页
  11. 员工管理系统-SpringBoot+Vue入门小项目实战
  12. 误差平方和用python_用Python学分析 - 单因素方差分析
  13. 今天我们来聊一个很高级的话题:如何设计一个大规模远程命令执行系统
  14. 为什么除法,开方,求对数比乘法,乘方,求指数更难
  15. html选择器的定义和使用,CSS选择器用法大全
  16. python单核运行_python下多核,单核CPU对于并行,并发执行效率的对比-Go语言中文社区...
  17. python如何爬虫股票数据_python爬虫实例,股票数据定向爬虫
  18. OKR 年度规划实践:如何在 2022 年做好准备
  19. ASCII字符代码表
  20. 【Docker】win7安装docker及镜像加速

热门文章

  1. vue图片宽高自适应_div或img图片高度随宽度自适应的方法
  2. 【BZOJ2440】【中山市选2011】—完全平方数(莫比乌斯函数+容斥原理)
  3. java调用corba_用JACORB开发corba应用
  4. 【代码复现】MIDIE
  5. excel数据输入窗体控件_工作表数据输入或Excel用户窗体
  6. 华为计算机不好用,华为电脑的指纹不好使怎么办
  7. Vim编辑器与Shell命令脚本
  8. 在visio中寻找变压器的双箭头
  9. 第五人格服务器维修到几点,第五人格4月4日停机维护时间_第五人格4月4日停机维护到几点_玩游戏网...
  10. android 6.0 小米note,小米Note用户有福了 安卓6.0全面来袭