本系列自定义View全部采用kt

系统: mac

android studio: 4.1.3

kotlin version:1.5.0

gradle: gradle-6.5-bin.zip

废话不多说,先来看今天要完成的效果:

效果分析:

首先我们需要将这个功能分为两部分

  • 画渐变过渡文字
  • 画“爆炸烟花”

其实烟花就是由一条条贝塞尔曲线构成,那么只要会画一条曲线,再循环一下就可以画出多条曲线

首先来画一条曲线!

画曲线

Path方法介绍:

  • moveTo(x,y): 将画笔移动到x,y位置
  • quadTo(cX,cY,x2,y2): cX和cY表示控制点, x2,y2表示结束点

这段代码很简单,就是贝塞尔最基本的使用

让贝塞尔动起来,

很显然,如果想让贝塞尔动起来,就不能使用这种方式, 最起码保证不能写死数据

先来看一眼要完成的效果,在来看代码:

再来看一眼代码:

class FireworksBlogView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {val paint = Paint(Paint.ANTI_ALIAS_FLAG).also {it.strokeWidth = 2.dpit.color = Color.BLACKit.style = Paint.Style.STROKE}var pointF = PointF()set(value) {field = value// 画线path.lineTo(value.x, value.y)invalidate()}val path = Path()override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {super.onSizeChanged(w, h, oldw, oldh)animator()}private fun animator() {val p0 = PointF(50.dp, 100.dp) // 开始点val p1 = PointF(100.dp, 50.dp) // 控制点val p2 = PointF(150.dp, 100.dp) // 结束点val animator = ObjectAnimator.ofObject(this,"pointF",SecondBezierTypeEvaluator(p1),p0,p2)// 将画笔移动到开始位置path.moveTo(p0.x, p0.y)animator.duration = 2000L // 设置时间animator.start()}override fun onDraw(canvas: Canvas) {canvas.drawPath(path, paint)}
}

如果想要自己画贝塞尔曲线,那么就不能通过paint自带画贝塞尔曲线的方式,

而是自己通过贝塞尔公式来计算!

这段代码中,最重要的就是自定义TypeEvaluator()方法

来看看SecondBezierTypeEvaluator类

贝塞尔公式现在都是透明的,只要往里面带入一下值就可以

只需要注意的是:

  • p0:开始点
  • p1:控制点
  • p2:结束点
  • t: 进度(0…1)

调用的时候,只需要

val animator = ObjectAnimator.ofObject(this,"pointF",SecondBezierTypeEvaluator(p1), // 传入控制点p0, // 开始点p2 // 结束点
)

最终贝塞尔曲线的路线,就会赋值到pointF上, 然后一直绘制pointF即可!

那么二阶贝塞尔这么操作的话,三阶贝塞尔也是同样的道理:

ObjectAnimator.ofObject(this,"pointF",ThirdBezierTypeEvaluator(p1, p2), // p1控制点1; p2控制点2;p0,// 开始点p3) // 结束点

这里用不到三阶贝塞尔,只是举例子.

画多条贝塞尔线

假设需要画100条贝塞尔曲线,并且平均分开

首先先别着急画贝塞尔曲线,先来简单的,**先画100条直线,**看看思路是否正确,然后在往下走

我们现在要画的效果长这样:

这里假装有100条QaQ, 其实只有8条…

这里我们要想画成这种效果,其实就是在一个圆内,求对应角的位置

这个圆的半径是自己定义的

每个角度 = 360.0 / 总个数

画辅助线来看看

这里就可以通过三角函数算出角A的位置

角A.x = 半径 * cos(45) + 中心点.x

角A.y = 半径 * sin(45) + 中心点.y

然后改变角度就可计算出其他的位置,来看看代码

可以看出,思路是没问题的, 那么结合画曲线和画直线,来完成今天的效果

  • 绘制曲线时候,是通过自定义TypeEvaluator来绘制

那么要绘制多条曲线,肯定是将所有的点放到list中然后交给TypeEvaluator来处理

来看看关键代码


// 控制点
private val controlPointF = PointF(100.dp, 100.dp)// 开始点
private val startPointF by lazy { PointF(width / 2f, height / 2f) }// 用来存储路径 first画笔颜色, second:路径
private val paths = arrayListOf<Pair<Int, Path>>()// 通过属性动画改变了值会跑到这里
var points = arrayListOf<PointF>()set(value) {field = valuerepeat(COUNT) {// 绘制每一条曲线paths[it].second.lineTo(value[it].x, value[it].y)}invalidate()}private fun secondListBezierAnimator() {val p0 = arrayListOf<PointF>() // 开始点val p1 = arrayListOf<PointF>() // 控制点val p2 = arrayListOf<PointF>() // 结束点var angle = 0.0// 循环所有的点repeat(COUNT) {p0.add(startPointF) // 添加开始点p1.add(controlPointF) // 添加控制点val x = FireworksView.RADIUS * sin(Math.toRadians(angle)) + width / 2fval y = FireworksView.RADIUS * cos(Math.toRadians(angle)) + height / 2fp2.add(PointF(x.toFloat(), y.toFloat()))// 一个的角度angle += 360.0 / COUNTval path = Path()// 将画笔移动到开始点path.moveTo(p0[it].x, p0[it].y)// 保存起来paths.add(colorRandom to path)}val animator = ObjectAnimator.ofObject(this,"points",SecondListBezierTypeEvaluator(p1),p0,p2)animator.duration = FireworksView.TIMEanimator.start()
}

这段代码应该也比较简单,就是画一条会动的曲线和 画多条直线的结合!

// 随机颜色
val colorRandom: Int get() {return Color.argb(255,(0 until 255).random(),(0 until 255).random(),(0 until 255).random())}

来看看SecondListBezierTypeEvaluator代码

class SecondListBezierTypeEvaluator(private val p1: List<PointF>) :TypeEvaluator<List<PointF>> {// p0开始点; p1控制点; p2结束点override fun evaluate(t: Float, p0: List<PointF>, p2: List<PointF>): List<PointF> {// 二阶贝塞尔公式地址: https://baike.baidu.com/item/贝塞尔曲线/1091769if (!(p0.size == p1.size && p0.size == p2.size)) {throw RuntimeException("长度不匹配")}val points = arrayListOf<PointF>()repeat(p0.size) {points.add(PointF((1 - t).pow(2) * p0[it].x + 2 * t * (1 - t) * p1[it].x + t.pow(2) * p2[it].x,(1 - t).pow(2) * p0[it].y + 2 * t * (1 - t) * p1[it].y + t.pow(2) * p2[it].y))}return points}
}

这里也比较简单,同样都是套公式, 不一样的只是多个一个循环而已

绘制:

override fun onDraw(canvas: Canvas) {paint.style = Paint.Style.STROKE// 绘制每一条线repeat(COUNT) {// 设置颜色paint.color = paths[it].first// 画曲线canvas.drawPath(paths[it].second, paint)}
}

来看看当前效果:

渐变文字绘制

还是同样的套路,从最简单开始

绘制一段文字,并居中

这段代码比较简单,来看代码

Paint#measureText:

@param 0: 需要测量的文字

返回文字的宽度

Canvas#drawText:

@param 0: 需要绘制的文字

@param start/ end: 绘制文字开始 / 结束 位置

@param x,y: 绘制文字位置

@param paint: 画笔

文字坐标系可以参考这篇

绘制完文字后,首先让文字全部渐变!

使用渐变有2个需要注意的点:

  • 渐变的时候,Paint.color 会失效
  • 渐变完成后,一定要将shader设置为null

否则就会出现这种情况

渐变都是调用api,就不多介绍了,如果有疑问底部会给出完整demo

让渐变颜色动起来,

首先来看看移动位置的起点以及终点

  • 蓝色的为渐变的开始位置 (x)

  • 绿色为渐变的结束位置 (x + textWidth)

动起来还是用属性动画

有了这是一只在变得值,那么只需要变换渐变的位置即可!

来看看绘制文字完整代码:

/** TODO 绘制文字*/
private fun drawText(canvas: Canvas) {paint.textSize = FireworksView.TEXT_SIZEpaint.style = Paint.Style.FILLpaint.color = Color.BLACK// 文字宽度val textWidth = paint.measureText(FireworksView.TEXT)val x = width / 2f - textWidth / 2fval y = -paint.fontMetrics.top + 50.dp// 渐变颜色val colors = intArrayOf(Color.BLACK,Color.RED, Color.YELLOW,Color.BLACK)// 线性渐变val linearGradient = LinearGradient(x, // 开始位置0f,x + 50.dp, // 渐变的位置 (这个位置是固定的,然后移动位置即可)0f,colors,null,Shader.TileMode.CLAMP)// 使用ktx扩展平移渐变位置linearGradient.transform {setTranslate(textWidthShader, 0f)}//  设置渐变色paint.shader = linearGradientcanvas.drawText(FireworksView.TEXT, 0, FireworksView.TEXT.length,x, y, paint)paint.shader = null
}

最终效果:

思路参考自

完整代码

原创不易,您的点赞与关注就是我最大的动力!

其他自定义文章:

  • android 自定义View 视差动画
  • android 仿QQ拖动效果
  • android 图解PhotoView 从百草园到三味书屋
  • android 浅析RecyclerView回收复用机制及实战(仿探探效果)
  • android ViewPager 进阶(仿画廊/图书翻页)

android自定义View 中秋节放个烟花吧~相关推荐

  1. android自定义View: 九宫格解锁

    本系列自定义View全部采用kt 系统:mac android studio: 4.1.3 kotlin version1.5.0 gradle: gradle-6.5-bin.zip 废话不多说,先 ...

  2. android自定义view获取控件,android 自定义控件View在Activity中使用findByViewId得到结果为null...

    转载:http://blog.csdn.net/xiabing082/article/details/48781489 1.  大家常常自定义view,,然后在xml 中添加该view 组件..如果在 ...

  3. Android自定义View:ViewGroup(三)

    自定义ViewGroup本质是什么? 自定义ViewGroup本质上就干一件事--layout. layout 我们知道ViewGroup是一个组合View,它与普通的基本View(只要不是ViewG ...

  4. android 自定义view滚动条,Android自定义View实现等级滑动条的实例

    Android自定义View实现等级滑动条的实例 实现效果图: 思路: 首先绘制直线,然后等分直线绘制点: 绘制点的时候把X值存到集合中. 然后绘制背景图片,以及图片上的数字. 点击事件down的时候 ...

  5. android 自定义 对号,Android自定义View实现打钩动画功能

    先上效果图 动图 静态图 1. 回顾 [Android自定义View:一个精致的打钩小动画]上一篇文章,我们已经实现了基本上实现了控件的效果了,但是...但是...过了三四天后,仔细看回自己写的代码, ...

  6. android自定义view案例,Android自定义View的实现方法实例详解

    一.自绘控件 下面我们准备来自定义一个计数器View,这个View可以响应用户的点击事件,并自动记录一共点击了多少次.新建一个CounterView继承自View,代码如下所示: 可以看到,首先我们在 ...

  7. Android 自定义View

    [Android 自定义View] Android 自定义View 自定义View基础 自定义TextView 继承View重写onDraw方法 View的构造方法 自定义属性 创建attrsxml文 ...

  8. Android 自定义view完全解析--带你通透了解自定义view

    参考转自郭霖博客带你一步步深入了解View系列 Android LayoutInflater原理分析 相信接触Android久一点的朋友对于LayoutInflater一定不会陌生,都会知道它主要是用 ...

  9. Android自定义View实现方位刻度尺(类似于吃鸡手游)

    Android自定义View实现方位刻度尺(类似于吃鸡手游) 先上效果图 gif可能看不清,我下面放几张图片 原理解析 首先,我们应该把看得到的内容从上至下分成三部分:最上面的文字.中间的竖线和最下面 ...

  10. Android自定义View绘制流程

    Android视图层次结构简介 在介绍View绘制流程之前,咱们先简单介绍一下Android视图层次结构以及DecorView,因为View的绘制流程的入口和DecorView有着密切的联系. 我们平 ...

最新文章

  1. Python3学习笔记-数据类型和变量
  2. 博客园7月底至8月初51Aspx源码发布详情
  3. 【干货】2014Q4手游崩溃数据报告,iphone6第1、三星第2
  4. Reading——The Non-Designer's Design Book
  5. Linux 命令之 whoami -- 打印当前有效的用户名称
  6. git reflog and checkout
  7. 二、配置数据源、SessionFactory、domain对象
  8. 教材订购模块java代码实现_java教材征订系统
  9. 常见函数式接口及其lambda实现样例
  10. 从aspx后台页面向浏览器输出js文件
  11. 毕设题目:Matlab语音处理
  12. 深度卷积神经网络(AlexNet)
  13. 日本向日葵8号卫星数据下载
  14. 洛谷P1600 天天爱跑步
  15. u盘中毒文件为html文档,U盘u盘中毒,文件被隐藏了怎么办 – 手机爱问
  16. 年轻人的第一次汉化APK(教程)
  17. ScreenFlow 8 for Mac(mac录屏软件)免激活版
  18. python一键批量制作word邀请函
  19. 红米2a android5,红米手机/小米手机2S/2A三机对比图赏
  20. 机器学习之特征向量维度与样本空间

热门文章

  1. 分享三大外汇日内交易策略
  2. addClass()与removeClass
  3. json-lib将xml转json报错java.lang.NoClassDefFoundError: nu/xom/ParentNode
  4. 即构SDK新增变声、立体声(3D环绕)、混响三大功能
  5. python mysql library,python调用mysql报错解决方案
  6. Cacti auth.php,linux下cacti的搭建之详细过程!
  7. JavaScript - ES6之Promise(then方法详解)
  8. SQL中常用的字符串LEFT函数和RIGHT函数详解
  9. PNG,JPEG,BMP,JIF图片格式详解及其对比
  10. BZOJ.4340.[BJOI2015]隐身术(后缀数组 搜索)