iOS 模拟充电动画

废话开篇:通过 UIView 绘制方法实现手机充电动画

一、实现效果

屏幕录制2022-03-17 上午11.13.45.gif

二、步骤分析

实现充电效果分为两个部分:

1、顶部能量积聚部分

展示原理:

利用多椭圆错位重叠旋转。绘制三个不同颜色的椭圆,并分别赋予三者不同的颜色、尺寸及初始旋转角度。在同时进行自旋即可产生上图所示效果。

2、底部吸附能量部分

展示原理:

通过上下文的绘制,将圆与贝塞尔曲线相结合,实现吸附效果。当然,这里面也分为不同的运动阶段,来区别贝塞尔曲线的绘制条件,整体上看上去是被吸附上来。

三、代码展示

1、椭圆 BubbleEllipseView 绘制

屏幕录制2022-03-17 下午1.10.38.gif

@interface BubbleEllipseView : UIView

@property(nonatomic,strong) UIColor * color;

@end

@implementation BubbleEllipseView

- (instancetype)initWithFrame:(CGRect)frame duration:(CFTimeInterval)duration color:(UIColor *)color rotate:(CGFloat)rotate
{
    if (self = [super initWithFrame:frame]) {
        if (rotate != 0) {
            CGAffineTransform transform = CGAffineTransformIdentity;
            transform = CGAffineTransformRotate(transform, rotate);
            self.transform = transform;
        }
        self.backgroundColor = [UIColor clearColor];
        self.color = color;
        CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
        animation.toValue = @(2*M_PI + rotate);
        animation.duration = duration;
        animation.removedOnCompletion = false;
        animation.repeatCount = MAXFLOAT;
        [self.layer addAnimation:animation forKey:nil];
    }
    return self;
}

- (void)drawRect:(CGRect)rect
{
    CGContextRef context =UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, self.color.CGColor);
    //椭圆
    CGContextAddEllipseInRect(context,self.bounds);//椭圆
    CGContextDrawPath(context,kCGPathFill);
}
@end
复制代码
2、圆 BubbleCircleView 绘制
@interface BubbleCircleView : UIView

@property(nonatomic,strong) UIColor * color;

@end

@implementation BubbleCircleView

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor clearColor];
    }
    return self;
}

- (void)drawRect:(CGRect)rect
{
    CGContextRef context =UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
    CGContextAddArc(context,rect.size.width / 2.0,rect.size.height / 2.0,rect.size.height / 2.0,0,2*M_PI,0);//添加一个圆
    CGContextDrawPath(context,kCGPathFill);//绘制路径加填充
}
复制代码
3、吸附能量条 SmallBubbleView 绘制

为了满足动画效果,这里代码比较多。对于各个运动阶段采取不同的绘制效果。

单束:

屏幕录制2022-03-17 下午1.12.29.gif

多束:

屏幕录制2022-03-17 下午1.14.35.gif

@interface SmallBubbleView : UIView

@property(nonatomic,assign) CGFloat startTime;
@property(nonatomic,strong) UIColor * color;
@property(nonatomic,strong) NSArray * colorsArr;
@property(nonatomic,assign) CGFloat bubbleRadius;
@property(nonatomic,assign) CGPoint bubbleCenter;
@property(nonatomic,assign) CGFloat radian;
@property(nonatomic,assign) CGFloat maxAbsorbLength;
@property(nonatomic,assign) CGFloat maxRadius;
@property(nonatomic,assign) BubbleLifeCircleType bubbleLifeCircleType;

@end

@implementation SmallBubbleView

- (instancetype)initWithFrame:(CGRect)frame startTime:(CGFloat)startTime
{
    if (self = [super initWithFrame:frame]) {
        self.startTime = startTime;
        self.backgroundColor = [UIColor clearColor];
        self.colorsArr = @[[UIColor colorWithRed:113 / 255.0 green:191 / 255.0 blue:188 / 255.0 alpha:0.8],[UIColor colorWithRed:160 / 255.0 green:170 / 255.0 blue:229 / 255.0 alpha:1],[UIColor colorWithRed:48 / 255.0 green:116 / 255.0 blue:195 / 255.0 alpha:0.8]];
        self.color = self.colorsArr[arc4random() % self.colorsArr.count];
        self.maxAbsorbLength = 63;
        self.maxRadius = self.frame.size.width / 2.5;
        self.bubbleCenter = CGPointMake(frame.size.width / 2.0, frame.size.height);
    }
    return self;
}

