Neste capítulo, você aprenderá a criar uma barra de progresso circular usando Shape shapes e Animation
animações.
Se você possui um AppleWatch
, definitivamente notará AppleWatch
o anel de exercícios na parte superior, que registra seu exercício diário em três aspectos: caminhar, respirar e subir escadas.
AppleWatch
O usuário é informado sobre o andamento do exercício por meio de uma barra circular de progresso, quando atingir o alvo, os AppleWatch
três anéis circulares se fecharão.
Portanto, neste capítulo, tentaremos criar uma barra de progresso circular semelhante.
criação do projeto
Primeiro, crie um novo projeto chamado SwiftUIProgress
.
Vamos analisar a estrutura primeiro.
Vamos primeiro olhar para um círculo, descobrimos que o círculo de registros de aptidão é feito de 2 círculos empilhados, podemos criar 2 círculos e depois usar a ZStack
superposição juntos.
Antes disso, como são muitas as cores usadas dessa vez, podemos extrair o grupo de cores e referenciá-lo diretamente na view, o que pode tornar o código mais simplificado.
grupo de cores
Criamos um novo Swift
arquivo e o nomeamos 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)
}
复制代码
Criamos um init
método que aceita valores red
de vermelho, green
verde e blue
azul. Em seguida, inicializamos a Color
instância.
Aqui definimos duas cores, uma é um rosa mais bonito gradientPink
, a outra é um amarelo mais bonito gradientYellow
, usaremos essas duas cores como gradiente de cor do anel posteriormente.
Estilo Básico - Anel Externo
Vamos voltar para ContentView
a página inicial e primeiro criar o fundo do anel na parte de trás.
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)
}
}
复制代码
Primeiro definimos a Circle
espessura thickness
do anel e a largura do anel width
, depois usamos stroke
o traço para traçar o anel e adicionar um systemGray6
cinza claro à borda.
然后设置圆环的大小。这样我们就获得了第一个圆环:背景圆环。
基础样式-内环
接下来,我们来完成内环,这是一个跟随进度变动的圆环,我们就不能直接用Circle
圆形绘制。
还记得之前的章节,我们使用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
度。
内环的绘制我们使用addArc
的方法,起始角度为0
,结束角度用360
度乘以progress
进度的值,而且要加上startAngle
开始角度来计算。
最后返回画好的圆,我们可以在ContentView
中引用它看看效果。
ZStack {
//外环
Circle()
.stroke(Color(.systemGray6), lineWidth: thickness)
//内环
RingShape(progress: 0.3, thickness: thickness)
}
.frame(width: width, height: width, alignment: .center)
复制代码
基础样式-渐变色
接下来,我们给我们的进度圆环附上渐变色Gradient
。
科普一个知识点。
AngularGradient
角梯度,AngularGradient
角梯度是SwiftUI
提供的一种绘制渐变色的方法,可以跟随不同角度变化,从起点到终点,颜色按顺时针做扇形渐变
。
AngularGradient(gradient: Gradient(colors: [.gradientPink, .gradientYellow]), center: .center, startAngle: .degrees(startAngle), endAngle: .degrees(360 * 0.3 + startAngle))
复制代码
这里,我们在AngularGradient
角梯度的框架里,指定渲染颜色为渐变色,引用我们定义好的gradientPink
粉色和gradientYellow
黄色。
渲染梯度开始角度为startAngle
定义的开始角度,结束角度为360 * 0.3 + startAngle
,从开始角度开始。
然后我们使用.fil
修饰符,将AngularGradient
角梯度赋予RingShape
内环。
非常不错!
动画效果
接下来,我们来实现下动画效果。
设置3个进度来展示进度:0%、50%、100%
,当我们的进度从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
赋值0
,同理,我们完成了3
个进度值选择。
我们将整个进度调节和内外环视图使用VStack
垂直排布。
同时,我们在内外环的组合视图中增加Animation
动画。
.animation(Animation.easeInOut(duration: 1.0),value: progress)
复制代码
嗯?好像出了点问题,我们发现内环的Animation
动画好像不起效果,进度加载仍旧很生硬
。
这是因为我们在实现RingShape
内环构建的过程中,它符合Shape
协议,而恰巧是Shape
协议它有一个默认
的动画,也就是没有数据的动画。
因此在ContentView
中,我们怎么加Animation
动画效果都没有作用。
要解决这个问题也很简单,我们只需要在构建RingShape
内环时,赋予progress
新的值就可以了。
var animatableData: Double {
get { progress }
set { progress = newValue }
}
复制代码
这样,我们就实现了内环进度的Animation
动画效果。
恭喜你,完成了本章的所有练习~
完整代码
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))
}
}
复制代码
快来动手试试吧!
如果本专栏对你有帮助,不妨点赞、评论、关注~