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

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动态设置的):

@Overrideprotected 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.mzph.cn/web/4205.shtml

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

相关文章

错误代码126:加载d3dcompiler_43.dll失败,分享多种解决方法

在正常使用电脑的过程中&#xff0c;当我尝试启动并运行一款心仪的游戏时&#xff0c;系统却突然弹出一个令人困扰的错误提示“错误代码126:加载d3dcompiler_43.dll失败”&#xff0c;它会导致游戏无法正常运行。为了解决这个问题&#xff0c;我经过多次尝试和总结&#xff0c;…

VMware ESXi虚拟机备份的方法和步骤

关于虚拟机备份 VMware ESXi 是 VMware vSphere 企业虚拟化套件的核心组件。在版本4.1之前&#xff0c;它一直被称为ESX。ESXi是一种裸机管理程序&#xff0c;直接安装在物理服务器上&#xff0c;它提供对底层资源的直接访问和控制&#xff0c;允许您在单个物理主机上创建和运…

网鼎杯-2018-unfinish解题方法

BUUCTF在线评测这里可以解题无需搭建 进入这题后我们会得到这样一个界面 这里我们会发现登录不进去也没有注册界面以及源码&#xff0c;这里我们需要获取它其他的一些界面找注入机会&#xff0c;这里有两种方式找其他界面&#xff0c;一种是猜这里是登录界面那么必定就会有注册…

网络安全新挑战:通用人工智能(AGI)等级保护指南

通用人工智能&#xff08;AGI&#xff09;的发展现状及趋势 随着2023年大语言模型应用的划时代突破&#xff0c;以ChatGPT为杰出代表的此类技术犹如一股洪流&#xff0c;彻底颠覆了人类与机器智能交互的疆界&#xff0c;引领通用人工智能&#xff08;AGI&#xff09;步入一个崭…

[SWPUCTF-2022-新生赛]ez_sql

title:[SWPUCTF 2022 新生赛]ez_sql 审题 根据提示&#xff0c;POST传参 得到假的flag 判断类型 字符型注入 判断列数 发现空格和’or’被过滤 重新构造 nss-1/**/oorrder/**/by/**/4#发现为3个字段 采用联合注入union 爆库 发现union被过滤&#xff0c;双写union绕过 发…

【小迪安全2023】第58天:服务攻防-应用协议设备KibanaZabbix远控向日葵VNCTV

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

腾讯云邮件推送如何设置?群发邮件的技巧?

腾讯云邮件推送功能有哪些&#xff1f;怎么有效使用邮件推送&#xff1f; 腾讯云邮件推送以其稳定、高效的特点&#xff0c;受到了众多企业的青睐。那么&#xff0c;腾讯云邮件推送如何设置呢&#xff1f;又有哪些群发邮件的技巧呢&#xff1f;下面AokSend就来详细探讨一下。 …

C++ ─── 匿名对象+变量的创建顺序

目录 1. 匿名对象&#xff08;临时对象&#xff09; 2. 编译器的优化 3.变量的创建与销毁 1. 匿名对象&#xff08;临时对象&#xff09; 我们先来看有名对象的创建 Date d1; Date d2(2024,4,27);匿名对象的创建 Date(2024,56,1); 生成了一个匿名对象&#xff0c;执行完Da…

photoshop如何使用PS中的吸管工具吸取软件外部的颜色?

第一步&#xff0c;打开PS&#xff0c;随意新建一个画布&#xff0c;或打开一个图片。 第二步&#xff0c;将PS窗口缩小&#xff0c;和外部窗口叠加放置&#xff0c;以露出后面的其它页面。 第三步&#xff0c;选中吸管工具&#xff0c;在PS窗口内单击一点吸取颜色&#xff0c;…

1、Flink DataStreamAPI 概述(上)

