代码地址如下:
http://www.demodashi.com/demo/13434.html

Android自定义水波气泡

前言:公司在做的一个项目,要求在地图上以水波气泡的形式来显示站点,并且气泡要有水波的动态效果。好吧!既然有这样的需求,那就手撸一款水波气泡吧!

效果图预览

  最后完成的效果图如下

实现方式

步骤拆解

  在需要自定义view的时候,我首先要做的就是将最后要实现的效果来进行拆分,拆分成许多小的步骤,然后一步步的来实现,最终达到想要的效果。

  可以将文章开始的时候的效果图拆分成以下几部分:

  1. 画出气泡后面的白色背景。
  2. 画内部的紫色气泡。
  3. 用贝塞尔曲线让内部的紫色气泡动起来。

拆解之后,就可以按照拆解的步骤来一步步实现了。

画白色背景

  这里画白色背景有以下两种方式:

  1. path直接描述一个白色背景的形状。
  2. path描述一个三角形,然后在画出一个圆形,即成最终的白色背景了。

第一种方式如下图的左图,用path直接描述出了白色背景,这种方式可以用path.addArc()来画上部弧形,然后用path.moveTo()path.lineTo()方法描述出下部分的尖角。

第二种实现的方式如下图的右图,直接画出一个圆,再用path.moveTo()path.lineTo()方法来描述出下部分的尖角。

本文采用的是第二种方式来实现的,具体代码如下

//此处代码是下部尖角的path
mBackgroundPath.moveTo(mResultWidth / 2 - mOutRadius / 2, mResultWidth / 2 + mOutRadius / 2);mBackgroundPath.lineTo(mResultWidth / 2, mResultWidth / 2 + mOutRadius + mOutRadius / 4);mBackgroundPath.lineTo(mResultWidth / 2 + mOutRadius / 2, mResultWidth / 2 + mOutRadius / 2);//画外部背景canvas.drawPath(mBackgroundPath, mBackgroundPaint);canvas.drawCircle(mResultWidth / 2, mResultWidth / 2, mOutRadius, mBackgroundPaint);

画内部的气泡

  内部的气泡的形状其实就是缩小的外部背景,具体的代码如下

 //内部气泡的尖角
mBubblesPath.moveTo(mResultWidth / 2 - mOutRadius / 2, mResultWidth / 2 + mOutRadius / 2 - dp2px(getContext(), 5));mBubblesPath.lineTo(mResultWidth / 2, mResultWidth / 2 + mOutRadius + mOutRadius / 4 - dp2px(getContext(), 5));mBubblesPath.lineTo(mResultWidth / 2 + mOutRadius / 2, mResultWidth / 2 + mOutRadius / 2 - dp2px(getContext(), 5));
//画圆mBubblesPath.addCircle(mResultWidth / 2, mResultWidth / 2, mInnerRadius, Path.Direction.CCW);

到这里已经将气泡的基本形状画出来了,见下图

我们会发现气泡内部的颜色是渐变色,那渐变色是怎么设置的呢?其实自定义view就是将想要的效果通过画笔画在画布上,实现颜色的渐变肯定就是通过设置画笔的属性来实现的了,设置渐变色的代码如下

 //设置渐变色Shader shader = new LinearGradient(mResultWidth / 2, mResultWidth / 2 - mInnerRadius, mResultWidth / 2, mResultWidth / 2 + mInnerRadius, Color.parseColor("#9592FB"),Color.parseColor("#3831D4"), Shader.TileMode.CLAMP);mBubblesPaint.setShader(shader);

LinearGradient(float x0, float y0, float x1, float y1, @ColorInt int color0, @ColorInt int color1, @NonNull TileMode tile)

x0 y0 x1 y1:渐变的两个端点的位置 color0 color1 是端点的颜色 tile:端点范围之外的着色规则,类型是 TileModeTileMode 一共有 3 个值可选: CLAMP, MIRRORREPEAT。一般用 CLAMP就可以了。