- (void)drawRect:(CGRect)rect
{

    CGContextRef context = UIGraphicsGetCurrentContext();
    switch (self.bubbleLifeCircleType) {
        case BubbleLifeCircleCreate:
        {
            CGContextSetFillColorWithColor(context, self.color.CGColor);
            CGContextAddArc(context,self.bubbleCenter.x,self.bubbleCenter.y,self.bubbleRadius,0,2*M_PI,0);//添加一个圆
            CGContextDrawPath(context,kCGPathFill);//绘制路径加填充
        }
            break;
        case BubbleLifeCircleStartAbsorb:
        case BubbleLifeCircleAbsorbEnough:
        {
            //圆
            CGContextSetFillColorWithColor(context, self.color.CGColor); 
            CGContextAddArc(context,self.bubbleCenter.x,self.bubbleCenter.y,self.bubbleRadius,0,2*M_PI,0);//添加一个圆

            //贝塞尔曲线
            UIBezierPath * bezierPath = [self getAbsorbBezierPath];
            CGContextAddPath(context, bezierPath.CGPath);
            CGContextDrawPath(context,kCGPathFill);//绘制路径加填充
        }
            break;
        case BubbleLifeCircleMove:
        {
            //圆
            CGContextSetFillColorWithColor(context, self.color.CGColor);
            CGContextAddArc(context,self.bubbleCenter.x,self.bubbleCenter.y,self.bubbleRadius,0,2*M_PI,0);//添加一个圆
            //贝塞尔曲线
            UIBezierPath * bezierPath = [self getAbsorbBezierPath];
            CGContextAddPath(context, bezierPath.CGPath);
            CGContextDrawPath(context,kCGPathFill);//绘制路径加填充
        }
            break;
        case BubbleLifeCircleEnd:
        {
        }
            break;
        default:
            break;
    }
}

//获取吸附状态贝塞尔曲线
- (UIBezierPath *)getAbsorbBezierPath
{
    UIBezierPath *bezierPath = [UIBezierPath bezierPath];
    //圆弧左点
    self.radian += 0.01;
    self.radian = self.radian > M_PI_4 ? M_PI_4 : self.radian;
    CGFloat leftCircleX = self.bubbleCenter.x - self.bubbleRadius * sin(self.radian);
    CGFloat leftCircleY = self.bubbleCenter.y + self.bubbleRadius * cos(self.radian);
    CGPoint leftCirclePoint = CGPointMake(leftCircleX, leftCircleY);
    //圆弧右点
    CGFloat rightCircleX = self.bubbleCenter.x + self.bubbleRadius * sin(self.radian);
    CGFloat rightCircleY = self.bubbleCenter.y + self.bubbleRadius * cos(self.radian);
    CGPoint rightCirclePoint = CGPointMake(rightCircleX, rightCircleY);
    switch (self.bubbleLifeCircleType) {
        case BubbleLifeCircleStartAbsorb:
        {
            [bezierPath moveToPoint:leftCirclePoint];
            [bezierPath addArcWithCenter:self.bubbleCenter radius:self.bubbleRadius startAngle:M_PI_2 + self.radian endAngle:M_PI_2 - self.radian clockwise:false];
            [bezierPath moveToPoint:rightCirclePoint];
            [bezierPath addLineToPoint:CGPointMake(self.bubbleCenter.x, self.frame.size.height)];
            [bezierPath addLineToPoint:leftCirclePoint];
        }

            break;
        case BubbleLifeCircleAbsorbEnough:
        {
            [bezierPath moveToPoint:leftCirclePoint];
            [bezierPath addArcWithCenter:self.bubbleCenter radius:self.bubbleRadius startAngle:M_PI_2 + self.radian endAngle:M_PI_2 - self.radian clockwise:false];
            [bezierPath moveToPoint:rightCirclePoint];
            [bezierPath addQuadCurveToPoint:CGPointMake(self.bubbleCenter.x, self.frame.size.height) controlPoint:CGPointMake(self.bubbleCenter.x, self.bubbleCenter.y + (self.frame.size.height - self.bubbleCenter.y) / 2.0)];
            [bezierPath addQuadCurveToPoint:leftCirclePoint controlPoint:CGPointMake(self.bubbleCenter.x, self.bubbleCenter.y + (self.frame.size.height - self.bubbleCenter.y) / 2.0)];
        }
            break;
        case BubbleLifeCircleMove:
        {
            [bezierPath moveToPoint:leftCirclePoint];
            [bezierPath addArcWithCenter:self.bubbleCenter radius:self.bubbleRadius startAngle:M_PI_2 + self.radian endAngle:M_PI_2 - self.radian clockwise:false];
            [bezierPath moveToPoint:rightCirclePoint];
            CGPoint bottomPoint = CGPointMake(self.bubbleCenter.x, self.bubbleCenter.y + self.maxRadius + self.maxAbsorbLength / 2.0);
            [bezierPath addQuadCurveToPoint:bottomPoint controlPoint:CGPointMake(self.bubbleCenter.x,self.bubbleCenter.y + (bottomPoint.y - self.bubbleCenter.y) / 2.0)];

            [bezierPath addQuadCurveToPoint:leftCirclePoint controlPoint:CGPointMake(self.bubbleCenter.x, self.bubbleCenter.y + (bottomPoint.y - self.bubbleCenter.y) / 2.0)];
        }
            break;
        default:
            break;
    }
    return bezierPath;
}

