使用贝塞尔曲线实现一个iOS时间轴
UI效果
实现的思路
就是通过贝塞尔曲线画出时间轴的圆环的路径,然后
使用CAShaper来渲染UI,再通过
animation.beginTime = [cilrclLayer convertTime:CACurrentMediaTime() fromLayer:nil] + circleTimeOffset 来设置每个圆环的动画开始时间,
,每个地方都是有两层layer的,一层是底部灰色样式的(即没有到达时候的颜色)一层是到达得阶段的颜色,
到达的layer在上面,如果要开启动画,我们就得先将
到达的layer隐藏掉,然后开始动画的时候,将对应的那一条展示,开启动画的时候将hidden置为NO,这时候,其实layer是展示不了的(不会整个展示),因为我们添加了strokeEnd的动画,他会随者动画的进行而逐渐展示,所以我们在动画代理方法animationDidStart中,将layer 设置为可见,(如果没有设置动画代理,也可以在添加动画的时候设置为可见)
代码
//
// LBTimeView.m
// LBTimeLine
//
// Created by mac on 2024/6/9.
//#import "LBTimeView.h"
#define ScreenWidth [UIScreen mainScreen].bounds.size.width
#define RGB(r, g, b) [UIColor colorWithRed:(r)/255.f green:(g)/255.f blue:(b)/255.f alpha:1.f]
#define SizeScale (([UIScreen mainScreen].bounds.size.width > 320) ? [UIScreen mainScreen].bounds.size.width/320 : 1)const float BETTWEEN_LABEL_OFFSET = 20;
const float LINE_WIDTH = 1.9;
const float CIRCLE_RADIUS = 3.7;
const float INITIAL_PROGRESS_CONTAINER_WIDTH = 20.0;
const float PROGRESS_VIEW_CONTAINER_LEFT = 51.0;
const float VIEW_WIDTH = 225.0;@interface LBTimeView ()
{CGPoint lastpoint;NSMutableArray *layers;NSMutableArray *circleLayers;int layerCounter;int circleCounter;CGFloat timeOffset;CGFloat leftWidth;CGFloat rightWidth;CGFloat viewWidth;
}@end@implementation LBTimeView-(id)initWithFrame:(CGRect)frame sum:(NSInteger)sum current:(NSInteger)current{self = [super initWithFrame:frame];if (self) {self.frame = frame;[self configureTimeLineWithNum:sum andCurrentNum:current];}return self;// Do any additional setup after loading the view, typically from a nib.
}- (void)configureTimeLineWithNum:(NSInteger)sum andCurrentNum:(NSInteger)currentStatus {// NSInteger currentStatus = 3;circleLayers = [[NSMutableArray alloc] init];layers = [[NSMutableArray alloc] init];CGFloat U = (ScreenWidth - 80- sum+1)/(sum - 1);CGFloat betweenLineOffset = 0;//CGFloat totlaHeight = 8;// CGFloat yCenter = - 48 + (ScreenWidth - 248)/2;CGFloat yCenter = 40;// CGFloat xCenter;UIColor *strokeColor;CGPoint toPoint;CGPoint fromPoint;int i = 0;for (int j = 0;j < sum;j ++) {//configure circlestrokeColor = i < currentStatus ? RGB(224, 0, 30) : RGB(233, 233, 233);UIBezierPath *circle = [UIBezierPath bezierPath];[self configureBezierCircle:circle withCenterY:yCenter];CAShapeLayer *circleLayer = [self getLayerWithCircle:circle andStrokeColor:strokeColor];//[circleLayers addObject:circleLayer];//add static background gray circleCAShapeLayer *grayStaticCircleLayer = [self getLayerWithCircle:circle andStrokeColor:RGB(233, 233, 233)];[self.layer addSublayer:grayStaticCircleLayer];[self.layer addSublayer:circleLayer];//configure lineif (i > 0) {fromPoint = lastpoint;toPoint = CGPointMake(yCenter - CIRCLE_RADIUS,60*SizeScale);lastpoint = CGPointMake( yCenter + CIRCLE_RADIUS+ 1,60*SizeScale);UIBezierPath *line = [self getLineWithStartPoint:fromPoint endPoint:toPoint];CAShapeLayer *lineLayer = [self getLayerWithLine:line andStrokeColor:strokeColor];// CAShapeLayer *lineLayer2 = [self getLayerWithLine:line andStrokeColor:strokeColor];[layers addObject:lineLayer];//add static background gray lineCAShapeLayer *grayStaticLineLayer = [self getLayerWithLine:line andStrokeColor:RGB(233, 233, 233)];[self.layer addSublayer:grayStaticLineLayer];[self.layer addSublayer:lineLayer];} else {lastpoint = CGPointMake( yCenter + CIRCLE_RADIUS+1,60*SizeScale);}betweenLineOffset = BETTWEEN_LABEL_OFFSET;yCenter += U;i++;}
}- (CAShapeLayer *)getLayerWithLine:(UIBezierPath *)line andStrokeColor:(UIColor *)strokeColor {CAShapeLayer *lineLayer = [CAShapeLayer layer];lineLayer.path = line.CGPath;lineLayer.strokeColor = strokeColor.CGColor;lineLayer.fillColor = nil;lineLayer.lineWidth = 1.4;return lineLayer;
}- (UIBezierPath *)getLineWithStartPoint:(CGPoint)start endPoint:(CGPoint)end {UIBezierPath *line = [UIBezierPath bezierPath];[line moveToPoint:start];[line addLineToPoint:end];return line;
}- (CAShapeLayer *)getLayerWithCircle:(UIBezierPath *)circle andStrokeColor:(UIColor *)strokeColor {CAShapeLayer *circleLayer = [CAShapeLayer layer];circleLayer.path = circle.CGPath;circleLayer.strokeColor = strokeColor.CGColor;circleLayer.fillColor = nil;circleLayer.lineWidth = LINE_WIDTH;circleLayer.lineJoin = kCALineJoinBevel;return circleLayer;
}- (void)configureBezierCircle:(UIBezierPath *)circle withCenterY:(CGFloat)centerY {[circle addArcWithCenter:CGPointMake( centerY,60*SizeScale)radius:CIRCLE_RADIUSstartAngle:M_PI_2endAngle:-M_PI_2clockwise:YES];[circle addArcWithCenter:CGPointMake(centerY,60*SizeScale)radius:CIRCLE_RADIUSstartAngle:-M_PI_2endAngle:M_PI_2clockwise:YES];
}- (void)startAnimatingWithCurrentIndex:(NSInteger)index
{for (CAShapeLayer *layer in layers) {layer.hidden = YES;}[self startAnimatingLayers:circleLayers forStatus:index];
}- (void)startAnimatingLayers:(NSArray *)layersToAnimate forStatus:(NSInteger)currentStatus {float circleTimeOffset = 1;circleCounter = 0;int i = 1;//add with animationfor (CAShapeLayer *cilrclLayer in layersToAnimate) {[self.layer addSublayer:cilrclLayer];CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];animation.duration = 0.2;animation.beginTime = [cilrclLayer convertTime:CACurrentMediaTime() fromLayer:nil] + circleTimeOffset;animation.fromValue = [NSNumber numberWithFloat:0.0f];animation.toValue = [NSNumber numberWithFloat:1.0f];animation.fillMode = kCAFillModeForwards;animation.delegate =(id <CAAnimationDelegate>) self;circleTimeOffset += .4;[cilrclLayer setHidden:YES];[cilrclLayer addAnimation:animation forKey:@"strokeCircleAnimation"];if (self.lastBlink && i == currentStatus && i != [layersToAnimate count]) {CABasicAnimation *strokeAnim = [CABasicAnimation animationWithKeyPath:@"strokeColor"];strokeAnim.fromValue = (id) [UIColor orangeColor].CGColor;strokeAnim.toValue = (id) [UIColor clearColor].CGColor;strokeAnim.duration = 1.0;strokeAnim.repeatCount = HUGE_VAL;strokeAnim.autoreverses = NO;[cilrclLayer addAnimation:strokeAnim forKey:@"animateStrokeColor"];}i++;}
}- (void)animationDidStart:(CAAnimation *)anim {if (circleCounter < circleLayers.count) {if (anim == [circleLayers[circleCounter] animationForKey:@"strokeCircleAnimation"]) {[circleLayers[circleCounter] setHidden:NO];circleCounter++;}}
}- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {if (layerCounter >= layers.count) {return;}CAShapeLayer *lineLayer = layers[layerCounter];CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];animation.duration = 0.200;animation.fromValue = [NSNumber numberWithFloat:0.0f];animation.toValue = [NSNumber numberWithFloat:1.0f];animation.fillMode = kCAFillModeForwards;lineLayer.hidden = NO;[self.layer addSublayer:lineLayer];[lineLayer addAnimation:animation forKey:@"strokeEndAnimation"];layerCounter++;
}@end
如果对您有帮助,欢迎给一个star
demo
相关文章:

使用贝塞尔曲线实现一个iOS时间轴
UI效果 实现的思路 就是通过贝塞尔曲线画出时间轴的圆环的路径,然后 使用CAShaper来渲染UI,再通过 animation.beginTime [cilrclLayer convertTime:CACurrentMediaTime() fromLayer:nil] circleTimeOffset 来设置每个圆环的动画开始时间, …...

【深度学习】深度学习之巅:在 CentOS 7 上打造完美Python 3.10 与 PyTorch 2.3.0 环境
【深度学习】深度学习之巅:在 CentOS 7 上打造完美Python 3.10 与 PyTorch 2.3.0 环境 大家好 我是寸铁👊 总结了一篇【深度学习】深度学习之巅:在 CentOS 7 上打造完美Python 3.10 与 PyTorch 2.3.0 环境✨ 喜欢的小伙伴可以点点关注 &#…...
在docker容器中使用gdb调试python3.11的进程
gdb调试python进程的前提条件 安装python及python调试信息安装gdb工具安装python-gdb.py扩展 安装过程 我们使用docker来安装以上内容,Dockerfile文件内容如下: FROM docker.io/centos:7.4.1708# 安装依赖 RUN yum install -y -q epel-release &…...
堆排序要点和难点以及具体案例应用
堆排序(Heap Sort)是一种基于堆数据结构的排序算法。下面我将以分点表示和归纳的方式,结合相关数字和信息,详细描述堆排序的PTA(Programming and Testing Approach,编程与测试方法)。 1. 堆排序原理 堆排序是一种树形选择排序,利用了完全二叉树的性质,通过构建最大堆…...

