使用贝塞尔曲线实现一个iOS时间轴

news2024/12/27 14:14:02

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.coloradmin.cn/o/1806384.html

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

相关文章

手写kNN算法的实现-用余弦相似度来度量距离

设a为预测点&#xff0c;b为其中一个样本点&#xff0c;在向量空间里&#xff0c;它们的形成的夹角为θ&#xff0c;那么θ越小&#xff08;cosθ的值越接近1&#xff09;&#xff0c;就说明a点越接近b点。所以我们可以通过考察余弦相似度来预测a点的类型。 from collections i…

【学术小白成长之路】03三方演化博弈(基于复制动态方程)均衡点与稳定性分析

从本专栏开始&#xff0c;笔者正式研究演化博弈分析&#xff0c;其中涉及到双方演化博弈分析&#xff0c;三方演化博弈分析&#xff0c;复杂网络博弈分析等等。 先阅读了大量相关的博弈分析的文献&#xff0c;总结了现有的研究常用的研究流程&#xff0c;针对每个流程进行拆解。…

HTML-CSS练习例子

HTML CSS 练习 https://icodethis.com 作为前端练习生。不敲代码只看&#xff0c;入门是很慢的&#xff0c;所以直接实战是学习前端最快的途径之一。 这个网站练习HTML CSS的&#xff0c;可以打开了解一下&#xff0c;可以每天打卡&#xff0c;例子简单&#xff0c;循序渐进&…

《TCP/IP网络编程》(第十三章)多种I/O函数(2)

使用readv和writev函数可以提高数据通信的效率&#xff0c;它们的功能可以概括为**“对数据进行整合传输及发送”**。 即使用writev函数可以将分散在多个缓冲中的数据一并发送&#xff0c;使用readv函数可以由多个缓冲分别接受&#xff0c;所以适当使用他们可以减少I/O函数的调…

Pytorch 实现目标检测一(Pytorch 23)

一 目标检测和边界框 在图像分类任务中&#xff0c;我们假设图像中只有一个主要物体对象&#xff0c;我们只关注如何识别其类别。然而&#xff0c;很多时候图像里有多个我们感兴趣的目标&#xff0c;我们不仅想知 道它们的类别&#xff0c;还想得到它们在图像中的具体位置。在…

ESP8266+STM32+阿里云保姆级教程(AT指令+MQTT)

前言&#xff1a;在开发过程中&#xff0c;几乎踩便了所有大坑小坑总结出的文章&#xff0c;我是把坑踩满了&#xff0c;帮助更过小白快速上手&#xff0c;如有错误之处&#xff0c;还麻烦各位大佬帮忙指正、 目录 一、ESP-01s介绍 1、ESP-01s管脚功能&#xff1a; 模组启动模…

vscode 突然无法启动 WSL terminal 了怎么办?

参考&#xff1a;https://github.com/microsoft/vscode/issues/107485 根据参考网页&#xff0c;似乎在 windows 更新之后&#xff0c;重启&#xff0c;就有可能出现标题所说的 vscode 无法启动 WSL terminal 的情况。 首先使用 cmd 进入 wsl 终端&#xff0c;把 ~/.vscode-se…

EON安装ASE Interface

EON安装 我的eon路径于/eon/。 则环境为 export PYTHONPATH/eon/:$PYTHONPATH export PATH/eon/bin:$PATHsource: https://theory.cm.utexas.edu/eon/installation.html ASE 测试系统ubuntu。如果你python2和python3总是纠缠不清&#xff0c;可以sudo apt install python-…

vscode中执行python语句dir(torch)不返回结果

输入半天&#xff0c;发现在IDLE运行后的shell界面输入语句就会返回一大串。但是在vscode中老是不返回值。 结果恍然发现这没加print&#xff08;&#xff09;。 无语惨了。 家人们&#xff0c;这是python&#xff0c;而不是matlab。思维还没转换过来&#xff0c;笑死

