加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

使用 Swift 构建自定义的ActivityIndicator View

发布时间:2020-12-14 05:18:53 所属栏目:百科 来源:网络整理
导读:本文由@Chun发表于Chun Tips :http://chun.tips/blog/2014/12/11/shi-yong-swift-gou-jian-zi-ding-yi-de-activityindicator-view/ 目前在自己的个人项目里,已经开始使用Swift去编写代码。这篇文章把项目中自己设计的一个ActivityIndicator View展示给大家

本文由@Chun发表于Chun Tips :http://chun.tips/blog/2014/12/11/shi-yong-swift-gou-jian-zi-ding-yi-de-activityindicator-view/

目前在自己的个人项目里,已经开始使用Swift去编写代码。这篇文章把项目中自己设计的一个ActivityIndicator View展示给大家。

在开始之前,我们先看看最终的效果,如下图:


我建议大家下载本文对应在Github分享的完整项目:https://github.com/yechunjun/CCActivityIndicatorView,以便跟着本篇文章来阅读代码。

需求分析

我们需要实现一个自定义的和 UIActivityIndicatorView 提供相似功能的一个Loading效果。我们将使用 Core Graphics 来绘制这样的效果,并使用 Core Animation让它动起来。

让我们先分析一下这个控件的组成,为我们实际编码提供具体的思路。

首先,这个loading效果图,是由8个圆弧组成的一个圆。

我们先要会画圆弧:

像这样画8个不同颜色的圆弧,围成一个圆,形成动画的一帧:

实现该动画至少需要8种不同情况的圆,然后通过Core Animation的CAKeyframeAnimation关键帧动画让它动起来。

绘制圆弧

这里我们使用 UIBezierPath 类去构建路径,然后通过绘制路径的方式绘制圆弧。

let context = UIGraphicsGetCurrentContext()

// 初始化一个 UIBezierPath 实例
let arcPath = UIBezierPath()

// 构建Arc路径
arcPath.addArcWithCenter(CGPointMake(CGFloat(self.frame.size.width/2),CGFloat(self.frame.size.height/2)),radius: CGFloat(Config.CC_ARC_DRAW_RADIUS),startAngle: CGFloat(DegreesToRadians(startAngle)),endAngle: CGFloat(DegreesToRadians(startAngle + Config.CC_ARC_DRAW_DEGREE)),clockwise: true)

// 把路径添加到当前绘图的上下文
CGContextAddPath(context,arcPath.CGPath)

// 设置线段宽度
CGContextSetLineWidth(context,CGFloat(Config.CC_ARC_DRAW_WIDTH))

// 设置线段颜色
CGContextSetStrokeColorWithColor(context,strokeColor)

// 绘制        
CGContextStrokePath(context)

通过如上的方式,我们就可以成功画出一个圆弧。其中:

func addArcWithCenter(center: CGPoint,radius: CGFloat,startAngle: CGFloat,endAngle: CGFloat,clockwise: Bool)

这个方法构建路径的解释是 center 为圆点坐标,radius 为半径,startAngle 为开始的弧度,endAngle 为结束的弧度,clockwise 表示的是顺时针还是逆时针。
绘制一帧Contents:8个圆弧

当我们可以成功在绘图上下文绘制出圆弧时,我们应该开始着手绘制效果图中的8个圆弧,并让它在正确的位置,带上不同颜色。

这里是效果图的一些参数设置,包括半径,宽度,颜色等信息:

static let CC_ACTIVITY_INDICATOR_VIEW_NUMBEROFFRAMES: Int = 8
static let CC_ACTIVITY_INDICATOR_VIEW_WIDTH: CGFloat = 40
static let CC_ARC_DRAW_PADDING: CGFloat = 3.0
static let CC_ARC_DRAW_DEGREE: CGFloat = 39.0
static let CC_ARC_DRAW_WIDTH: CGFloat = 6.0
static let CC_ARC_DRAW_RADIUS: CGFloat = 10.0
static let CC_ARC_DRAW_COLORS = [UIColor(red: 242/255.0,green: 242/255.0,blue: 242/255.0,alpha: 1.0).CGColor,UIColor(red: 230/255.0,green: 230/255.0,blue: 230/255.0,UIColor(red: 179/255.0,green: 179/255.0,blue: 179/255.0,UIColor(red: 128/255.0,green: 128/255.0,blue: 128/255.0,alpha: 1.0).CGColor]

我们可以在循坏中绘制8个圆弧,生成一张image,做为动画的一帧,此时完整的代码看上去像这样:

static func CCActivityIndicatorViewFrameImage(frame: Int,_ scale: CGFloat) -> UIImage {

    // 创建一个基于位图的上下文(context)
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(CC_ACTIVITY_INDICATOR_VIEW_WIDTH,CC_ACTIVITY_INDICATOR_VIEW_WIDTH),false,scale)

    let context = UIGraphicsGetCurrentContext()

    var startDegree = Config.CC_ARC_DRAW_PADDING
    for index in 1...8 {
        let arcPath = UIBezierPath()
        let center  = CGPointMake(Config.CC_ACTIVITY_INDICATOR_VIEW_WIDTH / 2,Config.CC_ACTIVITY_INDICATOR_VIEW_WIDTH / 2)
        let startAngle = CGFloat(DegreesToRadians(Double(startDegree)))
        let endAngle = CGFloat(DegreesToRadians(Double(startDegree + Config.CC_ARC_DRAW_DEGREE)))
        arcPath.addArcWithCenter(center,radius: Config.CC_ARC_DRAW_RADIUS,startAngle: startAngle,endAngle: endAngle,clockwise: true)
        CGContextAddPath(context,arcPath.CGPath)
        startDegree += Config.CC_ARC_DRAW_DEGREE + (Config.CC_ARC_DRAW_PADDING * 2)
        CGContextSetLineWidth(context,Config.CC_ARC_DRAW_WIDTH)
        let colorIndex = abs(index - frame)
        let strokeColor = Config.CC_ARC_DRAW_COLORS[colorIndex]
        CGContextSetStrokeColorWithColor(context,strokeColor)
        CGContextStrokePath(context)
    }

    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return image
}

使用for循环绘制8次,产生8个圆弧,并且设置不同的颜色。这里的frame用来确定当前image所处的帧数。通过它和当前index的绝对值,我们可以获得当前圆弧应该显示的颜色。
动起来

在设计一个ActivityIndicator View的时候,我们应该像UIKit提供的 UIActivityIndicatorView 一样,至少需要实现这三组API:

func startAnimating()
func stopAnimating()
func isAnimating() -> Bool

感谢 @传人Joe的评价,这里把原先通过timer执行的动画换成了CoreAnimation来实现。

func startAnimating () {

    func animate () {
        self.animating = true

        let animationDuration: CFTimeInterval = 0.8

        // 构造一组动画所需的images
        var animationImages = [CGImageRef]()
        for frame in 1...Config.CC_ACTIVITY_INDICATOR_VIEW_NUMBEROFFRAMES {
            animationImages.append(Config.CCActivityIndicatorViewFrameImage(frame,UIScreen.mainScreen().nativeScale).CGImage)
        }

        // 使用Core Animation关键帧CAKeyframeAnimation构建动画
        let animation = CAKeyframeAnimation(keyPath: "contents")
        animation.calculationMode = kCAAnimationDiscrete
        animation.duration = animationDuration
        animation.repeatCount = HUGE
        animation.values = animationImages
        animation.removedOnCompletion = false
        animation.fillMode = kCAFillModeBoth
        self.layer.addAnimation(animation,forKey: "contents")
    }

    if !self.animating {
        animate()
    }
}

func stopAnimating () {
    self.animating = false

    self.layer.removeAnimationForKey("contents")
    self.layer.contents = Config.CCActivityIndicatorViewFrameImage(0,UIScreen.mainScreen().nativeScale).CGImage
}

这里需要注意的是:考虑不同iPhone分辨率的情况,我们在绘制image作为视图Layer的contents的时候,使用UIScreen.mainScreen().nativeScale的大小构造image context上下文环境
总结

到这个时候,我们应该就能看到和效果图一样的动画效果。但是写一个可供使用的自定义控件时,应该考虑更多的细节工作。比如初始化,视图移除,intrinsicContentSize,是否需要支持 @IBInspectable 和 @IBDesignable 等等,来让使用我们控件的开发者更加友好。更加详细的代码和Demo可以去这里查看:https://github.com/yechunjun/CCActivityIndicatorView



推荐几篇阅读量最高的文章

1. 推荐:打造高效率iOS开发实战交流VIP群

2. 一个程序员这辈子需要挣多少钱才能维系一个家庭?

3. VIP群福利:Git使用教程(视频+PPT)

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读