pyspark中使用mysql jdbc报错java.lang.ClassNotFoundException: com.mysql.jdbc.Driver解决
报错信息: py4j.protocol.Py4JJavaError: An error occurred while calling o33.load. : java.lang.ClassNotFoundException: com.mysql.jdbc.Driver 我的解决方法: 这个报错就是提示你找不到jar包,所以你需要去下载一个和你mysql版本匹配的j…...

对称加密系统解析
目录 1.概述 2. 对称密码类型 3. 对称加密优缺点 4. 对称加密算法 4.1 DES 4.2 3DES 4.3 AES 4.4 SM1 4.5 SM4 1.概述 对称加密,是指在加密和解密时使用同一秘钥的方式。秘钥的传送和保存的保护非常重要,务必不要让秘…...

初识 java 2
1. idea 的调试 1. 点击鼠标左键设置断点 2.运行到断点处 点击 或点击鼠标右键,再点击 使代码运行到断点处,得到 2. 输出到控制台 System.out.println(value);//输出指定的内容,并换行 value 要打印的内容System.out.print(value);…...

云端狂飙:Django项目部署与性能优化的极速之旅
Hello,我是阿佑,这次阿佑将手把手带你亲自踏上Django项目从单机到云端的全过程,以及如何通过Docker实现项目的无缝迁移和扩展。不仅详细介绍了Docker的基本概念和操作,还深入探讨Docker Compose、Swarm和Kubernetes等高级工具的使…...
GDPU JavaWeb 大结局篇(持续更新中)
GDPUJavaWeb程序设计复习,习题集,重点知识总结,一篇就够了。 实验复习 JavaWeb代码复习,在专栏也可查阅。 课后巩固习题 1 【单选题】下列说法正确的是( D ) A、在B/S结构中,结果应用软件发生了改变,就必须通知所有的客户端重新…...

