原文发自:https://www.jianshu.com/p/e3634458ddbe

前言

没有前言,来看看网页版动态背景「五彩蛛网」是怎么实现的!

先上效果图:

初步分析

在效果图中,可以看到许多「小点」在屏幕中匀速运动并与「邻近的点」相连,每条连线的颜色随机,「小点」触碰到屏幕边缘则回弹;还有一个效果就是,手指在屏幕中移动、拖拽,与手指触摸点连线的点向触摸点靠拢。何为「邻近的点」,与某点的距离小于特定的阈值的点称为「邻近的点」。

提到运动,「运动」在物理学中指物体在空间中的相对位置随着时间而变化。

那么大家还记得「位移」与「速度」公式吗?

位移 = 初位移 + 速度 * 时间
速度 = 初速度 + 加速度

时间、位移、速度、加速度构成了现代科学的运动体系。我们使用 view 来模拟物体的运动。

  • 时间:在 view 的 onDraw 方法中调用 invalidate 方法,达到无限刷新来模拟时间流,每次刷新间隔,记为:1U

  • 位移:物体在屏幕中的像素位置,每个像素距离为:1px

  • 速度:默认设置一个值,单位(px / U)

  • 加速度:默认设置一个值,单位(px / U^2)

模拟「蛛网点」物体类:

public class SpiderPoint extends Point {    // x 方向加速度public int aX;    // y 方向加速度public int aY;    // 小球颜色public int color;    // 小球半径public int r;    // x 轴方向速度public float vX;    // y 轴方向速度public float vY;    // 点public float x;    public float y;    public SpiderPoint(int x, int y) {        super(x, y);}
}

蛛网点匀速直线运动

搭建测试 View,初始位置 (0,0) ,x 方向速度 10、y 方向速度 0 的蛛网点:

public class MoveView extends View {    // 画笔private Paint mPointPaint;    // 蛛网点对象(类似小球)private SpiderPoint mSpiderPoint;    // 坐标系private Point mCoordinate;    // 蛛网点 默认小球半径private int pointRadius = 20;    // 默认颜色private int pointColor = Color.RED;    // 默认x方向速度private float pointVX = 10;    // 默认y方向速度private float pointVY = 0;    // 默认 小球加速度private int pointAX = 0;    private int pointAY = 0;    // 是否开始运动private boolean startMove = false;    public MoveView(Context context) {        this(context, null);}    public MoveView(Context context, @Nullable AttributeSet attrs) {        this(context, attrs, 0);}    public MoveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);initData();initPaint();}    private void initData() {mCoordinate = new Point(500, 500);mSpiderPoint = new SpiderPoint();mSpiderPoint.color = pointColor;mSpiderPoint.vX = pointVX;mSpiderPoint.vY = pointVY;mSpiderPoint.aX = pointAX;mSpiderPoint.aY = pointAY;mSpiderPoint.r = pointRadius;}    // 初始化画笔private void initPaint() {mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mPointPaint.setColor(pointColor);}    @Overrideprotected void onDraw(Canvas canvas) {        super.onDraw(canvas);canvas.save();canvas.translate(mCoordinate.x, mCoordinate.y);drawSpiderPoint(canvas, mSpiderPoint);canvas.restore();        // 刷新视图 再次调用onDraw方法模拟时间流if (startMove) {updateBall();invalidate();}}    /*** 绘制蛛网点** @param canvas* @param spiderPoint*/private void drawSpiderPoint(Canvas canvas, SpiderPoint spiderPoint) {mPointPaint.setColor(spiderPoint.color);canvas.drawCircle(spiderPoint.x, spiderPoint.y, spiderPoint.r, mPointPaint);}    /*** 更新小球*/private void updateBall() {        //TODO --运动数据都由此函数变换}    @Overridepublic boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                // 开启时间流startMove = true;invalidate();                break;            case MotionEvent.ACTION_UP:                // 暂停时间流startMove = false;invalidate();                break;}        return true;}
}

1、水平运行运动:

根据上文中的位移公式,位移 = 初位移 + 速度 * 时间 ,这里的时间为 1U,更新小球位置的相关代码如下:

    /*** 更新小球*/private void updateBall() {        //TODO --运动数据都由此函数变换mSpiderPoint.x += mSpiderPoint.vX;}

2、回弹效果

回弹,速度取反,x 轴方向大于 400 则回弹:

3、无限回弹,回弹变色

相关代码如下:

    /*** 更新小球*/private void updateBall() {        //TODO --运动数据都由此函数变换mSpiderPoint.x += mSpiderPoint.vX;        if (mSpiderPoint.x > 400) {            // 更改颜色mSpiderPoint.color = randomRGB();mSpiderPoint.vX = -mSpiderPoint.vX;}        if (mSpiderPoint.x < -400) {mSpiderPoint.vX = -mSpiderPoint.vX;            // 更改颜色mSpiderPoint.color = randomRGB();}}

randomRGB 方法的代码如下:

    /*** @return 获取到随机颜色值*/private int randomRGB() {Random random = new Random();        return Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255));}

3、箱式弹跳

小球在 y 轴方向的平移与 x 轴方向的平移一致,这里不再讲解,看一下 x ,y 轴同时具有初速度,即速度斜向的情况。

改变 y 轴方向初速度:

    // 默认y方向速度private float pointVY = 6;

在 updateBall 方法中增加对 y 方向的修改:

    /*** 更新小球*/private void updateBall() {        //TODO --运动数据都由此函数变换mSpiderPoint.x += mSpiderPoint.vX;mSpiderPoint.y += mSpiderPoint.vY;        if (mSpiderPoint.x > 400) {            // 更改颜色mSpiderPoint.color = randomRGB();mSpiderPoint.vX = -mSpiderPoint.vX;}        if (mSpiderPoint.x < -400) {mSpiderPoint.vX = -mSpiderPoint.vX;            // 更改颜色mSpiderPoint.color = randomRGB();}        if (mSpiderPoint.y > 400) {            // 更改颜色mSpiderPoint.color = randomRGB();mSpiderPoint.vY = -mSpiderPoint.vY;}        if (mSpiderPoint.y < -400) {mSpiderPoint.vY = -mSpiderPoint.vY;            // 更改颜色mSpiderPoint.color = randomRGB();}}

效果如下图:

蛛网「小点」并没有涉及到变速运动,有关变速运动可以链接以下地址进行查阅:

Android原生绘图之让你了解View的运动

构思代码

通过观察网页「蛛网」动态效果,可以细分为以下几点:

  • 绘制一定数量的小球(蛛网点)

  • 小球斜向运动(具有 x,y 轴方向速度),越界回弹

  • 遍历所有小球,若小球 A 与其他小球的距离小于一定值,则两小球连线,反之则不连线

  • 若小球 A 先与小球 B 连线,为了提高性能,防止过度绘制,小球 B 不再与小球 A 连线

  • 在手指触摸点绘制小球,同连线规则一致,连线其他小球,若手指移动,连线的所有小球向触摸点靠拢

接下来,具体看看代码该怎么写。

编写代码

起名字

取名是一门学问,好的名字能够让你记忆犹新,那就叫 SpiderWebView  (蛛网控件)。

创建SpiderWebView

先是成员变量:

    // 控件宽高private int mWidth;    private int mHeight;    // 画笔private Paint mPointPaint;    private Paint mLinePaint;    private Paint mTouchPaint;    // 触摸点坐标private float mTouchX = -1;    private float mTouchY = -1;    // 数据源private List<SpiderPoint> mSpiderPointList;    // 相关参数配置private SpiderConfig mConfig;    // 随机数private Random mRandom;    // 手势帮助类 用于处理滚动与拖拽private GestureDetector mGestureDetector;

然后是构造函数:

    // view 的默认构造函数 参数不做讲解public SpiderWebView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        // setLayerType(LAYER_TYPE_HARDWARE, null);mSpiderPointList = new ArrayList<>();mConfig = new SpiderConfig();mRandom = new Random();mGestureDetector = new GestureDetector(context, mSimpleOnGestureListener);        // 画笔初始化initPaint();}

接着按着「构思代码」中的效果逐一实现。

绘制一定数量的小球

指定数量为 50,每个小球的位置、颜色随机,并且具有不同的加速度。相关代码如下:

    @Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);mWidth = w;mHeight = h;}