//重绘
- (void)refreshDraw
{
    CGFloat proportion = 10.0;
    switch (self.bubbleLifeCircleType) {
        case BubbleLifeCircleCreate:
        {
            self.bubbleRadius += (0.05 * proportion);
            self.bubbleRadius = self.bubbleRadius > self.maxRadius ? self.maxRadius : self.bubbleRadius;
            if (self.frame.size.height - self.bubbleCenter.y - self.maxRadius > 0) {
                self.bubbleLifeCircleType = BubbleLifeCircleStartAbsorb;
            }
            self.bubbleCenter = CGPointMake(self.bubbleCenter.x, self.bubbleCenter.y - (0.05 * proportion));
        }
            break;
        case BubbleLifeCircleStartAbsorb:
        {
            self.bubbleCenter = CGPointMake(self.bubbleCenter.x, self.bubbleCenter.y - (0.04 * proportion));
            if (self.frame.size.height - self.bubbleCenter.y - self.maxRadius > 23) {
                self.bubbleLifeCircleType = BubbleLifeCircleAbsorbEnough;
            }
        }
            break;
        case BubbleLifeCircleAbsorbEnough:
        {
            self.bubbleCenter = CGPointMake(self.bubbleCenter.x, self.bubbleCenter.y - (0.06 * proportion));
            if (self.frame.size.height - self.bubbleCenter.y - self.maxRadius > self.maxAbsorbLength) {
                self.bubbleLifeCircleType = BubbleLifeCircleMove;
            }
        }
            break;
        case BubbleLifeCircleMove:
        {
            self.bubbleCenter = CGPointMake(self.bubbleCenter.x, self.bubbleCenter.y - (0.06 * proportion));
            if (self.bubbleCenter.y < 0) {
                self.bubbleLifeCircleType = BubbleLifeCircleEnd;
            }
        }
            break;
        case BubbleLifeCircleEnd:
        {
            self.bubbleCenter = CGPointMake(self.frame.size.width / 2.0, self.frame.size.height);
            self.bubbleRadius = 0;
            self.color = self.colorsArr[arc4random() % self.colorsArr.count];
            self.bubbleLifeCircleType = BubbleLifeCircleCreate;
        }
            break;
        default:

            break;

    }
    [self setNeedsDisplay];
}

@end
复制代码
4、整体 BubbleView 绘制

全部绘制到 BubbleView 视图上,实现整体效果

@interface BubbleView()

@property(nonatomic,strong) NSTimer * timer;
@property(nonatomic,strong) NSMutableArray * smallBubbleSaveArr;

