使用 Swift 构建自定义的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 表示的是顺时针还是逆时针。 当我们可以成功在绘图上下文绘制出圆弧时,我们应该开始着手绘制效果图中的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) (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |