在本章中,你将学会使用Shape形状和Animation动画创建一个圆形进度条。

SwiftUI极简教程32:使用Shape形状和Animation动画创建一个圆形进度条

如果你恰巧拥有一块AppleWatch,你一定会注意到AppleWatch上的运动圆环,在你走路、呼吸、爬楼三个方面记录你每天的运动量。

AppleWatch通过圆环进度条的方式告知用户运动进度情梯度公式况,当你达标后,AppleWatch三个圆环就会闭合。初始化失败是怎么解决

那么本运动的好处章节,我们就尝试创建一个类似的圆形进度条。

项目创建

首先,创梯度下降法原理建一个新项目,命名为SwiftUIProgress

SwiftUI极简教程32:使用Shape形状和Animation动画创建一个圆形进度条

我们先来分析结构。

先来看一个圆,我们发现健身记录的圆环是由2个圆环层叠而运动健康成的,我们可以创建2个圆环,然后使用ZStack叠加在一起。

在那之前,因为本次用到的颜色比较多,我们可以将颜色组抽离出来,然后在视图中直接引用,初始化电脑这样做可以使得代码更加简化。

颜色组

我们新建一个Swift文件,命名为ColorExt.swift

import SwiftUI
extension Color {
public init(red: Int, green: Int, blue: Int, opacity: Double = 1.0) {
let redValue = Double(red) / 255.0
let greenValue = Double(green) / 255.0
let blueValue = Double(blue) / 255.0
self.init(red: redValue, green: greenValue, blue: blueValue, opacity: opacity)
}
public static let gradientPink = Color(red: 210, green: 153, blue: 194)
public static let gradientYellow = Color(red: 254, green: 249, blue: 215)
}

SwiftUI极简教程32:使用Shape形状和Animation动画创建一个圆形进度条

我们创建了appreciate一个init方法,它接受r梯度下降法原理ed红色、green绿色和blue蓝色的值。然后我们初始化Color实例。

这里我们定义了两个颜色,一个是比较好看的粉色gradientPink运动的好处一个是比较好看的黄色gradientYellow,我们待会儿要使用这两个颜色作为圆环的渐变颜色。

基础样式-外环

我们回到ContentView主页中,先创建背部的圆环背景。

import SwiftUI
struct ContentView: View {
var thickness: CGFloat = 30.0
var width: CGFloat = 250.0
var body: some View {
ZStack {
Circle()
.stroke(Color(.systemGray6),lineWidth: thickness)
}
.frame(width: width, height: width, alignment: .center)
}
}

SwiftUI极简教程32:使用Shape形状和Animation动画创建一个圆形进度条

我们首梯度洗脱先定义了Circl梯度下降e圆环的厚度thickappointmentness和圆环的宽度width,然后使用stroke给圆环描边,边框加个了淡淡的systemGray6灰色。

然后设置圆环的大运动小。这样我们就获梯度得了第一个圆环:背景圆环。

基础样式-内环

接下来,我们来完成内环,这是一个跟随进度变动的圆环,我们就不能直梯度下降法接用Circle运动完多久可以洗澡形绘制。

还记得之前的章节appear,我们使用Shape形状绘制各种各样的图形么,这里我们也使用Shape形状的方法绘制内环。

我们创建一个新的结构体梯度稀释,命名为RingShape

//内环
struct RingShape: Shape {
var progress: Double = 0.0
var thickness: CGFloat = 30.0
 var startAngle: Double = -90.0
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(center: CGPoint(x: rect.width / 2.0, y: rect.height / 2.0), radius: min(rect.width, rect.height) / 2.0,startAngle: .degrees(startAngle),endAngle: .degrees(360 * progress+startAngle), clockwise: false)

        return path.strokedPath(.init(lineWidth: thickness, lineCap: .round))
}
}

我们创建了RingShape结构体,它遵循Shape协议。然后我们运动员和外环一样,定义了它的厚度thickness运动员,另外还有内环的进度百分比progress参数。

startAngle开始角度为-90,这是因为我们圆放在坐标轴上,它的开始点是圆右边中间的位置,而我们进度的圆环是从圆的顶部的顶点开始,所以startAngle开始角度需要设置为-90度。

内环的绘制我们使用add梯度下降法Arc的方法,起始角度为0,结束角度用360度乘以prog运动品牌ress进度的值,而且要加上startAngle开始角度来计算。

最后返回画好的圆,我们可以在ContentView中引运动世界校园用它看看效果。

ZStack {
 //外环
 Circle()
  .stroke(Color(.systemGray6), lineWidth: thickness)
 //内环
 RingShape(progress: 0.3, thickness: thickness)
}
.frame(width: width, height: width, alignment: .center)

SwiftUI极简教程32:使用Shape形状和Animation动画创建一个圆形进度条

基础样式APP-渐变色

接下来,我们给我们的进度圆环附梯度下降法原理上渐变色Gradient

科普一个知识点。

AngularGradient梯度AngularGradi初始化是什么意思ent角梯度是SwiftUI提供的一种绘制渐变色的方法,可以跟随不同角度变化,从起点到终点,颜色按顺时针运动员扇形渐变

AngularGradient(gradient: Gradient(colors: [.gradientPink, .gradientYellow]), center: .center, startAngle: .degrees(startAngle), endAngle: .degrees(360 * 0.3 + startAngle))

这里,我们在AngularGradient角梯度的框架里,指定渲染颜色为渐变色,引用我们定义好的gradienappointmenttPink粉色和gradientYellow黄色。

渲染梯度开始角初始化失败是怎么解决度为startAngle定义的开始角度,结束角度为360 * 0.3 + startAng运动会口号le,从开始角度开始。

然后我们使用.fil修饰符,将AngularGradient角梯度赋予Rin梯度下降法原理gShap初始化磁盘e内环。

SwiftUI极简教程32:使用Shape形状和Animation动画创建一个圆形进度条

非常不错!

动画效果

接下来,我们来实现下动画效果。

设置3个进度来展示进初始化游戏启动器失败度:0%、50%、100%,当我们appearance的进度从0%50%时,我们可以看到进度条内环从0~50度的全过程。

我们先定义好初始的进度,替换我们固定的进度值:

@State var progress = 0.0

然后,我们完成下进度值的选择。

//进度调节
HStack {
Group {
Text("0%")
.font(.system(.headline, design: .rounded))
.onTapGesture {
self.progress = 0.0
}
Text("50%")
.font(.system(.headline, design: .rounded))
.onTapGesture {
self.progress = 0.5
}
Text("100%")
.font(.system(.headline, design: .rounded))
.onTapGesture {
self.progress = 1.0
}
}
.padding()
.background(Color(.systemGray6)).clipShape(RoundedRectangle(cornerRadius: 15.0, style: .continuous))
.padding()
}
.padding()

我们设置了3个进度值调节,当我们点击0%的进度值时,进度progress赋值0appetite同理,我们完成了3个进度值选择。

我们将整个进度调节和内外环视图使用VStack垂直排布。

同时,我们在内外环的组合视图中增加Animation动画。

.animation(Animation.easeInOut(duration: 1.0),value: progress)

SwiftUI极简教程32:使用Shape形状和Animation动画创建一个圆形进度条

嗯?好像出了点问题,我们梯度下降法发现内环的Animation动画好像不起效果,进度加载仍旧很生硬

这是因为我们在实现RingShape内环构建的过程中,它符合Shape协议,而恰巧是Shape协议它有一个默认的动画,也就是没有数据的动画。

因此在ContentView中,我们怎么加Animation动画效果都没有作用。

要解决这个问题也很简单,我们只需要在构建Ring梯度公式Shape内环时,赋予proapprovegress新的值就可以了。

var animatableData: Double {
 get { progress }
 set { progress = newValue }
}

SwiftUI极简教程32:使用Shape形状和Animation动画创建一个圆形进度条

这样,我们就实现了内环进度的Animation动画效果。

恭喜你,完成了本章的所有练apple习~

完整代码

import SwiftUI
struct ContentView: View {
var thickness: CGFloat = 30.0
var width: CGFloat = 250.0
var startAngle = -90.0
@State var progress = 0.0
var body: some View {
VStack {
ZStack {
// 外环
Circle()
.stroke(Color(.systemGray6), lineWidth: thickness)
// 内环
RingShape(progress: progress, thickness: thickness)
.fill(AngularGradient(gradient: Gradient(colors: [.gradientPink, .gradientYellow]), center: .center, startAngle: .degrees(startAngle), endAngle: .degrees(360*0.3 + startAngle)))
}
.frame(width: width, height: width, alignment: .center)
.animation(Animation.easeInOut(duration: 1.0),value: progress)
//进度调节
HStack {
Group {
Text("0%")
.font(.system(.headline, design: .rounded))
.onTapGesture {
self.progress = 0.0
}
Text("50%")
.font(.system(.headline, design: .rounded))
.onTapGesture {
self.progress = 0.5
}
Text("100%")
.font(.system(.headline, design: .rounded))
.onTapGesture {
self.progress = 1.0
}
}
.padding()
.background(Color(.systemGray6)).clipShape(RoundedRectangle(cornerRadius: 15.0, style: .continuous))
.padding()
}
.padding()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// 内环
struct RingShape: Shape {
var progress: Double = 0.0
var thickness: CGFloat = 30.0
var startAngle: Double = -90.0
var animatableData: Double {
get { progress }
set { progress = newValue }
}
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(center: CGPoint(x: rect.width / 2.0, y: rect.height / 2.0), radius: min(rect.width, rect.height) / 2.0,startAngle: .degrees(startAngle),endAngle: .degrees(360 * progress + startAngle), clockwise: false)
return path.strokedPath(.init(lineWidth: thickness, lineCap: .round))
}
}

快来动手试试吧!

如果本专栏对你有帮助,不妨点赞、评论、关注~APP