• 使用 Swift 构建自定义的ActivityIndicator View


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

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

    content-loader.gif

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

    需求分析

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

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

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

    我们先要会画圆弧:

    360桌面截图20141211151745.jpg

    像这样画8个圆弧,围成一个圆:

    360桌面截图20141211151813.jpg

    然后通过重复改变每一个圆弧的颜色,让它动起来。

    我们继承UIView, 重写drawRect方法绘制界面,第一步得到当前绘图的上下文:

    1
    let context = UIGraphicsGetCurrentContext()

    绘制圆弧

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 初始化一个 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)

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

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

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

    绘制8个圆弧

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    struct Config {
        static let CC_ACTIVITY_INDICATOR_VIEW_WIDTH = 40
        static let CC_ARC_DRAW_PADDING = 3.0
        static let CC_ARC_DRAW_DEGREE = 39.0
        static let CC_ARC_DRAW_WIDTH = 6.0
        static let CC_ARC_DRAW_RADIUS = 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, alpha: 1.0).CGColor, UIColor(red: 179/255.0, green: 179/255.0, blue: 179/255.0, alpha: 1.0).CGColor, UIColor(red: 128/255.0, green: 128/255.0, blue: 128/255.0, alpha: 1.0).CGColor, UIColor(red: 128/255.0, green: 128/255.0, blue: 128/255.0, alpha: 1.0).CGColor, UIColor(red: 128/255.0, green: 128/255.0, blue: 128/255.0, alpha: 1.0).CGColor, UIColor(red: 128/255.0, green: 128/255.0, blue: 128/255.0, alpha: 1.0).CGColor, UIColor(red: 128/255.0, green: 128/255.0, blue: 128/255.0, alpha: 1.0).CGColor]
        }

    我们可以在drawRect方法,循坏绘制8个圆弧,此时完整的代码看上去像这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    override func drawRect(rect: CGRect) {
     
        let context = UIGraphicsGetCurrentContext()
     
        var startAngle = Config.CC_ARC_DRAW_PADDING
     
        for index in 1...8 {
            let arcPath = UIBezierPath()
            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)
            startAngle += Config.CC_ARC_DRAW_DEGREE + (Config.CC_ARC_DRAW_PADDING * 2)
     
            CGContextSetLineWidth(context, CGFloat(Config.CC_ARC_DRAW_WIDTH))
            let colorIndex = abs(index - self.animateIndex)
            let strokeColor = Config.CC_ARC_DRAW_COLORS[colorIndex]
            CGContextSetStrokeColorWithColor(context, strokeColor)
            CGContextStrokePath(context)
        }
    }

    使用for循环绘制8次,产生8个圆弧,并且设置不同的颜色。这里的self.animateIndex用来跟踪整个动画的头一个颜色最浅圆弧的位置。通过它和当前index的绝对值,获得当前圆弧应该显示的颜色。

    动起来

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

    1
    2
    3
    func startAnimating()
    func stopAnimating()
    func isAnimating() -> Bool

    这里我们使用一个timer去改变self.animateIndex的值,不断重画当前视图,来产生动画效果,代码看起来像这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // 使用该值驱动改变圆弧颜色,产生动画效果
    private var animateIndex: Int = 1
     
    // 动画的Timer
    private var animatedTimer: NSTimer?
     
    // timer响应的事件,在这里setNeedsDisplay让UIKit重画当前视图,然后不断改变animateIndex值。
    @objc private func animate () {
        if !self.hidden {
            self.setNeedsDisplay()
            self.animateIndex++
            if self.animateIndex > 8 {
                self.animateIndex = 1
            }
        }
    }
     
    // 开始动画
    func startAnimating () {
     
        if self.hidden {
            self.hidden = false
        }
     
        if let timer = self.animatedTimer {
            timer.fire()
        else {
            self.animatedTimer = NSTimer(timeInterval: 0.1, target: self, selector: "animate", userInfo: nil, repeats: true)
            NSRunLoop.currentRunLoop().addTimer(self.animatedTimer!, forMode: NSRunLoopCommonModes)
        }
    }

    这里使用

    1
    init(timeInterval ti: NSTimeInterval, target aTarget: AnyObject, selector aSelector: Selector, userInfo: AnyObject?, repeats yesOrNo: Bool) -> NSTimer

    而不是使用

    1
    class func scheduledTimerWithTimeInterval(ti: NSTimeInterval, target aTarget: AnyObject, selector aSelector: Selector, userInfo: AnyObject?, repeats yesOrNo: Bool) -> NSTimer

    构建timer的原因是:当我们在使用自己的ActivityIndicator View的时候,我们可能把它放到UIScrollView上面。这个时候使用scheduledTimerWithTimeInterval创建的timer是加入到当前Run Loop中的,而UIScrollView在接收到用户交互事件时,主线程Run Loop会设置为UITrackingRunLoopMode。这个时候会导致timer失效。更详细的解答,我在走进Run Loop的世界 (一):什么是Run Loop?一文中有说明。

    总结

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

  • 相关阅读:
    BitmapFactory.decodeStream(inputStream)返回null的解决方法
    android studio 自用快捷键方案
    jquery源码学习(四)—— jquery.extend()
    css3动画性能优化
    组件化开发之vue
    调用本地摄像头并通过canvas拍照
    傳説中的 jsonp
    jsonp的原理
    正确而又严谨得ajax原生创建方式
    让浏览器阻塞10秒钟的方法
  • 原文地址:https://www.cnblogs.com/itlover2013/p/4165231.html
Copyright © 2020-2023  润新知