扩散模型Stable Diffusion

扩散模型构成 Text Encoder(CLIPText) Clip Text为文本编码器。以77 token为输入&#xff0c;输出为77 token 嵌入向量&#xff0c;每个向量有768维度。 Diffusion(UNetScheduler) 在潜在空间中逐步处理扩散信息。以文本嵌入向量和由噪声组成的起始多维数组为输入&#xff0c…

二叉树—leetcode

前言 本篇博客我们来仔细说一下二叉树二叉树的一些OJ题目 请看完上一篇&#xff1a;数据结构-二叉树-CSDN博客 &#x1f493; 个人主页&#xff1a;普通young man-CSDN博客 ⏩ 文章专栏&#xff1a;LeetCode_普通young man的博客-CSDN博客 若有问题 评论区见&#x1f4dd; &…

Zynq7000 系列FPGA模块化仪器

• 基于 XilinxXC7Z020 / 010 / 007S • 灵活的模块组合 • 易于嵌入的紧凑型外观结构 • 高性能的 ARM Cortex 处理器 • 成熟的 FPGA 可编程逻辑 &#xff0c;基于 IP 核的软件库 FPGA 控制器 Zynq7000 系列模块是基于 Xilinx XC7Z020/010/007S 全可编程片上系统 (SoC) 的…

LLM的基础模型8:深入注意力机制

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则提…

[ue5]建模场景学习笔记(5)——必修内容可交互的地形,交互沙(3)

1.需求分析&#xff1a; 我们现在已经能够让这片地形出现在任意地方&#xff0c;只要角色走在这片地形上&#xff0c;就能够产生痕迹&#xff0c;但这片区域总是需要人工指定&#xff0c;又无法把这片区域无限扩大&#xff08;显存爆炸&#xff09;&#xff0c;因此尝试使角色无…

【BUG】已解决:Could not find a version that satisfies the requirement tensorflow

已解决&#xff1a;Could not find a version that satisfies the requirement tensorflow 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;同时是武汉城市开发者社区主理人 擅长.net、C…

Java抽象队列同步器AQS

AQS介绍 AQS是一个抽象类&#xff0c;主要用来构建锁和同步器。 public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { }AQS为构建锁和同步器提供了一些通用功能的实现&#xff0c;因此&#xff0c;使用…

基于STM32开发的智能语音助理系统

⬇帮大家整理了单片机的资料 包括stm32的项目合集【源码开发文档】 点击下方蓝字即可领取&#xff0c;感谢支持&#xff01;⬇ 点击领取更多嵌入式详细资料 问题讨论&#xff0c;stm32的资料领取可以私信&#xff01; 目录 引言环境准备智能语音助理系统基础代码实现&#xff…

BFS实现图的点的层次-java

加强对广度优先搜索的理解&#xff0c;其实就是主要的3个步骤&#xff0c;外加数组模拟单链表是基础&#xff0c;要搞懂。 目录 前言 一、图中点的层次 二、算法思路 1.广度优先遍历 2.算法思路 三、代码如下 1.代码如下&#xff08;示例&#xff09;&#xff1a; 2.读入…

【Python教程】2-函数、逻辑运算与条件判断

在整理自己的笔记的时候发现了当年学习python时候整理的笔记&#xff0c;稍微整理一下&#xff0c;分享出来&#xff0c;方便记录和查看吧。个人觉得如果想简单了解一名语言或者技术&#xff0c;最简单的方式就是通过菜鸟教程去学习一下。今后会从python开始重新更新&#xff0…

0基础学习区块链技术——入门

大纲 区块链构成区块链相关技术Hash算法区块链区块链交易 参考资料 本文力求简单&#xff0c;不讨论任何技术细节&#xff0c;只是从简单的组成来介绍区块链技术&#xff0c;以方便大家快速入门。同时借助一些可视化工具&#xff0c;辅助大家有直观的认识。 区块链构成 顾名思…