En este capítulo, aprenderá a crear una barra de progreso circular utilizando formas y Animation
animaciones de formas.
Si tiene uno AppleWatch
, definitivamente notará AppleWatch
el anillo de ejercicio en la parte superior, que registra su ejercicio diario en tres aspectos: caminar, respirar y subir escaleras.
AppleWatch
El usuario es informado del progreso del ejercicio por medio de una barra de progreso circular, cuando alcance el objetivo, los AppleWatch
tres anillos circulares se cerrarán.
Entonces, en este capítulo, intentaremos crear una barra de progreso circular similar.
creación de proyectos
Primero, crea un nuevo proyecto llamado SwiftUIProgress
.
Analicemos primero la estructura.
Primero veamos un círculo, descubrimos que el círculo de los registros de fitness está formado por 2 círculos apilados, podemos crear 2 círculos y luego usar la ZStack
superposición juntos.
Antes de eso, debido a que esta vez se usan muchos colores, podemos extraer el grupo de colores y hacer referencia directamente a él en la vista, lo que puede simplificar el código.
grupo de colores
Creamos un nuevo Swift
archivo y lo nombramos 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)
}
复制代码
Creamos un init
método que acepta valores red
rojos, green
verdes y blue
azules. Luego inicializamos la Color
instancia.
Aquí definimos dos colores, uno es un rosa más hermoso gradientPink
, el otro es un amarillo más hermoso gradientYellow
, usaremos estos dos colores como el color degradado del anillo más adelante.
Estilo básico - Anillo exterior
Volvamos a ContentView
la página de inicio y primero creemos el fondo del anillo en la parte posterior.
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)
}
}
复制代码
Primero definimos el Circle
grosor thickness
del anillo y el ancho del anillo width
, luego usamos stroke
el trazo para trazar el anillo y agregar un systemGray6
gris claro al borde.
然后设置圆环的大小。这样我们就获得了第一个圆环:背景圆环。
基础样式-内环
接下来,我们来完成内环,这是一个跟随进度变动的圆环,我们就不能直接用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))
}
}
复制代码
快来动手试试吧!
如果本专栏对你有帮助,不妨点赞、评论、关注~