Linux系统信息的查看
目录 前言一、系统环境二、查看系统IP地址信息2.1 ifconfig命令2.2 ip address命令 三、查看系统端口信息3.1 nmap命令3.2 netstat命令 四、查看系统进程信息4.1 ps命令4.2 kill命令 五、查看系统监控信息5.1 top命令5.2 df命令iostat命令5.3 sar命令 总结 前言 本篇文章介绍查…...
LE Audio音频广播新功能Auracast介绍
LE Audio音频广播新功能Auracast介绍 /*! \copyright Copyright (c) 2019-2022 Qualcomm Technologies International, Ltd. All Rights Reserved. Qualcomm Technologies International, Ltd. Confidential and Proprietary. \file audio_sources.h \defgroup audio_so…...

一文学习yolov5 实例分割:从训练到部署
一文学习yolov5 实例分割:从训练到部署 1.模型介绍1.1 YOLOv5结构1.2 YOLOv5 推理时间 2.构建数据集2.1 使用labelme标注数据集2.2 生成coco格式label2.3 coco格式转yolo格式 3.训练3.1 整理数据集3.2 修改配置文件3.3 执行代码进行训练 4.使用OpenCV进行c部署参考文…...

【设计模式】行为型设计模式之 策略模式学习实践
介绍 策略模式(Strategy),就是⼀个问题有多种解决⽅案,选择其中的⼀种使⽤,这种情况下我们 使⽤策略模式来实现灵活地选择,也能够⽅便地增加新的解决⽅案。⽐如做数学题,⼀个问题的 解法可能有…...
lua中大数相乘的问题
math.maxinteger * 2 --> -2 原因:math.maxinteger的二进制 : 0111111111111111111111111111111111111111111111111111111111111111 往左移位,最右加一个0,是 1111111111111111111111111111111111111111111111111111111111111…...

第一个SpringBoot项目
目录 💭1、新建New Project IDEA2023版本创建Sping项目只能勾选17和21,却无法使用Java8?🌟 2、下载JDK 17🌟 💭2、项目创建成功界面 1、目录 🌟 2、pom文件🌟 💭3、…...
Android 10.0 Launcher修改density禁止布局改变功能实现
1.前言 在10.0的系统rom定制化开发中,在关于Launcher3的定制化功能中,在有些功能需要要求改变系统原有的density屏幕密度, 这样就会造成Launcher3的布局变化,所以就不符合要求,接下来就来看下如何禁止改变density造成Launcher3布局功能 改变的实现 2.Launcher修改densit…...

CAN协议简介
协议简介 can协议是一种用于控制网络的通信协议。它是一种基于广播的多主机总线网络协议,常用于工业自动化和控制领域。can协议具有高可靠性、实时性强和抗干扰能力强的特点,被广泛应用于汽车、机械、航空等领域。 can协议采用了先进的冲突检测和错误检测…...

(二)JSX基础
什么是JSX 概念:JSX是JavaScript和XML(HTML)的缩写,表示在JS代码中编写HTML模版结构,它是React中编写UI模板的方式。 优势:1.HTML的声明式模版方法;2.JS的可编程能力 JSX的本质 JSX并不是标准…...
GB 38469-2019 船舶涂料中有害物质限量检测
船舶涂料是指涂于船舶各部位,能防止海水、海洋大气腐蚀和海生物附着及满足船舶特种要求的各种涂料的统称。 GB 38469-2019船舶涂料中有害物质限量检测项目: 测试指标 测试方法 挥发性有机化合物VOC GB 30981 甲苯 GB 24408 苯 GB 30981 甲醇 G…...

汇编:数组-寻址取数据
比例因子寻址: 比例因子寻址(也称为比例缩放索引寻址或基址加变址加比例因子寻址)是一种复杂的内存寻址方式,常用于数组和指针操作。它允许通过一个基址寄存器、一个变址寄存器和一个比例因子来计算内存地址。 语法 比例因子寻…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...

AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...

Windows安装Miniconda
一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分: 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...