SwiftUI Minimalist Tutorial 32: Cree una barra de progreso circular usando Forma y Animación

En este capítulo, aprenderá a crear una barra de progreso circular utilizando formas y Animationanimaciones de formas.

1.png

Si tiene uno AppleWatch, definitivamente notará AppleWatchel anillo de ejercicio en la parte superior, que registra su ejercicio diario en tres aspectos: caminar, respirar y subir escaleras.

AppleWatchEl usuario es informado del progreso del ejercicio por medio de una barra de progreso circular, cuando alcance el objetivo, los AppleWatchtres 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 .

2.png

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 ZStacksuperposició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 Swiftarchivo 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)
}
复制代码

3.png

Creamos un initmétodo que acepta valores redrojos, greenverdes y blueazules. Luego inicializamos la Colorinstancia.

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 ContentViewla 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)
    }
}
复制代码

4.png

Primero definimos el Circlegrosor thicknessdel anillo y el ancho del anillo width, luego usamos strokeel trazo para trazar el anillo y agregar un systemGray6gris 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)
复制代码

5.png

基础样式-渐变色

接下来,我们给我们的进度圆环附上渐变色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内环。

6.png

非常不错!

动画效果

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

设置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)
复制代码

7.png

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

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

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

要解决这个问题也很简单,我们只需要在构建RingShape内环时,赋予progress新的值就可以了。

var animatableData: Double {

    get { progress }
    set { progress = newValue }

}
复制代码

8.png

这样,我们就实现了内环进度的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))
    }
}
复制代码

快来动手试试吧!

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

Supongo que te gusta

Origin juejin.im/post/7095519626301276174
Recomendado
Clasificación