自定义View-旋转变色圆角三角形的绘制

news2024/11/18 23:29:07

8760bfbe10f644409aeefa541ca7fd40.jpeg

f82fe27a055a3dff3776b5345bf4415f.gif

本文字数:3151

预计阅读时间:20分钟

4dbe7f0133ae250877db3e6686368a51.png

在现代设计中,动效图在APP的UI界面中所起到的作用无疑是显著的。相比于静态的界面,动效更符合人类的自然认知体系,它有效地降低了用户的认知负载,UI动效俨然已经成为了不可或缺的一部分。

那么在开发过程中,当遇到UI提供的动态物料满足不了内存以及效果的要求时,我们程序员就不得不通过代码自己去实现效果,这就引出了我们今天要实现的这个旋转变色圆角三角形。当时接到需求的时候,我也以为只要UI同学提供物料即可,但是实验的时候发现如果想呈现动效效果好一点的话,在不同的手机上可能会发生丢帧,OOM以及图形的严重锯齿化等一系列问题,最后实现这个效果的重任就落到了我们程序员的头上(很头大)。

记得当时为了实现这个动画效果,也是发挥了我尘封已久的初中数学知识,还有朋友的帮忙,才能顺利完成开发任务。所以虽然时间过去了很久,依然想做个记录,供大家参考探讨!废话说完终于要进入正题了,大家可以看一下这个UI动效,见下图:

a6b26cf5d57dfc04f2a586c413ca9c5b.gif

首先我们仔细看这个动画,将它进行静态化拆分,大概如下图:

9f40d9cbef4ba0e9db84d9b933ddd1b6.png

其实它的组成还是相对简单的,就是由以下这几个元素构成:黑色的三角形,沿着黑色三角形运动的红色/黑色轨迹,还有整体的旋转。那么我们就按部就班地一步一步来就行了。

第一步:画出一个“被放倒”的等边黑色三角形

问题来了,它三个角的坐标怎么确定呢?这里我们可以先考虑一下,它是要旋转的,等边三角形旋转的轨迹肯定是一个圆,那么呼之欲出的,到这里就需要我们拿出初中的数学知识了——三角形的外接圆,通过它,我们就能够确认出这个三角形的坐标系,也就是三个顶点的坐标位置,自然就可以连线出这个三角形的形状,具体做法如下:

fb544cef04299bf1f0f97db98d27d0e3.png

通过上面这个图,相信能唤醒一些数学基本知识了。我们以o点为坐标系的原点,边长我们命名为a,外接圆半径是r,那么:

A点的x坐标就应该是负的外接圆半径除以2,y坐标是负的三角形的边长除以2,即(-r/2,-a/2);

B点的x坐标应该是负的外接圆半径/2, y坐标是三角形的边长除以2,即(-r/2,a/2);

C点的x坐标应该是外接圆半径,y坐标是0,即(r,0)。

在这个过程中又一个问题出现了,外周圆的半径怎么确认呢?数学公式已经忘得差不多了,但是百度出真知,果然,度娘给出了我们想要的答案:r=√3/3*a,所以我们现在就可以确认这三个点的坐标值了这个过程转换成代码我们就实现了第一步了,具体代码如下:

1、自定义一个View,然后定义出我们所需要的这些变量:

private Paint mPaint;  //画笔
private Path mPath;   //三个点连成线的路径

2、确定这个view的大小,我们可以暂定设置为100dp(当然这个是可以xml动态设置的):

@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), getDefaultSize(0, heightMeasureSpec));
    int childHeightSize = DisplayUtils.dipToPx(getContext(), 100);
    heightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
    int childHeightWidth = DisplayUtils.dipToPx(getContext(), 100);
    widthMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightWidth, MeasureSpec.EXACTLY);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  }

3、在初始化的时候,我们按照上面分析的数学步骤先初始化它的坐标系:

private void initCoordinate() {
    x0 = -(float) (Math.sqrt(3) * mWidth / 6);
    y0 = -mWidth / 2;
    x1 = (float) (Math.sqrt(3) * mWidth / 3);
    y1 = 0;
    x2 = -(float) (Math.sqrt(3) * mWidth / 6);
    y2 = mWidth / 2;
}

4、我们初始化它的画笔,注意我们这个三角形的拐角部分都是圆角,所以需要特别设置一下:

private void initPaint() {
    mPaint = new Paint();
    mPaint.setAntiAlias(true);//抗锯齿
mPaint.setStyle(Paint.Style.STROKE);//画线模式  mPaint.setStrokeWidth(DisplayUtils.dipToPx(mContext, 2));
    //设置所有拐角处变成圆角
    mPaint.setPathEffect(new CornerPathEffect(10));
    mColorBlack = mContext.getResources().getColor(R.color.bg_333333);
    mPaint.setColor(mColorBlack);
 }

5、将三点连线:

