SwiftUI动画—只需5个步骤即可构建加载微调器

自SwiftUI出现以来,编写UI代码的方式就已经改变了,它为我们发展创造力提供了很多功能,这些功能之一与动画状态转换有关。

本文会指引你构建自定义加载微调器,下图就是基于此设计

%E6%9C%AA%E5%91%BD%E5%90%8D_%E5%89%AF%E6%9C%AC

此外,你可以在我的项目存储库中找到所有操作细节:

让我们开始吧!

1.创建

针对此动画,我们将使用 Circle 结构。如你所见,此次进行的小改动将帮助我们获得所需的微调框外观。

struct Spinner: View {

    var body: some View {
        ZStack {
            SpinnerCircle()
        }.frame(width: 200, height: 200)
    }
}

struct SpinnerCircle: View {

    var body: some View {
        Circle()
            .stroke(style: StrokeStyle(lineWidth: 20, lineCap: .round))
    }
}
  • 创建一个名为 Spinner 的新SwiftUI文件。
  • 再创建一个名为 SpinnerCircle 的视图,其中包含一个 Circle
  • 使用 stroke(style:) 修改器获得微调器外观。
  • 最后,在主视图中,构建一个200像素的 SpinnerCircle 框架。

1_IWIt6s-H8M15ky5b8_42qQ
当前预览

2.修剪

要包含的下一个修饰符是 trim(from:to:) ,这样我们就可以利用提供的参数来绘制部分形状。具体工作方式如下:

考虑到这一点,我们在 SpinnerCircle 视图中做了一些更改:

struct SpinnerCircle: View {
    var start: CGFloat
    var end: CGFloat

    var body: some View {
        Circle()
            .trim(from: start, to: end)
            .stroke(style: StrokeStyle(lineWidth: 20, lineCap: .round))
    }
}

3.旋转

最后,我们将包含另一个修饰符。 rotationEffect() 可以使我们的视图旋转到特定的角度。

拥有半个修剪圆( .trim(from:0.0 to: 0.5) ),并且 rotationEffect(d) 中的 d 是先前定义的一个变量,然后旋转修饰符将导致:

要做的更改:

struct SpinnerCircle: View {
    var start: CGFloat
    var end: CGFloat
    var rotation: Angle
    var color: Color

    var body: some View {
        Circle()
            .trim(from: start, to: end)
            .stroke(style: StrokeStyle(lineWidth: 20, lineCap: .round))
            .fill(color)
            .rotationEffect(rotation)
    }
}

我们完成了!并且已经准备好进行动画处理。还要注意的是,我们包括了 fill 修饰符和 color 属性。

在这一点上, Spinner 整体视图是不完整的,不用担心,这一问题在下一部分会得到解决。

4.对其进行动画处理

回到主视图,我们要定义一些常量,例如微调器旋转的持续时间以及将微调器置于初始位置的度数:

struct Spinner: View {

    let rotationTime: Double = 0.75
    let fullRotation: Angle = .degrees(360)
    static let initialDegree: Angle = .degrees(270)

    var body: some View { ... }
}

动画将基于已更改的 SpinnerCircle 修剪和旋转属性,因此包含带有 State 的以下变量:

    @State var spinnerStart: CGFloat = 0.0
    @State var spinnerEndS1: CGFloat = 0.03
    @State var rotationDegreeS1 = initialDegree

最后,主体中包含 SpinnerCircle 视图、一些动画方法和一个 .onAppear() 块:

    var body: some View {
        ZStack {
            // S1
            SpinnerCircle(start: spinnerStart, end: spinnerEndS1, rotation: rotationDegreeS1, color: darkBlue)

        }.frame(width: 200, height: 200)
        .onAppear() {
            Timer.scheduledTimer(withTimeInterval: animationTime, repeats: true) { (mainTimer) in
                self.animateSpinner()
            }
        }
    }

    // MARK: Animation methods
    func animateSpinner(with timeInterval: Double, completion: @escaping (() -> Void)) {
        Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false) { _ in
            withAnimation(Animation.easeInOut(duration: rotationTime)) {
                completion()
            }
        }
    }

    func animateSpinner() {

    }
  • 动画方法将在特定时间间隔内更新微调器的圆圈属性,从而创建单独的动画。
  • 一旦视图出现,微调框将使用 Timer 不断进行动画处理。
  • animationTime 代表动画的总时间。它是动画方法中定义所有时间间隔的总和。
  • 将其定义为 let animationTime: Double = 1.9