让内部气泡动起来

  气泡内部的动画是水波的形式,这里画水波用的是二阶贝塞尔曲线,关于Android中贝塞尔曲线的知识可以参考这里。实现气泡内部水波效果的代码如下

 /*** 核心代码,计算path** @return*/private Path getPath() {int itemWidth = waveWidth / 2;//半个波长Path mPath = new Path();mPath.moveTo(-itemWidth * 3, baseLine);//起始坐标Log.d(TAG, "getPath: " + baseLine);//核心的代码就是这里for (int i = -3; i < 2; i++) {int startX = i * itemWidth;mPath.quadTo(startX + itemWidth / 2 + offset,//控制点的X,(起始点X + itemWidth/2 + offset)getWaveHeight(i),//控制点的YstartX + itemWidth + offset,//结束点的XbaseLine//结束点的Y);//只需要处理完半个波长,剩下的有for循环自已就添加了。}Log.d(TAG, "getPath: ");//下面这三句话是行程封闭的效果,不明白可以将下面3句代码注释看下效果的变化mPath.lineTo(width, height);mPath.lineTo(0, height);mPath.close();return mPath;}//奇数峰值是正的,偶数峰值是负数private float getWaveHeight(int num) {if (num % 2 == 0) {return baseLine + waveHeight;}return baseLine - waveHeight;}

上面的代码画出的水波如下图

到这里已经画出了水波,但现在水波还是静止的,要让水波不停的移动,就要添加属性动画,添加动画的代码如下

 /*** 不断的更新偏移量,并且循环。*/public void updateXControl() {//设置一个波长的偏移ValueAnimator mAnimator = ValueAnimator.ofFloat(0, waveWidth);mAnimator.setInterpolator(new LinearInterpolator());mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float animatorValue = (float) animation.getAnimatedValue();offset = animatorValue;//不断的设置偏移量,并重画postInvalidate();}});mAnimator.setDuration(1800);mAnimator.setRepeatCount(ValueAnimator.INFINITE);mAnimator.start();}

修改一下onDraw中的代码,如下

 @Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);mBubblesPath.reset();//设置渐变色Shader shader = new LinearGradient(mResultWidth / 2, mResultWidth / 2 - mInnerRadius, mResultWidth / 2, mResultWidth / 2 + mInnerRadius, Color.parseColor("#9592FB"),Color.parseColor("#3831D4"), Shader.TileMode.CLAMP);mBubblesPaint.setShader(shader);//此处代码是下部尖角的pathmBackgroundPath.moveTo(mResultWidth / 2 - mOutRadius / 2, mResultWidth / 2 + mOutRadius / 2);mBackgroundPath.lineTo(mResultWidth / 2, mResultWidth / 2 + mOutRadius + mOutRadius / 4);mBackgroundPath.lineTo(mResultWidth / 2 + mOutRadius / 2, mResultWidth / 2 + mOutRadius / 2);//内部气泡的尖角mBubblesPath.moveTo(mResultWidth / 2 - mOutRadius / 2, mResultWidth / 2 + mOutRadius / 2 - dp2px(getContext(), 5));mBubblesPath.lineTo(mResultWidth / 2, mResultWidth / 2 + mOutRadius + mOutRadius / 4 - dp2px(getContext(), 5));mBubblesPath.lineTo(mResultWidth / 2 + mOutRadius / 2, mResultWidth / 2 + mOutRadius / 2 - dp2px(getContext(), 5));//画外部背景canvas.drawPath(mBackgroundPath, mBackgroundPaint);canvas.drawCircle(mResultWidth / 2, mResultWidth / 2, mOutRadius, mBackgroundPaint);Log.d(TAG, "cx: " + mResultWidth / 2);//画水波mBubblesPath.addCircle(mResultWidth / 2, mResultWidth / 2, mInnerRadius, Path.Direction.CCW);canvas.drawPath(getPath(), mBubblesPaint);}

好了,现在水波已经可以移动了,看下效果

what!怎么成这个样子了呀,明显不是我想要的效果呀,肯定是哪里出错了,经过我仔细的推敲,总结了出现上面问题的原因,原因如下图

出现上面问题的原因就是因为下面三句代码

 mPath.lineTo(width, height);mPath.lineTo(0, height);mPath.close();

知道是这三句代码的原因,那应该怎么修改呢?这三句代码好像不能动,不然就会出现波浪画的不完整的情况,额…,那应该修改哪里呢?灵光一闪,不是可以裁剪画布嘛,只要将画布裁剪成想要的形状,然后在画波浪不久完美了。再修改onDraw方法,修改后的代码如下

  @Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);mBubblesPath.reset();//设置渐变色Shader shader = new LinearGradient(mResultWidth / 2, mResultWidth / 2 - mInnerRadius, mResultWidth / 2, mResultWidth / 2 + mInnerRadius, Color.parseColor("#9592FB"),Color.parseColor("#3831D4"), Shader.TileMode.CLAMP);mBubblesPaint.setShader(shader);//此处代码是下部尖角的pathmBackgroundPath.moveTo(mResultWidth / 2 - mOutRadius / 2, mResultWidth / 2 + mOutRadius / 2);mBackgroundPath.lineTo(mResultWidth / 2, mResultWidth / 2 + mOutRadius + mOutRadius / 4);mBackgroundPath.lineTo(mResultWidth / 2 + mOutRadius / 2, mResultWidth / 2 + mOutRadius / 2);//内部气泡的尖角mBubblesPath.moveTo(mResultWidth / 2 - mOutRadius / 2, mResultWidth / 2 + mOutRadius / 2 - dp2px(getContext(), 5));mBubblesPath.lineTo(mResultWidth / 2, mResultWidth / 2 + mOutRadius + mOutRadius / 4 - dp2px(getContext(), 5));mBubblesPath.lineTo(mResultWidth / 2 + mOutRadius / 2, mResultWidth / 2 + mOutRadius / 2 - dp2px(getContext(), 5));//画外部背景canvas.drawPath(mBackgroundPath, mBackgroundPaint);canvas.drawCircle(mResultWidth / 2, mResultWidth / 2, mOutRadius, mBackgroundPaint);Log.d(TAG, "cx: " + mResultWidth / 2);//切割画布,画水波canvas.save();mBubblesPath.addCircle(mResultWidth / 2, mResultWidth / 2, mInnerRadius, Path.Direction.CCW);//将画布裁剪成内部气泡的样子canvas.clipPath(mBubblesPath);canvas.drawPath(getPath(), mBubblesPaint);canvas.restore();}