private void initPath() {
    mPath = new Path();
    mPath.moveTo(x0, y0);
    mPath.lineTo(x1, y1);
    mPath.lineTo(x2, y2);
    mPath.close();
 }

6、在onDraw方法中 把坐标中心挪到画布的中心 然后把他们画出来:

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //移动坐标系
  canvas.translate(getMeasuredWidth() / 2, getMeasuredHeight() / 2);
    canvas.drawPath(mPath, mPaint);
 }

到这一步 我们run一下代码,果然整个静态黑色圆角三角形 就可以呈现出来了,如下图:

87120824c65aca515bb9694ce08942c7.png

第二步:进行红/黑线走向的绘制

仔细观察可以得出,红/黑线是onebyone,在等同的时间内,走完这个静态三角形轨迹的,属性动画应该可以搞定,动画不难,难的是利用进度进行计算的这个过程,我们下面主要说说获取到动画进度后,怎么计算红/黑线的坐标数值。

首先,我们要定义一个动态的点(x,y)用来记录红/黑线这个走向的坐标点,接下来,我们就要根据这个黑色三角形的三个顶点,通过属性动画计算(x,y)的路径,步骤如下:

1、我们确定画笔的起点以及终点也就是三个顶点,如第一段的起点就是(x0,y0)终点就是(x1,y1),第二段的起点就是(x1,y1)终点就是(x2,y2)以此类推第三个点的坐标也很容易确认;

2、因为是等边三角形,所以这个边的动画进度应该是总进度的三分之一,第一段应该就是0至1/3  第二段就是1/3至1/32 , 第三段就是1/32至1;

3、利用path.lineTo进行两点的连接以上几步转换成代码如下所示:

if (progress < 0.33333333f) { //第一段
float progressInLine = progress / 0.33333333f;
path.lineTo(x + Math.abs(x1 - x) * progressInLine, y + Math.abs(y1 - y) * progressInLine);
} else if (progress < 0.66666666f) {//第二段
float progressInLine = (progress - 0.33333333f) / 0.33333333f;
path.lineTo(x1, y1);
path.lineTo(x1 - Math.abs(x2 - x1) * progressInLine, y1 + Math.abs(y2 - y1) * progressInLine);
} else if (progress < 1) {//第三段
float progressInLine = (progress - 0.66666666f) / 0.33333333f;
path.lineTo(x1, y1);
path.lineTo(x2, y2);
path.lineTo(x2, y2 - Math.abs(y2 - y0) * progressInLine);
} else if (progress >= 1f) {
path.lineTo(x1, y1);
path.lineTo(x2, y2);
path.lineTo(x0, y0);
path.close();
}

但是,这样计算之后,你会发现,这个动画是有瑕疵的,见下图:

d720ab202b2c7315a37ac8f604508170.gif

就是在画线的时候,你会发现在角的地方会突出去一点,这是为什么呢?还记得咱们之前设置过一个圆弧半径10吗,就是这个小小的弧度导致了我们要连接坐标的实际距离其实是缩短了的,具体可以观察以下两个图(弧度弄得有点夸张,这样比较明显):

a25410b518ac4739f643034954c418c3.png

很明显两个三角形的边长是不一样的,所以,我们不能单纯的只是连接这个三角形的顶点,还需要计算出圆弧导致的偏移量,这时候又要搬出三角函数了,已知圆弧半径,我们可以利用余弦和正弦分别求出x和y的偏移值(具体公式可以百度),代码如下:

float offsetX= (float) (10* Math.sin(Math.PI * 60 / 180));
float offsetY = (float) (10* Math.cos(Math.PI * 60 / 180));

那么刚才的代码步骤就会变成:

if (progress < 0.33333333f) { //第一段
float fromX1 = x1 - offsetX;
float fromY1 = y1 - offsetY;
float progressInLine = progress / 0.33333333f;
path.lineTo(x + Math.abs(fromX1 - x) * progressInLine, y + Math.abs(fromY1 - y) * progressInLine);
} else if (progress < 0.66666666f) {//第二段
float progressInLine = (progress - 0.33333333f) / 0.33333333f;
float fromX1 = x1 - offsetX;
float fromY1 = y1 + offsetY;
float desX2 = x2 + offsetX;
float desY2 = y2 - offsetY;
path.lineTo(x1, y1);
path.lineTo(fromX1 - Math.abs(desX2 - fromX1) * progressInLine, fromY1 + Math.abs(desY2 - fromY1) * progressInLine);
} else if (progress <1) {//第三段
float progressInLine = (progress - 0.66666666f) / 0.33333333f;
float fromY2 = y2 - offsetY;
float desY0 = y0 + offsetY;
path.lineTo(x1, y1);
path.lineTo(x2, y2);
path.lineTo(x2, fromY2 - Math.abs(fromY2 - desY0) * progressInLine);
} else if (progress >= 1f) {
......
}