先获取控件到控件的宽高。然后初始化小球集合:

    /*** 初始化小点*/private void initPoint() {        for (int i = 0; i < mConfig.pointNum; i++) {            int width = (int) (mRandom.nextFloat() * mWidth);            int height = (int) (mRandom.nextFloat() * mHeight);SpiderPoint point = new SpiderPoint(width, height);            int aX = 0;            int aY = 0;            // 获取加速度while (aX == 0) {aX = (int) ((mRandom.nextFloat() - 0.5F) * mConfig.pointAcceleration);}            while (aY == 0) {aY = (int) ((mRandom.nextFloat() - 0.5F) * mConfig.pointAcceleration);}point.aX = aX;point.aY = aY;            // 颜色随机point.color = randomRGB();mSpiderPointList.add(point);}}

mConfig 表示配置参数,具体有以下成员变量:

public class SpiderConfig {    // 小点半径 1public int pointRadius = DEFAULT_POINT_RADIUS;    // 小点之间连线的粗细(宽度) 2public int lineWidth = DEFAULT_LINE_WIDTH;    // 小点之间连线的透明度 150public int lineAlpha = DEFAULT_LINE_ALPHA;    // 小点数量 50public int pointNum = DEFAULT_POINT_NUMBER;    // 小点加速度 7public int pointAcceleration = DEFAULT_POINT_ACCELERATION;    // 小点之间最长直线距离 280public int maxDistance = DEFAULT_MAX_DISTANCE;    // 触摸点半径 1public int touchPointRadius = DEFAULT_TOUCH_POINT_RADIUS;    // 引力大小 50public int gravitation_strength = DEFAULT_GRAVITATION_STRENGTH;
}

获取到小球集合,最后绘制小球:

    @Overrideprotected void onDraw(Canvas canvas) {        super.onDraw(canvas);        // 绘制小球mPointPaint.setColor(spiderPoint.color);canvas.drawCircle(spiderPoint.x, spiderPoint.y, mConfig.pointRadius, mPointPaint);}

效果图如下:

小球斜向运动,越界回弹

根据位移与速度公式 位移 = 初位移 + 速度 * 时间速度 = 初速度 + 加速度 ,由于初速度为 0 ,时间为 1U,得到 位移 = 初位移 + 加速度

    spiderPoint.x += spiderPoint.aX;spiderPoint.y += spiderPoint.aY;

判定越界,原理在上文中已经提到:

    @Overrideprotected void onDraw(Canvas canvas) {        super.onDraw(canvas);        for (SpiderPoint spiderPoint : mSpiderPointList) {spiderPoint.x += spiderPoint.aX;spiderPoint.y += spiderPoint.aY;            // 越界反弹if (spiderPoint.x <= mConfig.pointRadius) {spiderPoint.x = mConfig.pointRadius;spiderPoint.aX = -spiderPoint.aX;} else if (spiderPoint.x >= (mWidth - mConfig.pointRadius)) {spiderPoint.x = (mWidth - mConfig.pointRadius);spiderPoint.aX = -spiderPoint.aX;}            if (spiderPoint.y <= mConfig.pointRadius) {spiderPoint.y = mConfig.pointRadius;spiderPoint.aY = -spiderPoint.aY;} else if (spiderPoint.y >= (mHeight - mConfig.pointRadius)) {spiderPoint.y = (mHeight - mConfig.pointRadius);spiderPoint.aY = -spiderPoint.aY;}}}

效果图如下:

两球连线

循环遍历所有小球,若小球 A 与其他小球的距离小于一定值,则两小球连线,反之则不连线。双层遍历会导致一个问题,如果小球数量过多,双层遍历效率极低,从而引起界面卡顿,目前并没有找到更好的算法来解决这个问题,为了防止卡顿,对小球的数量有所控制,不能超过 150 个。

    @Overrideprotected void onDraw(Canvas canvas) {        super.onDraw(canvas);        for (SpiderPoint spiderPoint : mSpiderPointList) {            // 绘制连线for (int i = 0; i < mSpiderPointList.size(); i++) {SpiderPoint point = mSpiderPointList.get(i);                // 判定当前点与其他点之间的距离if (spiderPoint != point) {                    int distance = disPos2d(point.x, point.y, spiderPoint.x, spiderPoint.y);                    if (distance < mConfig.maxDistance) {                        // 绘制小点间的连线int alpha = (int) ((1.0F - (float) distance / mConfig.maxDistance) * mConfig.lineAlpha);mLinePaint.setColor(point.color);mLinePaint.setAlpha(alpha);canvas.drawLine(spiderPoint.x, spiderPoint.y, point.x, point.y, mLinePaint);}}}}invalidate();}

disPos2d 方法用于计算两点之间的距离:

    /*** 两点间距离函数*/public static int disPos2d(float x1, float y1, float x2, float y2) {        return (int) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));}

如果两小球的距离在 maxDistance 范围内,距离越近透明度越小:

    int alpha = (int) ((1.0F - (float) distance / mConfig.maxDistance) * mConfig.lineAlpha);

一起来看看两球连线的效果:

防止过度绘制

由于双层遍历,若小球 A 先与小球 B 连线,为了提高性能,防止过度绘制,小球 B 不再与小球 A 连线。最开始的想法是记录小球 A 与其他小球的连线状态,当其他小球与小球 A 连线时,根据状态判定是否连线,如果小球 A 先与许多小球连线,必然会在小球 A 对象内部维护一个集合,用于存储小球 A 已经与哪些小球连线,这样效率并不高,反而把简单的问题变复杂了。最后用了一个取巧的办法:记录第一次循环的索引值,第二次循环从当前的索引值开始,这样就避免了两小球之间的多次连线。相关代码如下:

    @Overrideprotected void onDraw(Canvas canvas) {        super.onDraw(canvas);        int index = 0;        for (SpiderPoint spiderPoint : mSpiderPointList) {            // 绘制连线for (int i = index; i < mSpiderPointList.size(); i++) {SpiderPoint point = mSpiderPointList.get(i);                // 判定当前点与其他点之间的距离if (spiderPoint != point) {                    int distance = disPos2d(point.x, point.y, spiderPoint.x, spiderPoint.y);                    if (distance < mConfig.maxDistance) {                        // 绘制小点间的连线int alpha = (int) ((1.0F - (float) distance / mConfig.maxDistance) * mConfig.lineAlpha);mLinePaint.setColor(point.color);mLinePaint.setAlpha(alpha);canvas.drawLine(spiderPoint.x, spiderPoint.y, point.x, point.y, mLinePaint);}}}index++;}invalidate();}

手势处理

还记得吗?在文章 第一站小红书图片裁剪控件,深度解析大厂炫酷控件 已经讲解了手势的处理流程。在网页版中触摸点(鼠标按下点)跟随鼠标移动而移动,在手机屏幕中「触摸点」(手指按下点)跟随手指移动而移动,从而需要重写手势类的 onScroll 方法:

        @Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {            // 单根手指操作if (e1.getPointerCount() == e2.getPointerCount() && e1.getPointerCount() == 1) {mTouchX = e2.getX();mTouchY = e2.getY();                return true;}            return super.onScroll(e1, e2, distanceX, distanceY);}

onFling 方法与 onScroll 方法处理方式一致,实时获取到「触摸点」位置。获取到了位置,绘制触摸点:

    @Overrideprotected void onDraw(Canvas canvas) {        super.onDraw(canvas);        // 绘制触摸点if (mTouchY != -1 && mTouchX != -1) {canvas.drawPoint(mTouchX, mTouchY, mTouchPaint);}}

若「触摸点」与其他小球的距离小于一定值,则两小球连线,反之则不连线:

    @Overrideprotected void onDraw(Canvas canvas) {        super.onDraw(canvas);            // 绘制触摸点与其他点的连线if (mTouchX != -1 && mTouchY != -1) {                int offsetX = (int) (mTouchX - spiderPoint.x);                int offsetY = (int) (mTouchY - spiderPoint.y);                int distance = (int) Math.sqrt(offsetX * offsetX + offsetY * offsetY);                if (distance < mConfig.maxDistance) {                    int alpha = (int) ((1.0F - (float) distance / mConfig.maxDistance) * mConfig.lineAlpha);mLinePaint.setColor(spiderPoint.color);mLinePaint.setAlpha(alpha);canvas.drawLine(spiderPoint.x, spiderPoint.y, mTouchX, mTouchY, mLinePaint);}}}

同时还具有与「触摸点」连线的所有小球向「触摸点」靠拢的效果,可采用「位移相对减少」的方案来实现靠拢的效果,相关代码如下:

    @Overrideprotected void onDraw(Canvas canvas) {        super.onDraw(canvas);            // 绘制触摸点与其他点的连线if (mTouchX != -1 && mTouchY != -1) {....... // 省略相关代码          if (distance < mConfig.maxDistance) {                    if (distance >= (mConfig.maxDistance - mConfig.gravitation_strength)) {                        // x 轴方向位移减少if (spiderPoint.x > mTouchX) {spiderPoint.x -= 0.03F * -offsetX;} else {spiderPoint.x += 0.03F * offsetX;}                        // y 轴方向位移减少if (spiderPoint.y > mTouchY) {spiderPoint.y -= 0.03F * -offsetY;} else {spiderPoint.y += 0.03F * offsetY;}} ....... // 省略相关代码

看看效果图:

「五彩蛛网」控件差不多就讲到这里,有什么疑问,请留言讨论?

献上源码;

https://github.com/HpWens/MeiWidgetView

https://github.com/HpWens/SpiderWebView

BAT主流Android架构技术大纲+全套视频

架构技术详解和学习路线与资料分享请看这篇《BATJ一线大厂最主流的Android高级架构技术;体系详解+学习路线》

(包括自定义控件、NDK、架构设计、混合式开发工程师(React native,Weex)、性能优化、完整商业项目开发等)

  • 阿里P8级Android架构师技术脑图;

  • 全套体系化高级架构视频;七大主流技术模块,视频+源码+笔记

转载于:https://blog.51cto.com/14048760/2386879

Android 实现动态背景“五彩蛛网”特效,让你大开眼界!相关推荐

  1. android sony 动态背景,安卓福利:精选索尼手机原生壁纸 每一张都有索尼的信仰加成!...

    原标题:安卓福利:精选索尼手机原生壁纸 每一张都有索尼的信仰加成! 好看的皮囊千篇一律,有趣的灵魂万里挑一,欢迎大家来到赛先生福瑞的壁纸推荐栏目(●'◡'●) 自从全面屏流行以来,从前人们老提到的手机 ...

  2. Android毛玻璃(磨砂)效果(静态&动态背景图模糊 收集)

    果然前人栽树,后人乘凉. google搜索"Android实现动态高斯模糊效果"发现2016年转载的博客居多(或者试试英文搜索,可能时间会提前很多.),看来2016年想实现这种效果 ...

  3. html怎样实现动态背景效果,利用jQuery实现动态背景特效

    特效描述:利用jQuery实现 动态背景特效.利用jQuery实现动态背景特效 代码结构 1. 引入CSS 2. 引入JS 3. HTML代码 Example 1 ooh, pretty. Notic ...

  4. 网页动态背景——随鼠标变换的动态线条(鼠标蜘蛛网特效)

    网页动态背景--随鼠标变换的动态线条 效果图如下 代码如下: <!DOCTYPE HTML> <html> <head> <meta http-equiv=& ...

  5. android 背景磨砂效果,跨浏览器磨砂效果背景图片模糊特效

    background-blur是一款非常炫酷的跨浏览器磨砂效果背景图片模糊特效jQuery插件.它会抽取图片的主要色彩,并通过SVG过滤器来制作模糊效果.并且它还通过velocity.js来提供额外的 ...

  6. 几种炫酷的canvas动态背景特效

    canvas光斑闪烁动态背景特效 立即下载       在线预览 canvas科技感粒子特效,html5科幻粒子动画代码,js鼠标跟随. 立即下载       在线预览 htm5圆圈线条连接动画网页背 ...

  7. 关闭CSDN网页端动态背景特效

    关闭CSDN网页端动态背景特效 许多博客都采用了动态背景特效,虽然也许有些好看,但对于我的老式笔记本来说是一个很大的负担.每次打开带有特效的博客,cpu占用率立马飚到70%,风扇直接3000+,都不能 ...

  8. ZYI官网单页html引导页源码动态背景特效

    介绍: 一款动态背景特效的html单页引导页面源码,喜欢的可以下载看看. 网盘下载地址: http://kekewl.net/Eh0aZj8srbR 图片:

  9. ZYI官网单页html引导页源码 动态背景特效

    介绍: 一款动态背景特效的html单页引导页面源码,喜欢的可以下载看看. 网盘下载地址: http://kekewl.org/jv0hXTu0cMP 图片:

  10. 原创安卓手机QQ7.0登录界面动态背景视频实现方案

    qq7.0登录界面动态背景实现 qq7.0登录界面动态视频背景实现 android动态视频背景 android动态背景 分析qq7.0: 视频在打开登录界面就开始播放 了,而且期间无黑屏 而且是循环播 ...

最新文章

  1. 编写递归下降语法分析器_面试BAT必问的JVM,今天我们来说一说它类加载器的底层原理...
  2. 经典C语言程序100例之九三
  3. wsdl文档中的soap:address的生成规则_BAT大牛都在使用的数据库文档生成插件,不来看一下?...
  4. matlab修改图片位深度_如何利用matlab统一处理照片亮度对比度
  5. itext 7 设置页面大小_indesign页面设置技巧教程【indesign页面大小设置教程】
  6. 谱图理论-拉普拉斯矩阵理解
  7. 6-2 视频分解图片
  8. 2011年11月份第二周51Aspx源码发布详情
  9. 戴尔服务器r720u盘装系统,DELL R720服务器U盘安装操作系统指南
  10. 键盘拆开重新安装步骤_笔记本键盘按键安装拆卸详解
  11. dcdc芯片效率不高的原因_影响DC-DC转换器效率的主要因素
  12. 十二进制加计数器-20151112
  13. Vue + ElementUI 实现一个动态添加元素的小例子
  14. 07 给Form视图添加Chatter(学Odoo,就得Do)
  15. EDK2源码下载及环境搭建
  16. matlab无理数转分数,把无理数转化成分数值的方法
  17. linux无法识别m2固态,主板识别不出m.2固态硬盘怎么办|笔记本电脑无法识别m.2固态硬盘解决方法-系统城...
  18. oracle 整理磁盘碎片
  19. CE-植物大战僵尸-僵尸-关卡-金币
  20. 那些令你憎恶的系统从何而来?

热门文章

  1. 上网篇:USB网络共享
  2. Guava Splitter,Splitter与Java split的对比
  3. PyG搭建GCN实现节点分类(GCNConv参数详解)
  4. Kotlin中问号 ? 和两个叹号 !! 的含义
  5. 低光图像增强(Low-light image enhancement)文章整理
  6. 19款探岳刷隐藏教程_19款探岳怎么选,小编在此支你几招 拿起小本本记住了
  7. 拉勾网爬取失败?试试这一招
  8. iPhone/iPad怎么进入恢复模式?
  9. python创建通讯录_python实现简易通讯录修改版
  10. 消费品与社区图腾:从 Coven 看女性向 PFP 市场