一、DataStream API 1、概述 1&#xff09;Flink程序剖析 1.Flink程序组成 a&#xff09;Flink程序基本组成 获取一个执行环境&#xff08;execution environment&#xff09;&#xff1b;加载/创建初始数据&#xff1b;指定数据相关的转换&#xff1b;指定计算结果的存储…

SpringBoot 缓存

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 目录 一、缓存的作用二、SpringBoot启用缓存三…

STM32单片机通过ST-Link 烧录和调试

系列文章目录 STM32单片机系列专栏 C语言术语和结构总结专栏 文章目录 1. ST-LINK V2 2. 操作步骤 2.1 连接方式 2.2 驱动安装常规步骤 2.3 Keil中的设置 3. 调式仿真 4. 常见问题排查 1. ST-LINK V2 ST LINK v2下载器用于STM32单片机&#xff0c;可以下载程序、调试…

c++并查集

文章目录 前言一、并查集1、并查集原理2、并查集实现3、并查集应用1.省份数量2.等式方程的可满足性 前言 一、并查集 1、并查集原理 在一些应用问题中&#xff0c;需要将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个单元素集合&#xff0c;然后…

nvm基本使用

nvm基本使用 文章目录 nvm基本使用1.基本介绍2.下载地址3.常用指令 1.基本介绍 NVM是一个用于管理 Node.js 版本的工具。它允许您在同一台计算机上同时安装和管理多个 Node.js 版本&#xff0c;针对于不同的项目可能需要不同版本的 Node.js 运行环境。 NVM 主要功能&#xff…

24深圳杯AC题完整思路+可执行代码+参考论文!!!!

比赛题目的完整版思路可执行代码数据参考论文都会在第一时间更新上传的&#xff0c;大家可以参考我往期的资料&#xff0c;所有的资料数据以及到最后更新的参考论文都是一次付费后续免费的。注意&#xff1a;&#xff08;建议先下单占坑&#xff0c;因为随着后续我们更新资料数…

Windows10系统安装IIS的步骤

在Windows 10上安装IIS的步骤如下&#xff1a;12 打开控制面板&#xff0c;选择“程序”或“程序和功能”。点击“启用或关闭Windows功能”。在列表中找到“Internet Information Services”&#xff0c;勾选该选项。根据需要勾选IIS的具体组件&#xff0c;如万维网服务、IIS可…

创新指南 | 2024年企业如何十步打造最佳的数字化营销策略组合

营销是一个动态且不断变化的领域。顶级的数字营销策略随着消费者和技术趋势的变化而变化。这就是为什么每个公司都需要一个经过良好规划并具有明确里程碑和目标的营销策略。一旦你有了正确的计划&#xff0c;你实现为业务设定的目标的可能性就会大大增加。这意味着&#xff0c;…

面试经典150题——求根节点到叶节点数字之和

​ 1. 题目描述 2. 题目分析与解析 2.1 思路一——DFS 理解问题&#xff1a; 首先要理解题目的要求&#xff0c;即对于给定的二叉树&#xff0c;我们需要找出从根节点到所有叶子节点的所有路径&#xff0c;然后将每一条路径上的数字组成一个整数&#xff0c;最后求出这些整数…

手把手教数据结构与算法:栈的应用(平衡符号和简单计算器)

栈 基本概念 栈的定义 栈&#xff08;Stack&#xff09;&#xff1a;是只允许在一端进行插入或删除的线性表。首先栈是一种线性表&#xff0c;但限定这种线性表只能在某一端进行插入和删除操作。 栈顶&#xff08;Top&#xff09;&#xff1a;线性表允许进行插入删除的那一端…

Docker常用命令(镜像、容器)

一、镜像 1.1 存出镜像 1.2 载入镜像 1.3 上传镜像 二、容器 2.1 容器创建 2.2 查看容器的运行状态 ​2.3 启动容器 2.4 创建并启动容器 2.5 在后台持续运行 docker run 创建的容器 2.6 终止容器运行 2.7 容器的进入 ​2.8把宿主机的文件传入到容器内部 2.9 从容器…