第三步:在黑色走线时使三角形转起来

首先我们要确认旋转的角度,由于等边三角形的三个角完全相同,旋转时,只要使下一个角对准原角,就能重合,所以一圈360度除以3,就得到120度.也就是说旋转120度就可以跟原三角形完全重合,但是我们的效果是 a角转到b角 所以 我们直接除以3 也就是旋转40度即可到达我们的预想效果了,转换成代码如下:

private void computePath(float progress) {
    float progressInRotate = progress;
    mDegree = 40 * progressInRotate;//黑线旋转使用
......
 }

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //移动坐标系
    canvas.translate(getMeasuredWidth() / 2, getMeasuredHeight() / 2);
    //旋转  注意顺序
    canvas.rotate(mDegree);
    canvas.drawPath(mPath, mPaint);
}

到此 这个旋转变色的圆角三角形就绘制完成了,虽然效果看上去比较简单,但是实际开发起来还是有一些细节需要注意的,仅此给大家提供一个实现类似效果的小思路,也希望大家可以在评论区提出自己更好的想法,如果需要源码,可以联系我~

e2615f0481c2b2debdcae3a13f2584ee.jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1629597.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

利用ENVI SPEAR工具和WV-2卫星影像数据量测水深

ENVI的SPEAR工具集&#xff08;(Spectral Processing Exploitation and Analysis Resource)&#xff09;是将很多的遥感图像处理过程集成为流程化的操作方式&#xff0c;使得遥感图像处理知识相对薄弱的非专业人员也能利用流程化的工具进行图像处理&#xff0c;图像处理速度也有…

自动驾驶框架 UniAD环境部署

感谢大佬们的开源工作 UniAD-github地址-YYDS更多bev算法部署参考如果您觉得本帖对您有帮助&#xff0c;感谢您一键三连支持一波^_^ 统一自动驾驶框架 (UniAD) &#xff0c;第一个将全栈驾驶任务整合到一个深度神经网络中的框架&#xff0c;并可以发挥每个子任务以及各个模块的…

[每周一更]-(第94期):认识英伟达显卡

英伟达显卡&#xff1a;引领图形计算的领先者&#xff0c;显卡也常称为GPU&#xff08;图形处理器 Graphics processing unit&#xff09;&#xff0c;是一种专门在个人电脑、工作站、游戏机和一些移动设备&#xff08;如平板电脑、智能手机等&#xff09;上执行绘图运算工作的…

【STM32+HAL】三轴按键PS2摇杆

一、准备工作&#xff1a; 有关CUBEMX的初始化配置&#xff0c;参见我的另一篇blog&#xff1a;【STM32HAL】CUBEMX初始化配置 有关定时器触发ADC模式配置&#xff0c;详见【STM32HAL】ADC采集波形实现 二、所用工具&#xff1a; 1、芯片&#xff1a; STM32F407VET6 2、CUBE…

大数据面试题 —— Spark数据倾斜及其解决方案

目录 1 调优概述2 数据倾斜发生时的现象3 数据倾斜发生的原理4 如何定位导致数据倾斜的代码4.1 某个 task 执行特别慢的情况4.2 某个 task 莫名其妙内存溢出的情况5 查看导致数据倾斜的 key 的数据分布情况6 数据倾斜的解决方案6.1 使用 Hive ETL 预处理数据6.2 过滤少数导致倾…

Ubuntu中的 Everything 搜索软件 ==> fsearch

本文所使用的 Ubuntu 系统版本是 Ubuntu 22.04 ! 在 Windows 中&#xff0c;我经常使用 Everything 来进行文件搜索&#xff0c;搜索效率比 Windows 自带的高出千百倍。 那么在 Ubuntu 系统中&#xff0c;有没有类似的软件呢&#xff1f;那必须有&#xff0c;它就是 FSearch 。…

JavaScript-Web API基本认知-什么是DOM和BOM

基本认知 var、let、const选用Web API作用和分类什么是DOM什么是DOM树DOM对象&#xff08;重要&#xff09;什么是BOM var、let、const选用 var or let or const &#xff1f; 首先var 先排除&#xff0c;老派写法&#xff0c;问题很多&#xff0c;可以淘汰掉… let or const …

Docker 的数据管理 与 Docker 镜像的创建

目录 一、Docker 的数据管理 1.1.数据卷 1.2.数据卷容器 1.3.容器互联&#xff08;使用centos镜像&#xff09; 二、Docker 镜像的创建 2.1.基于现有镜像创建 2.2.基于本地模板创建 2.3.基于Dockerfile创建 2.3.1联合文件系统&#xff08;UnionFs&#xff09; 2.3.2…

多线程模型浅谈