到这里已经实现了文章开始时的效果了,文章也该结束了。

项目结构

结束语

  本文主要是讲解怎样实现水波气泡,并没有讲到View的测量,贴出的也只是绘制气泡的代码,完整的代码可以点击这里获取。

  虽然已经撸出了这个效果,但最后项目中并没有用这种动态的气泡,因为气泡多的时候是在是卡……。最后,喜欢此demo,就随手给个star吧!手撸一款精美的水波气泡

代码地址如下:
http://www.demodashi.com/demo/13434.html

注:本文著作权归作者,由demo大师发表,拒绝转载,转载需要作者授权

手撸一款精美的水波气泡相关推荐

  1. umi脚手架搭建的项目_还在从零开始搭建项目?手撸了款快速开发脚手架!

    之前开源了一款项目骨架mall-tiny,完整继承了mall项目的整个技术栈.总感觉mall-tiny集成了太多中间件,过于复杂了.这次对其进行了简化和升级,使它成为了一款拥有完整权限管理功能的快速开 ...

  2. 手撸一款简单高效的线程池(五)

    在之前的内容中,我们给大家介绍了 C++实现线程池过程中的一些常用线优化方案,并分析了不同机制使用时的利弊.这一篇,是线程池系列的最后一章.我们会介绍一下 CGraph 中的 threadpool 如 ...

  3. 还在从零开始搭建项目?手撸了款快速开发脚手架!

    简介 mall-tiny是一款基于SpringBoot+MyBatis-Plus的快速开发脚手架,拥有完整的权限管理功能,可对接Vue前端,开箱即用.   项目演示 mall-tiny项目可无缝对接m ...

  4. 手撸一款属于自己的Maven插件,说干就干

    大家好,我是冰河~~ 今天,冰河给大家分享一篇大部分人都不会的技能,那就是我们自己动手写一款属于自己的Maven插件.好了,直接进入今天的主题吧. Maven插件的相关概念 插件坐标定位 插件与普通j ...

  5. 【附源代码】手把手教你用Python+uiautomator2手撸一款自动抢菜应用

    包菜 -- 包你有菜 包菜是我开发的一款自动抢菜软件,解决yiqing期间大家吃菜难的问题. 需要完整源代码的朋友可以私信我 背景 事情的起因是这样的:yiqing导致物资紧张.配送困难,抢菜成为风控 ...

  6. 太强了,手撸一款导弹跟踪算法(Python版)

    作者:半壶砂 https://www.cnblogs.com/halfsand/p/7976636.html 这里涉及拦截导弹的自动跟踪.最近,看到了一个挺有趣的自动跟踪算法,一个Python的简单模 ...

  7. 手撸一款第三方链克钱包

    为什么80%的码农都做不了架构师?>>>    好久没更新博客了.主要是最近在研究区块链技术(炒币),当然也成为了一个小矿工,挖迅雷的玩客币.不过前不久,迅雷宣布将停止国内转账,而在 ...

  8. 手撸一款Android屏幕适配SDK

    1.屏幕适配的原因 Android手机屏幕碎片化严重,导致界面元素在不同屏幕上的显示效果不一致.下面我们看下未对控件适配在不同屏幕上的截图. 这张是在不同机型上未适配的截图. 这张是在不同机型适配后的 ...

  9. ​.NET手撸2048小游戏

    前言 2048是一款益智小游戏,得益于其规则简单,又和 2的倍数有关,因此广为人知,特别是广受程序员的喜爱. 本文将再次使用我自制的"准游戏引擎" FlysEngine,从空白窗口 ...

