使用贝塞尔曲线实现一个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 circle
        strokeColor = 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 circle
        CAShapeLayer *grayStaticCircleLayer = [self getLayerWithCircle:circle andStrokeColor:RGB(233, 233, 233)];
        [self.layer addSublayer:grayStaticCircleLayer];
        [self.layer addSublayer:circleLayer];
        //configure line
        if (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 line
            CAShapeLayer *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_RADIUS
                  startAngle:M_PI_2
                    endAngle:-M_PI_2
                   clockwise:YES];
    [circle addArcWithCenter:CGPointMake(centerY,60*SizeScale)
                      radius:CIRCLE_RADIUS
                  startAngle:-M_PI_2
                    endAngle:M_PI_2
                   clockwise: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 animation
    for (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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Flutter Image源码分析

本文用于记录分析Imge图片加载流程源码分析学习笔记 切入点是Image.network,加载网络图片 构造方法会创建NetworkImage,加载图片的实现类,父类是ImageProvider 加载本地图片等等都是类似 下面进入_ImageState类 void resolveStreamForKey(ImageConfiguration configurat…

C# WPF入门学习主线篇(二十一)—— 静态资源和动态资源

C# WPF入门学习主线篇&#xff08;二十一&#xff09;—— 静态资源和动态资源 欢迎来到C# WPF入门学习系列的第二十一篇。在上一章中&#xff0c;我们介绍了WPF中的资源和样式。本篇文章将深入探讨静态资源&#xff08;StaticResource&#xff09;和动态资源&#xff08;Dynam…

【适配鸿蒙next】Flutter 新一代混合栈管理框架

前言 据最新消息显示&#xff0c;华为今年下半年将全面转向其自主平台HarmonyOS&#xff0c;放弃Android系统。 报道中提到&#xff0c;下一版HarmonyOS预计将随华为即将推出的Mate 70旗舰系列一起发布。 据悉&#xff0c;HarmonyOS Next 已经扩展到4000个应用程序&#xff0c;…

802.11漫游流程简单解析与笔记_Part1

最近在进行和802.11漫游有关的工作&#xff0c;需要对wpa_supplicant认证流程和漫游过程有更多的了解&#xff0c;所以通过阅读论文等方式&#xff0c;记录整理漫游相关知识。Part1将记录802.11漫游的基本流程、802.11R的基本流程、与认证和漫游都有关的三层秘钥基础。Part1将包…

代码随想录刷题笔记-哈希表篇

文章目录 242 有效的字母异位词(easy)力扣地址题目描述题目实例解题思路代码实现 383 赎金信(easy)力扣地址题目描述题目实例解题思路代码实现 49 字母异位词分组(mid)力扣地址题目描述题目实例解题思路代码实现 438 找到字符串中所有字母异位词(mid)力扣地址题目描述题目实例解…

MyBatis插件机制

MyBatis插件机制是该框架提供的一种灵活扩展方式&#xff0c;允许开发者在不修改框架源代码的情况下对MyBatis的功能进行定制和增强。这种机制主要通过拦截器&#xff08;Interceptor&#xff09;实现&#xff0c;使得开发者可以拦截和修改MyBatis在执行SQL语句过程中的行为。 …

社交创新:Facebook的技术与产品发展

在当今数字化时代&#xff0c;社交网络已经渗透到我们生活的方方面面&#xff0c;成为了人们日常交流、信息获取和社交互动的主要方式。而在这个众多社交平台中&#xff0c;Facebook作为其中的佼佼者&#xff0c;其技术与产品的发展历程也是一个社交创新的缩影。本文将探索Face…

IDEA 连接GitHub仓库并上传项目(同时解决SSH问题)

目录 1 确认自己电脑上已经安装好Git 2 添加GitHub账号 2.1 Setting -> 搜索GitHub-> ‘’ -> Log In with Token 2.2 点击Generate 去GitHub生成Token 2.3 勾选SSH后其他不变直接生成token 2.4 然后复制token添加登录账号即可 3 点击导航栏中VCS -> Create…

从年金理论到杠杆效应,再到财务报表与投资评估指标

一、解释普通年金终值和普通年金现值的概念。 普通年金终值&#xff1a;以利率为1%&#xff0c;每期收款100元&#xff0c;5期为例&#xff0c;普通年金终值的折算过程如图&#xff1a; 普通年金现值&#xff1a;以利率为1%&#xff0c;每期收款100元&#xff0c;5期为例&am…

AI大模型在健康睡眠监测中的深度融合与实践案例

文章目录 1. 应用方案2. 技术实现2.1 数据采集与预处理2.2 构建与训练模型2.3 个性化建议生成 3. 优化策略4. 应用示例&#xff1a;多模态数据融合与实时监测4.1 数据采集4.2 实时监测与反馈 5. 深入分析模型选择和优化5.1 LSTM模型的优势和优化策略5.2 CNN模型的优势和优化策略…

[ROS 系列学习教程] 建模与仿真 - ros_control 介绍

ROS 系列学习教程(总目录) 本文目录 一、ros_control 架构1.1 hardware_interface1.2 combined_robot_hw1.3 controller_interface1.4 controller_manager1.5 controller_manager_msgs1.6 joint_limits_interface1.7 transmission_interface1.8 realtime_tools 二、ros_control…

Linux | buildrootfs 添加mkfs.ext3/mkfs.ext4 支持

因个人需要&#xff0c;mkfs.ext3 但是项目中还没有这个命令 所以琢磨了半天 这里将其小记一下 在buildrootfsz中&#xff0c;需要将e2fsprogs 勾选上然后重新编译就好了 make menuconfig Target packages-> Filesystem and flash utilities-> e2fsprogs

CentOS安装Node.js以及JSDOM跳坑记

笔者在一台 CentOS 7.9 的服务器上使用常规的安装命令&#xff1a;sudo yum install node 来安装 Node.js&#xff0c;到最后系统提示&#xff1a; Error: Package: 2:nodejs-20.14.0-1nodesource.x86_64 (nodesource-nodejs) Requires: libstdc.so.6(GLIBCXX_3.4.20)(64bit) …

任务调度选择之PowerJob 和 Snail Job

背景 最近在选择一款任务调度产品&#xff0c;找了几款产品进行调研&#xff0c;我对产品的要求是可以进行可视化、有角色权限、任务编排、支持http、接入成本低等&#xff0c;发现有有两款挺符合的PowerJob和Snail Job。 同类产品对比 Elastic-Jobxxl-jobPowerJobSnail Job…

YOLOv5改进 | 注意力机制 | 在主干网络中添加SOCA模块【原理+附完整代码】

&#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 现有的基于CNN的SISR方法主要关注更宽或更深的架构设计&#xff0c;忽视了探索中间层的特征相关性&#xff0c;因此阻碍了CNN的表达能力。为…

SmartEDA VS Multisim/Proteus:电子设计江湖,谁主沉浮?

在电子设计的江湖里&#xff0c;SmartEDA、Multisim和Proteus无疑是几大门派&#xff0c;各自拥有独特的武功秘籍和门派特色。今天&#xff0c;我们就来一场巅峰对决&#xff0c;看看这些电子设计软件究竟谁能笑傲江湖&#xff0c;成为电子设计界的霸主&#xff01; 一、门派起…

Java进阶_抽象类与方法

抽象类概念 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就是抽象类。 抽象类除了不能实例化对象之…

使用Nextjs学习(学习+项目完整版本)

创建项目 运行如下命令 npx create-next-app next-create创建项目中出现的各种提示直接走默认的就行,一直回车就行了 创建完成后进入到项目运行localhost:3000访问页面,如果和我下面页面一样就是创建项目成功了 整理项目 将app/globals.css里面的样式都删除,只留下最上面三…

Git:从配置到合并冲突

目录 1.前言 2.Git的下载与初始化配置 3.Git中新建仓库 4.Git的工作区域和文件状态 5.Git中查看操作和提交记录 6.Git中添加和提交文件 7.Git中回退提交版本 8.Git中查看版本间的差异 9.Git中删除文件 10.Git中忽略指定文件 11.Git中配置SSH密钥 12.Git中关联克隆仓库 13.Git中…

核心社群营销和覆盖区域选型

目录 一、背景介绍 &#xff08;一&#xff09;核心流程 &#xff08;二&#xff09;用户进群 &#xff08;三&#xff09;内容匹配 &#xff08;四&#xff09;数据追踪 &#xff08;五&#xff09;风险管控 二、业界调研 三、聚焦群覆盖区域 &#xff08;一&#xf…