优质博文&#xff1a;IT-BLOG-CN 笔者近期在维护的项目中发现了一些比较随机的问题&#xff0c;时有时无的&#xff0c;排查之后发现是使用多线程导致的&#xff0c;恍然之下研究了下多线程的底层模型相关知识&#xff0c;现不大家简要分享下。 一个程序进程可包含多个线程&am…

全志ARM-超声波测距

超声波测距模块是用来测量距离的一种产品&#xff0c;通过发送和收超声波&#xff0c;利用时间差和声音传播速度&#xff0c; 计算出模块到前方障碍物的距离 1.测距原理&#xff1a; 给Trig端口至少10us的高电平发送声波&#xff0c;Echo信号&#xff0c;由低电平跳转到高电平…

【语音识别】搭建本地的语音转文字系统:FunASR(离线不联网即可使用)

参考自&#xff1a; 参考配置&#xff1a;FunASR/runtime/docs/SDK_advanced_guide_offline_zh.md at main alibaba-damo-academy/FunASR (github.com)参考配置&#xff1a;FunASR/runtime/quick_start_zh.md at 861147c7308b91068ffa02724fdf74ee623a909e alibaba-damo-aca…

绘唐科技AIGC怎么激活

绘唐科技AIGC怎么激活绘唐科技AIGC怎么激活绘唐科技AIGC怎么激活绘唐科技AIGC怎么激活 这里激活免费3天体验 Docshttps://qvfbz6lhqnd.feishu.cn/wiki/D3YLwmIzmivZ7BkDij6coVcbn7W

【Django】初识Django快速上手

Django简介 Django是一个高级的、开源的Python Web框架&#xff0c;旨在快速、高效地开发高质量的Web应用程序 https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Django/Introduction 安装Django pip install Django如果要知道安装的Django的版本&#xff0c;可…

机器学习:深入解析SVM的核心概念(问题与解答篇)【一、间隔与支持向量】

直接阅读原始论文可能有点难和复杂&#xff0c;所以导师直接推荐我阅读周志华的《西瓜书》&#xff01;&#xff01;然后仔细阅读其中的第六章&#xff1a;支持向量机 间隔与支持向量 问题一&#xff1a;什么叫法向量&#xff1f;为什么是叫法向量 在这个线性方程中&#xff…

新科技辅助器具赋能视障生活:让盲人出行融入日常

随着科技日新月异的发展&#xff0c;一款名为蝙蝠避障专为改善盲人日常生活的盲人日常生活辅助器具应运而生&#xff0c;它通过巧妙整合实时避障与拍照识别功能&#xff0c;成功改变了盲人朋友们的生活格局&#xff0c;为他们提供了更为便捷、高效的生活体验。 这款非同…

数据结构——二叉树的顺序存储(堆)(C++实现)

数据结构——二叉树的顺序存储&#xff08;堆&#xff09;&#xff08;C实现&#xff09; 二叉树可以顺序存储的前提堆的定义堆的分类大根堆小根堆 整体结构把握两种调整算法向上调整算法递归版本 非递归版本向下调整算法非递归版本 向上调整算法和向下调整算法的比较 我们接着…

1. 房屋租赁管理系统(Java项目 springboot/vue)

1.此系统的受众 1.1 在校学习的学生&#xff0c;可用于日常学习使用或是毕业设计使用 1.2 毕业一到两年的开发人员&#xff0c;用于锻炼自己的独立功能模块设计能力&#xff0c;增强代码编写能力。 1.3 亦可以部署为商化项目使用。 2. 技术栈 jdk8springbootvue2mysq5.7&8…

论文阅读之MMSD2.0: Towards a Reliable Multi-modal Sarcasm Detection System

文章目录 论文地址主要内容主要贡献模型图技术细节数据集改进多视图CLIP框架文本视图图像视图图像-文本交互视图 实验结果 论文地址 https://arxiv.org/pdf/2307.07135 主要内容 这篇文章介绍了一个名为MMSD2.0的多模态讽刺检测系统的构建&#xff0c;旨在提高现有讽刺检测系…

Amazon云计算AWS之[5]关系数据库服务RDS

文章目录 RDS的基本原理主从备份和下读写分离 RDS的使用 RDS的基本原理 Amazon RDS(Amazon Relational Database Service) 将MySQL数据库移植到集群中&#xff0c;在一定的范围内解决了关系数据库的可扩展性问题。 MySQL集群方式采用Share-Nothing架构。每台数据库服务器都是…

JavaEE——介绍 HTTPServlet 三部分使用与 cookie 和 session 的阐述

文章目录 一、HTTPServlet介绍其中的关键 三个方法 二、HTTPServletRequest(处理请求)1.分块介绍方法作用get 为前缀的方法字段中 含有 getParameter 字段 的方法(前后端交互)&#xff1a;字段中 含有 getHeader 字段 的方法&#xff1a; 2.解释前后端的交互过程3.使用 json 格…