我们准备好了,那就开始制作动画吧!

animateSpinner() 方法中,包括在 rotationTime 期间填充 Circle 形状所需的代码,具体如下:

animateSpinner(with:rotationTime){self.spinnerEndS1 = 1.0}

1_mZFL2Hpx_EE2DzunaxMDgA
当前预览

在下面的旋转中( rotationTime x 2 ),我们将返回到初始形状:

animateSpinner(with:(rotationTime * 2)){     
    self.spinnerEndS1 = 0.03 
}

1_wTJNCv4F9o8F6N28q1ZFfg
当前预览

最后的一个技巧。我们将在上一个动画之前进行一个完整的旋转(360°)。这样,我们就可以实现所需的动画了:

animateSpinner(with:(rotationTime * 2)
    -0.025 ){        self.rotationDegreeS1 + = fullRotation 
}

1_fFxf3c_zE_mAEvTO6fOdjw
当前预览

  • 通过测试得到 0.025 。我们尝试几个值,直到找到一个合适的值为我们提供一个流畅的动画。

5.完成

其余练习只是对先前步骤的重复。如果要检查我们正使用的设计,你会注意到其余部分也都是 Circle

因此,如果我们在主视图中添加此颜色和一些颜色详细信息,则它看起来就像:

ZStack {
    darkGray
       .edgesIgnoringSafeArea(.all)

    ZStack {
        // S3
        SpinnerCircle(start: spinnerStart, end: spinnerEndS2S3, rotation: rotationDegreeS3, color: darkViolet)

         // S2
         SpinnerCircle(start: spinnerStart, end: spinnerEndS2S3, rotation: rotationDegreeS2, color: darkPink)

         // S1
         SpinnerCircle(start: spinnerStart, end: spinnerEndS1, rotation: rotationDegreeS1, color: darkBlue)

    }.frame(width: 200, height: 200)
}
  • spinnerEndS2S3 的初始化方式与 spinnerEndS1 相同。
  • rotationDegreeS2rotationDegreeS3rotationDegreeS1 相同。
  • 使用的颜色是自定义的颜色,可以检查存储库或使用你自己的颜色。

另一方面, animateSpinner() 将包含所需动画:

    func animateSpinner() {
        animateSpinner(with: rotationTime) { self.spinnerEndS1 = 1.0 }

        animateSpinner(with: (rotationTime * 2) - 0.025) {
            self.rotationDegreeS1 += fullRotation
            self.spinnerEndS2S3 = 0.8
        }

        animateSpinner(with: (rotationTime * 2)) {
            self.spinnerEndS1 = 0.03
            self.spinnerEndS2S3 = 0.03
        }

        animateSpinner(with: (rotationTime * 2) + 0.0525) { self.rotationDegreeS2 += fullRotation }

        animateSpinner(with: (rotationTime * 2) + 0.225) { self.rotationDegreeS3 += fullRotation }
    }

这样,就大功告成了:

1_nSTJtByyd0bU9p-FLkokIg
当前预览

如果你对SwiftUI动画感兴趣并希望看到更多内容,可以着手于该项目或在Instagram上关注我:

谢谢阅读,下次再见:grin:

原文作者 Finsi Ennes
原文链接 https://medium.com/swlh/swiftui-animations-loading-spinner-2e01a3d8e9c0

推荐阅读
作者信息
AgoraTechnicalTeam
TA 暂未填写个人简介
文章
134
相关专栏
开源技术
63 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和 Agora 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。