当前位置: 首页 > news >正文

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

【深度学习】深度学习之巅:在 CentOS 7 上打造完美Python 3.10 与 PyTorch 2.3.0 环境

【深度学习】深度学习之巅&#xff1a;在 CentOS 7 上打造完美Python 3.10 与 PyTorch 2.3.0 环境 大家好 我是寸铁&#x1f44a; 总结了一篇【深度学习】深度学习之巅&#xff1a;在 CentOS 7 上打造完美Python 3.10 与 PyTorch 2.3.0 环境✨ 喜欢的小伙伴可以点点关注 &#…...

在docker容器中使用gdb调试python3.11的进程

gdb调试python进程的前提条件 安装python及python调试信息安装gdb工具安装python-gdb.py扩展 安装过程 我们使用docker来安装以上内容&#xff0c;Dockerfile文件内容如下&#xff1a; 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解决

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

对称加密系统解析

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

初识 java 2

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

云端狂飙:Django项目部署与性能优化的极速之旅

Hello&#xff0c;我是阿佑&#xff0c;这次阿佑将手把手带你亲自踏上Django项目从单机到云端的全过程&#xff0c;以及如何通过Docker实现项目的无缝迁移和扩展。不仅详细介绍了Docker的基本概念和操作&#xff0c;还深入探讨Docker Compose、Swarm和Kubernetes等高级工具的使…...

GDPU JavaWeb 大结局篇(持续更新中)

GDPUJavaWeb程序设计复习&#xff0c;习题集&#xff0c;重点知识总结&#xff0c;一篇就够了。 实验复习 JavaWeb代码复习&#xff0c;在专栏也可查阅。 课后巩固习题 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 实例分割&#xff1a;从训练到部署 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部署参考文…...

【设计模式】行为型设计模式之 策略模式学习实践

介绍 策略模式&#xff08;Strategy&#xff09;&#xff0c;就是⼀个问题有多种解决⽅案&#xff0c;选择其中的⼀种使⽤&#xff0c;这种情况下我们 使⽤策略模式来实现灵活地选择&#xff0c;也能够⽅便地增加新的解决⽅案。⽐如做数学题&#xff0c;⼀个问题的 解法可能有…...

lua中大数相乘的问题

math.maxinteger * 2 --> -2 原因&#xff1a;math.maxinteger的二进制 &#xff1a; 0111111111111111111111111111111111111111111111111111111111111111 往左移位&#xff0c;最右加一个0&#xff0c;是 1111111111111111111111111111111111111111111111111111111111111…...

第一个SpringBoot项目

目录 &#x1f4ad;1、新建New Project IDEA2023版本创建Sping项目只能勾选17和21&#xff0c;却无法使用Java8&#xff1f;&#x1f31f; 2、下载JDK 17&#x1f31f; &#x1f4ad;2、项目创建成功界面 1、目录 &#x1f31f; 2、pom文件&#x1f31f; &#x1f4ad;3、…...

Android 10.0 Launcher修改density禁止布局改变功能实现

1.前言 在10.0的系统rom定制化开发中,在关于Launcher3的定制化功能中,在有些功能需要要求改变系统原有的density屏幕密度, 这样就会造成Launcher3的布局变化,所以就不符合要求,接下来就来看下如何禁止改变density造成Launcher3布局功能 改变的实现 2.Launcher修改densit…...

CAN协议简介

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

(二)JSX基础

什么是JSX 概念&#xff1a;JSX是JavaScript和XML&#xff08;HTML&#xff09;的缩写&#xff0c;表示在JS代码中编写HTML模版结构&#xff0c;它是React中编写UI模板的方式。 优势&#xff1a;1.HTML的声明式模版方法&#xff1b;2.JS的可编程能力 JSX的本质 JSX并不是标准…...

GB 38469-2019 船舶涂料中有害物质限量检测

船舶涂料是指涂于船舶各部位&#xff0c;能防止海水、海洋大气腐蚀和海生物附着及满足船舶特种要求的各种涂料的统称。 GB 38469-2019船舶涂料中有害物质限量检测项目&#xff1a; 测试指标 测试方法 挥发性有机化合物VOC GB 30981 甲苯 GB 24408 苯 GB 30981 甲醇 G…...

汇编:数组-寻址取数据

比例因子寻址&#xff1a; 比例因子寻址&#xff08;也称为比例缩放索引寻址或基址加变址加比例因子寻址&#xff09;是一种复杂的内存寻址方式&#xff0c;常用于数组和指针操作。它允许通过一个基址寄存器、一个变址寄存器和一个比例因子来计算内存地址。 语法 比例因子寻…...