最新文章

  1. 基于YOLOv5模型压缩、模型量化、模型剪枝
  2. 运维基础(9)Linux性能调优三大系统
  3. Windows 网卡配置多VLAN
  4. r语言解析html,R语言爬虫入门-rvest教程
  5. NumPy 文件数据读写
  6. 浅谈数据结构-二叉树
  7. sublime插件CSS转rem配置
  8. C语言中指针的数据类型小结
  9. paip.android APK安装方法大总结系统应用的安装
  10. Layui字体图标大全
  11. 对话阿里云弹性计算负责人褚霸:把计算做到极致,关键还加量不加价!
  12. android 手机数据查看及 samsung galaxy s10 开发者模式
  13. iphone屏幕镜像如何全屏_苹果投屏有什么方法?使用“屏幕镜像”功能,任意切换大小屏幕...
  14. 集合分页展示,补全最后一页
  15. 企业宣传软文怎么写?手把手教大家撰写企业宣传软文
  16. js 获取当前与一个月前的日期
  17. 计算机笔记--【Redis高级】
  18. 如何编写一个程序,将一个给定的整数插到原本有序的整数序列中,使结果序列仍然有序。
  19. css flex 文字右对齐,css flex align-items属性 交叉轴上对齐方式垂直对齐方式
  20. 用格里高利公式求π的近似值

热门文章

  1. 基于mycat的mysql_基于Mycat中间件的MySQL读写分离
  2. STM32F103:一.(4)JWAG功能IO复用
  3. 【计算机二级基础知识笔记】【C+Python】
  4. 【声学基础】概述——辐射
  5. Nginx基本数据结构之ngx_queue_t
  6. linux内核中打开文件 及属性控制
  7. 内核并发控制---信号量 (来自网易)
  8. Homography 知多少?
  9. linux恢复桌面,ubuntu恢复unity桌面
  10. Java面试之ArrayList为什么线程不安全?