@end

@implementation BubbleView

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        self.smallBubbleSaveArr = [[NSMutableArray alloc] init];
        self.backgroundColor = [UIColor blackColor];
        CGPoint center = CGPointMake(frame.size.width / 2.0, frame.size.height / 2.0);
        CGFloat width = 150;
        
        //椭圆绘制
        BubbleEllipseView * ellipseView = [[BubbleEllipseView alloc] initWithFrame:CGRectMake(0, 0, width, width + 22) duration:1.6 color:[UIColor colorWithRed:160 / 255.0 green:170 / 255.0 blue:229 / 255.0 alpha:1] rotate:0];
        ellipseView.center = center;

        BubbleEllipseView * ellipseView1 = [[BubbleEllipseView alloc] initWithFrame:CGRectMake(0, 0, width + 15, width) duration:1.5 color:[UIColor colorWithRed:113 / 255.0 green:191 / 255.0 blue:188 / 255.0 alpha:1] rotate:0];
        ellipseView1.center = center;

        BubbleEllipseView * ellipseView2 = [[BubbleEllipseView alloc] initWithFrame:CGRectMake(0, 0, width + 25, width - 10) duration:2.5 color:[UIColor colorWithRed:48 / 255.0 green:116 / 255.0 blue:195 / 255.0 alpha:0.8] rotate:M_PI / 4];
        ellipseView2.center = center;

        //圆绘制
        BubbleCircleView * circleView = [[BubbleCircleView alloc] initWithFrame:CGRectMake(0, 0, width - 5, width - 5)];
        circleView.center = center;

        self.timer = [NSTimer scheduledTimerWithTimeInterval:0.005 target:self selector: **@selector**(absorbEnergy) userInfo:**nil** repeats:YES];
        [self.timer fire];

        CGFloat smallBubbleWidth = 30;
        CGFloat smallBubbleHeight = frame.size.height - CGRectGetMaxY(circleView.frame);
        
        //吸附能量左、中、右绘制
        SmallBubbleView * smallBubbleView = [[SmallBubbleView alloc] initWithFrame:CGRectMake((frame.size.width - smallBubbleWidth) / 2.0, frame.size.height - smallBubbleHeight - 30, smallBubbleWidth, smallBubbleHeight) startTime:0];

        [self.smallBubbleSaveArr addObject:smallBubbleView];

        SmallBubbleView * smallBubbleLeftView = [[SmallBubbleView alloc] initWithFrame:CGRectMake((frame.size.width - smallBubbleWidth) / 2.0 - smallBubbleWidth, frame.size.height - smallBubbleHeight - 30, smallBubbleWidth, smallBubbleHeight) startTime:2];
        [self.smallBubbleSaveArr addObject:smallBubbleLeftView];

        SmallBubbleView * smallBubbleRightView = [[SmallBubbleView alloc] initWithFrame:CGRectMake((frame.size.width - smallBubbleWidth) / 2.0 + smallBubbleWidth, frame.size.height - smallBubbleHeight - 30, smallBubbleWidth, smallBubbleHeight) startTime:1.5];
        [self.smallBubbleSaveArr addObject:smallBubbleRightView];

        //添加子视图
        [self addSubview:smallBubbleLeftView];
        [self addSubview:smallBubbleView];
        [self addSubview:smallBubbleRightView];
        [self addSubview:ellipseView2];
        [self addSubview:ellipseView1];
        [self addSubview:ellipseView];
        [self addSubview:circleView];

    }
    return self;
}

//开始吸附动画
- (void)absorbEnergy
{
    for (SmallBubbleView * smallBubbleView in self.smallBubbleSaveArr) {
        //延迟刷新,交错效果
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(smallBubbleView.startTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [smallBubbleView refreshDraw];
        });
    }
}

//停止定时器
- (void)stop
{
    [self.timer invalidate];
    self.timer = nil;
}

@end
复制代码

四、总结与思考

简单的实现充电效果,代码拙劣,大神勿笑[抱拳][抱拳][抱拳]

猜你喜欢

转载自juejin.im/post/7075934953585770526