全面掌握ESP WiFi中继器DHCP服务器配置:高效管理嵌入式设备网络

全面掌握ESP WiFi中继器DHCP服务器配置&#xff1a;高效管理嵌入式设备网络 【免费下载链接】esp_wifi_repeater A full functional WiFi Repeater (correctly: a WiFi NAT Router) 项目地址: https://gitcode.com/gh_mirrors/es/esp_wifi_repeater ESP WiFi中继器是一款…...

CLAP Zero-Shot Audio Classification Dashboard部署教程:HTTPS反向代理配置(Nginx)保障生产环境访问安全

CLAP Zero-Shot Audio Classification Dashboard部署教程&#xff1a;HTTPS反向代理配置&#xff08;Nginx&#xff09;保障生产环境访问安全 1. 为什么需要HTTPS反向代理 当你成功部署了CLAP音频分类应用后&#xff0c;可能会发现直接通过HTTP访问存在一些安全问题。在生产环…...

DAMO-YOLO实战:用AI视觉系统做内容安全审核与统计

DAMO-YOLO实战&#xff1a;用AI视觉系统做内容安全审核与统计 1. 引言&#xff1a;当AI视觉遇见内容安全 在数字内容爆炸式增长的今天&#xff0c;如何高效地进行内容审核成为许多平台面临的挑战。传统人工审核不仅效率低下&#xff0c;而且容易因疲劳导致误判。本文将介绍如…...

次元画室快速部署教程:手把手解决网络权限与配置问题

次元画室快速部署教程&#xff1a;手把手解决网络权限与配置问题 1. 环境准备与快速部署 1.1 系统要求检查 在开始部署次元画室前&#xff0c;请确保您的系统满足以下最低要求&#xff1a; 操作系统&#xff1a;Ubuntu 20.04/22.04 LTS 或 CentOS 8/9&#xff08;推荐使用Ub…...

易语言实现阶乘与组合数计算

是的&#xff0c;我听说过易语言&#xff0c;它是一款面向中文使用者的编程语言&#xff0c;以其直观的中文语法和图形化界面开发能力而著称。 一、 数学概念解析 在深入编程实现前&#xff0c;我们先明确两个基础的数学概念。 1. 阶乘 阶乘 是所有小于及等于该数的正整数的…...

大数据在电力行业的应用案例解析 -【电力技术】(一)—— 基于电力大客户运营的大数据落地拓展

目录 一、电力大客户运营场景与大数据价值 二、大数据平台架构(大客户运营专用) 三、落地应用案例一:电力大客户价值分群与精准画像 1. 业务目标 2. 数据宽表(工程常用) 3. 核心算法:K-Means 用户分群(简化示例代码) 4. 应用效果 四、落地应用案例二:大客户负荷…...

欧拉Euler~21.10系统下OpenSSH 9.0升级与安全加固实战指南

1. 环境准备&#xff1a;从零搭建OpenSSH 9.0升级基础 在欧拉Euler~21.10系统上升级OpenSSH&#xff0c;就像给老房子换新门窗——既要保证新功能正常使用&#xff0c;又不能破坏原有结构。我最近刚在测试环境完成这套操作&#xff0c;整个过程踩过几个坑&#xff0c;这里把完整…...

RK3588开发板跑YOLOv5视频流demo,遇到Segmentation fault别慌!保姆级core文件生成与调试指南

RK3588开发板YOLOv5视频流推理崩溃排查&#xff1a;从Segmentation fault到精准调试全攻略 当你在RK3588开发板上满心期待地运行YOLOv5视频流推理demo时&#xff0c;屏幕上突然闪现的"Segmentation fault (core dumped)"就像一盆冷水浇灭了热情。这种崩溃提示信息量极…...

OpenClaw技能扩展指南:为百川2-13B添加公众号发布模块

OpenClaw技能扩展指南&#xff1a;为百川2-13B添加公众号发布模块 1. 为什么需要公众号发布技能 上周我正忙着准备一篇技术分享文章&#xff0c;突然意识到一个痛点&#xff1a;每次写完Markdown文档后&#xff0c;手动复制到公众号编辑器、调整格式、上传封面、设置摘要的过…...

告别标注烦恼:用DINOv2自监督模型,在Intel Image数据集上3个epoch实现93%准确率

零标注成本实战&#xff1a;DINOv2自监督模型在Intel Image数据集上的高效迁移方案 当我在实验室第一次尝试用传统方法训练一个图像分类模型时&#xff0c;面对数千张需要手动标注的图片&#xff0c;几乎要放弃这个课题。直到发现了自监督学习这个宝藏领域——特别是DINOv2这样…...