The beginning of the nonsense: passUIViewDrawing method to achieve mobile phone charging animation

One, achieve the effect

Step analysis

The charging effect is divided into two parts:

1. Top energy accumulation part

Display principle:

Using multiple elliptic dislocation overlapping rotation. Draw three ellipses of different colors and give them different colors, sizes and initial rotation angles. Spinning simultaneously produces the effect shown above.

2. Adsorption energy part at the bottom

Display principle:

The circle is combined with Bezier curve through context rendering to achieve adsorption effect. Of course, there are also different stages of motion, to distinguish the conditions under which the Bezier curve is drawn, and the overall appearance is adsorption.

Iii. Code display

1. BubbleEllipseView drawing

@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); / / oval CGContextAddEllipseInRect (the context, the self. The bounds); / / oval CGContextDrawPath (context, kCGPathFill); } @endCopy the code
2. Draw the 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.sie.width / 2.0, rect.sie.height / 2.0, rect.sie.height / 2.0,0,2*M_PI,0); // Add a circle CGContextDrawPath(context,kCGPathFill); // Draw a path and fill it}Copy the code
3, Adsorption energy bar SmallBubbleView drawing

There’s a lot of code here for animation purposes. Different drawing effects are adopted for each movement stage.

Single beam:

Multiple beam:

@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
Copy the code
4. Overall BubbleView drawing

Draw it all onto the BubbleView view to achieve the overall effect

@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
Copy the code

Fourth, summary and thinking

Simple implementation of charging effect, poor code, god do not laugh [fist][fist][fist]