大体思路,借鉴了中的缓冲圆圈动画Android 绘制动画(波浪动画/轨迹动画/PathMeasure)_LZ_Luzhuo的博客-CSDN博客_android 轨迹动画

绘制一个矩形边框,通过截取轨迹片段,通过修改轨迹片段的 start 和 stop 不断刷新,就会形成线条在移动的动画效果。

准备工作

1.控制线条的属性

// 设置旋转方向 CW 顺时针 CCW 逆时针var direction: Path.Direction = Path.Direction.CCWset(value) {field = value}// 初始化画笔
private val paint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)// 设置线条的宽度
var lineWidth: Int = 10set(value) {field = value + 10paint.strokeWidth = KBubbleUtils.dp2px(field).toFloat()}// 设置线条的长度
var lineWidth: Int = 10set(value) {field = value + 10paint.strokeWidth = KBubbleUtils.dp2px(field).toFloat()}// 设置线条的旋转速度
var lineWidth: Int = 10set(value) {field = value + 10paint.strokeWidth = KBubbleUtils.dp2px(field).toFloat()}// type == 0 单条线   type == 1 双线
var type = 0set(value) {field = value// 初始化线条位置rightAnimValue = (screenWidth + lineLength - (radius / 2)) / lengthleftAnimValue = (screenWidth * 2 + screenHeight + lineLength - (radius / 2)) / length}

2.设置一下画笔的基本属性和初始化动画

 init {paint.style = Paint.Style.STROKEpaint.strokeWidth = KBubbleUtils.dp2px(lineWidth).toFloat()// 绘制的轨迹dst = Path()// 画一个圆, 关联pathMeasure// 创建属性动画animator = ValueAnimator.ofFloat(0f, 1f)animator?.duration = 5000animator?.interpolator = LinearInterpolator()animator?.repeatCount = ValueAnimator.INFINITEanimator?.addUpdateListener { invalidate() }animator?.start()}

3.创建一个带圆角的矩形Path

这里用到了 moveTo(移动到某一位置)、 lineTo(画直线)、 cubicTo(二阶贝塞尔曲线,画圆角)

小知识点

// 将起始点移动到 x, y
public void moveTo(float x, float y) // 画一条直线到 x,y 坐标
public void lineTo(float x, float y)// 二阶贝塞尔曲线(两个控制点)
// x1 y1 第一个控制点
// x2 y2 第二个控制点
// x3 y3 曲线的终点
public void cubicTo(float x1, float y1, float x2, float y2,float x3, float y3)

开始绘制矩形并测量矩形的长度

 val path = Path()path.moveTo(radius, 0f)path.lineTo(screenWidth - radius, 0f)path.cubicTo(screenWidth - radius / 2,0f,screenWidth.toFloat(),radius / 2,screenWidth.toFloat(),radius)path.lineTo(screenWidth.toFloat(), screenHeight - radius)//path.cubicTo(screenWidth.toFloat(),screenHeight - radius / 2,screenWidth - radius / 2,screenHeight.toFloat(),screenWidth - radius,screenHeight.toFloat())path.lineTo(radius, screenHeight.toFloat())path.cubicTo(radius / 2,screenHeight.toFloat(),0f,screenHeight - radius / 2,0f,screenHeight - radius)path.lineTo(0f, radius)path.cubicTo(0f, radius / 2, radius / 2, 0f, radius, 0f)pathMeasure = PathMeasure() // 测量Path的类pathMeasure.setPath(path, true)length = pathMeasure.length // 路径长度

想让线条变得炫酷一些可以给线条增加渐变色

小知识点

详细请参考 线性渐变LinearGradient使用总结_盛大人很低调的博客-CSDN博客_lineargradient

我们这里用到线性渐变

// x0 y0 渐变的起始点
// x1 横向渐变的长度
// y1 纵向渐变的长度
// colors 渐变颜色 colors 必须 >= 2 否则会报错
// pos 必须和colors数量一致 或者 写 null
// tile 渐变的模式 CLAMP 会将边缘的一个像素进行拉伸、扩展
//               REPEAT 平移复制
//               MIRROR 镜面翻转
public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int[] colors,@Nullable float[] positions, @NonNull TileMode tile)

绘制花里胡哨的线

val colors = intArrayOf(Color.parseColor("#4FBCB5"),Color.parseColor("#9D27DC"),Color.parseColor("#4FBCB5"))val pos = floatArrayOf(0f, 0.5f, 1f)val linearGradient =LinearGradient(0f, 0f, lineLength, lineLength, colors, pos, Shader.TileMode.REPEAT)val matrix = Matrix()linearGradient.setLocalMatrix(matrix)paint.shader = linearGradient

设置左右两条线的起点

rightAnimValue = (w + lineLength - (radius / 2)) / length
leftAnimValue = (w * 2 + h + lineLength - (radius / 2)) / length

4.开始绘制矩形和线条片段

// 关键方法
private fun drawLine(dst: Path, animValue: Float, canvas: Canvas): Float {var tempAnimValue = animValuedst.reset()// 绘制方向if (direction == Path.Direction.CW) {tempAnimValue += speed} else {tempAnimValue -= speed}dst.lineTo(0f, 0f) // 解决硬件加速的bugval stop = length * tempAnimValueval start = stop - lineLength//这里就是当动画走完一遍让他看起来不是立马重置,而是要继续绘制完线条的长度,才能连贯的转if (tempAnimValue >= 1) { // 顺时针旋转pathMeasure.getSegment(length * tempAnimValue - lineLength,length,dst,true) // 截取整个path的任何片段(开始长度 / 结束长度 / 保存截取的路径 / 是否从起点开始截取)canvas.drawPath(dst, paint)pathMeasure.getSegment(0f,length * (tempAnimValue - 1),dst,true) // 截取整个path的任何片段(开始长度 / 结束长度 / 保存截取的路径 / 是否从起点开始截取)canvas.drawPath(dst, paint)if (tempAnimValue > 1 + lineLength / length) {tempAnimValue = lineLength / length}} else if (tempAnimValue <= lineLength / length) { // 逆时针旋转pathMeasure.getSegment(0f,length * tempAnimValue,dst,true)canvas.drawPath(dst, paint)pathMeasure.getSegment((1 + tempAnimValue) * length - lineLength,length,dst,true)canvas.drawPath(dst, paint)if (tempAnimValue < 0) {tempAnimValue = 1f}} else {pathMeasure.getSegment(start,stop,dst,true) // 截取整个path的任何片段(开始长度 / 结束长度 / 保存截取的路径 / 是否从起点开始截取)canvas.drawPath(dst, paint)}return tempAnimValue // 返回当前截取的位置}
override fun onDraw(canvas: Canvas) {super.onDraw(canvas)rightAnimValue = drawLine(dst, rightAnimValue, canvas)if (type == 1) { // type == 1 双线leftAnimValue = drawLine(dst, leftAnimValue, canvas)}}

完整代码

package com.example.phonelightning.widget.screenimport android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import android.view.animation.LinearInterpolator
import com.example.phonelightning.widget.KBubbleUtilsclass LinePathView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {// 方向var direction: Path.Direction = Path.Direction.CCWset(value) {field = value}private val paint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)private val dst: Pathprivate var length: Float = 0fprivate var rightAnimValue: Float = 0fprivate var leftAnimValue: Float = 0fprivate val radius = 50fprivate var screenWidth: Int = 0private var screenHeight: Int = 0var lineWidth: Int = 10set(value) {field = value + 10paint.strokeWidth = KBubbleUtils.dp2px(field).toFloat()}var lineLength: Float = 400fset(value) {field = 400f + 50 * value}var speed: Float = 0.002fset(value) {field = 0.002f + (value / 2000)}lateinit var pathMeasure: PathMeasureprivate var animator: ValueAnimator? = nullvar type = 0set(value) {field = valuerightAnimValue = (screenWidth + lineLength - (radius / 2)) / lengthleftAnimValue = (screenWidth * 2 + screenHeight + lineLength - (radius / 2)) / length}init {paint.style = Paint.Style.STROKEpaint.strokeWidth = KBubbleUtils.dp2px(lineWidth).toFloat()dst = Path()// 画一个圆, 关联pathMeasure// 创建属性动画animator = ValueAnimator.ofFloat(0f, 1f)animator?.duration = 5000animator?.interpolator = LinearInterpolator()animator?.repeatCount = ValueAnimator.INFINITEanimator?.addUpdateListener { invalidate() }animator?.start()}override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {super.onSizeChanged(w, h, oldw, oldh)screenHeight = hscreenWidth = wval path = Path()path.moveTo(radius, 0f)path.lineTo(screenWidth - radius, 0f)path.cubicTo(screenWidth - radius / 2,0f,screenWidth.toFloat(),radius / 2,screenWidth.toFloat(),radius)path.lineTo(screenWidth.toFloat(), screenHeight - radius)//path.cubicTo(screenWidth.toFloat(),screenHeight - radius / 2,screenWidth - radius / 2,screenHeight.toFloat(),screenWidth - radius,screenHeight.toFloat())path.lineTo(radius, screenHeight.toFloat())path.cubicTo(radius / 2,screenHeight.toFloat(),0f,screenHeight - radius / 2,0f,screenHeight - radius)path.lineTo(0f, radius)path.cubicTo(0f, radius / 2, radius / 2, 0f, radius, 0f)pathMeasure = PathMeasure() // 测量Path的类pathMeasure.setPath(path, true)length = pathMeasure.length // 路径长度//渐变颜色.colors和pos的个数一定要相等val colors = intArrayOf(Color.parseColor("#4FBCB5"),Color.parseColor("#9D27DC"),Color.parseColor("#4FBCB5"))val pos = floatArrayOf(0f, 0.5f, 1f)val linearGradient =LinearGradient(0f, 0f, lineLength, lineLength, colors, pos, Shader.TileMode.REPEAT)val matrix = Matrix()linearGradient.setLocalMatrix(matrix)paint.shader = linearGradientrightAnimValue = (w + lineLength - (radius / 2)) / lengthleftAnimValue = (w * 2 + h + lineLength - (radius / 2)) / length}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)rightAnimValue = drawLine(dst, rightAnimValue, canvas)if (type == 1) {leftAnimValue = drawLine(dst, leftAnimValue, canvas)}}private fun drawLine(dst: Path, animValue: Float, canvas: Canvas): Float {var tempAnimValue = animValuedst.reset()if (direction == Path.Direction.CW) {tempAnimValue += speed} else {tempAnimValue -= speed}dst.lineTo(0f, 0f) // 解决硬件加速的bugval stop = length * tempAnimValueval start = stop - lineLengthif (tempAnimValue >= 1) {pathMeasure.getSegment(length * tempAnimValue - lineLength,length,dst,true) // 截取整个path的任何片段(开始长度 / 结束长度 / 保存截取的路径 / 是否从起点开始截取)canvas.drawPath(dst, paint)pathMeasure.getSegment(0f,length * (tempAnimValue - 1),dst,true) // 截取整个path的任何片段(开始长度 / 结束长度 / 保存截取的路径 / 是否从起点开始截取)canvas.drawPath(dst, paint)if (tempAnimValue > 1 + lineLength / length) {tempAnimValue = lineLength / length}} else if (tempAnimValue <= lineLength / length) {pathMeasure.getSegment(0f,length * tempAnimValue,dst,true)canvas.drawPath(dst, paint)pathMeasure.getSegment((1 + tempAnimValue) * length - lineLength,length,dst,true)canvas.drawPath(dst, paint)if (tempAnimValue < 0) {tempAnimValue = 1f}} else {pathMeasure.getSegment(start,stop,dst,true) // 截取整个path的任何片段(开始长度 / 结束长度 / 保存截取的路径 / 是否从起点开始截取)canvas.drawPath(dst, paint)}return tempAnimValue}
}

轨迹动画(屏幕线条环绕)相关推荐

  1. 百度地图——显示小车轨迹动画回放

    百度地图,Android显示车辆轨迹动画 初次设计想就用百度自带的显示覆盖物的方式,计算两个坐标点的距离,添加短距离的坐标点,然后在密密麻麻的坐标点之间显示,隐藏覆盖物,形成移动的视觉效果. 后来发现 ...

  2. android 数字画圈动画,【iOS】画圈的轨迹动画

    动画 最近接到一个需求,需要做一个启动的加载动画,如下图: load.gif 思考下,可以拆分几个部分 动画拆分 1 . 中间圆圈的旋转动画! 2 . 两边的画线轨迹! 3 . 动态变化的进度显示! ...

  3. css3圆形轨迹动画

    <!doctype html>      <html lang="en">      <head>          <meta char ...

  4. iOS 动画绘制线条颜色渐变的折线图

    效果图 .................... 概述 现状 折线图的应用比较广泛,为了增强用户体验,很多应用中都嵌入了折线图.折线图可以更加直观的表示数据的变化.网络上有很多绘制折线图的demo,有 ...

  5. 分享5:我常用的一款动画屏幕录制软件

    LICEcap : 是一款简洁易用的动画屏幕录制软件 下载: 适用于Windows: https://www.cockos.com/licecap/licecap128-install.exe 适用于 ...

  6. keep怎么弄轨迹动画_keep怎么录视频?教学视频录像和轨迹动画视频录制方法介绍...

    keep怎么录视频?平时大家没事的时候,都会打开Keep软件,然后按上面的教程进行煅炼,那么我们可以利用keep锻炼时录好视频方便查看.同时呢,我们使用keep记录跑步信息以后,还可以可以将跑步在地图 ...

  7. vue使用高德地图aMap实现轨迹动画查询显示

    vue使用高德地图搜索地址添加标记marker,定位,拖拽选址功能https://blog.csdn.net/SmartJunTao/article/details/123955679 实现效果: 需 ...

  8. PathMeasure 轨迹动画神器 路径动画

    PathMeasure 轨迹动画神器 轨迹动画一般利用SVG来实现,或者使用属性动画,自定义估计值,根据两点之间的线性关系式计算坐标(复杂) 但是使用PathMeasure来进行绘制轨迹动画,so e ...

  9. vue项目中 使用百度地图 轨迹动画

    在上篇博客中,介绍了如何在vue项目中集成百度地图,这篇博客主要是说如何在vue项目中使用轨迹动画 在项目开发过程中,比如你需要实时的观察一个人的行走路线,行走过程.  这个时候我们就需要在地图上使用 ...

最新文章

  1. C# 3.0 New Language Features (Part 1)
  2. ESP32 OTA升级策略
  3. BIOS——PE无法识别硬盘问题问题解决方案
  4. 重磅发布 | 承载亿级流量的开发框架,闲鱼Flutter技术解析与实战大公开
  5. linux安装java的脚本吗,Linux安装JDK脚本
  6. Linux维护笔记四
  7. leetcode323. 无向图中连通分量的数目
  8. 十步让 WebForm项目 变为 Mvc项目
  9. PyTorch学习(7)-Seq2Seq与 Attention
  10. sonarqube代码检核工具安装
  11. java in 绑定变量_ng-model绑定的变量在controller中为undefined
  12. 深度linux桌面启动器,在Deepin桌面系统启动器中创建“我的世界”启动项
  13. 华为堡垒机_浪潮无线分析,华为云堡垒机
  14. 前端基础:call,apply,bind的的理解
  15. 微信小程序开发部署发布可以在10分钟内完成
  16. php手机网页唤醒支付宝APP支付,支付宝H5唤醒APP
  17. 学习笔记13--障碍物检测之基于图像障碍物检测
  18. 计算机应用专业招聘试讲内容,广东文艺职业学院2018年第二批合同制人员招聘专业技能考核和试讲题目...
  19. 职业规划-Android工程师
  20. java和c#通过esb服务互调用组件

热门文章

  1. CUDA out of memory(然鹅明明还有空间)
  2. font-face使用的web字体格式介绍、浏览器兼容和字体转换
  3. C/C++黑魔法-常量字符串连接
  4. 手机拍照上传图片旋转角度问题
  5. 0x00007FF8DC013526(ntdll.dll) 处(位于XXX.exe中) 引发的异常 osg 0xC0000005: 读取位置 0xFFFFFFFFFFFFFFFF时发生访问
  6. 数据库原理与应用 构建音乐商店,实现复杂查询和批量操作及视图练习
  7. C语言学习笔记:switch语句、循环语句--while、for、getchar,eof概念(推荐MSDN查询函数概念等)
  8. 使用composer开发dfc程序
  9. gcc / -Wl,-Bsymbolic
  10. 实践:nginx代理,通过使用GeoIp模块获取访问者IP及访问地区信息