SwiftUI 動畫

演示

企業(yè)微信截圖_20210315204655.png

學(xué)習(xí)內(nèi)容

  • 創(chuàng)建自定義形狀
  • 為自定義形狀添加漸變顏色
  • 動畫自定義形狀

開始

啟動一個(gè)新的Xcode項(xiàng)目:

  • 開啟Xcode
  • 創(chuàng)建一個(gè)新的Xcode項(xiàng)目
  • 選擇單視圖應(yīng)用程序,然后單擊下一步
  • 為您的應(yīng)用命名(RingGraph),并確保用戶界面是Swift UI
  • 最后,單擊“完成”
  • ContentView文件名和結(jié)構(gòu)重命名為RingGraph,并確保在中將其引用重命名SceneDelegate
  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

        // Create the SwiftUI view that provides the window contents.
        let ringGraph = RingGraph()

        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: ringGraph)
            self.window = window
            window.makeKeyAndVisible()
        }
    }

您看到的所有ContentView引用RingGraph。

創(chuàng)建RingShape

創(chuàng)建一個(gè)名為Shapes的文件夾,并在其中創(chuàng)建一個(gè)名為的快速文件RingShape。

import SwiftUI

struct RingShape: Shape {
    var percent: Double
    var radius: CGFloat = 100
    
    var animatableData: Double{
        get{
            return percent
        }
        
        set{
            percent = newValue
        }
    }

    func path(in rect: CGRect) -> Path {
        let width = rect.width
        let height = rect.height
        let center = CGPoint(x: width / 2, y: height / 2)
        let endAngle = Angle(degrees: ( percent / 100 * 360) - 90)
        let radius = width / 2
        
        return Path{ path in
            path.addArc(center: center, radius: radius, startAngle: Angle(degrees: -90.0) , endAngle: endAngle, clockwise: false)
        }
    }
}

說明

  • 要創(chuàng)建自定義形狀,該結(jié)構(gòu)必須符合Shape協(xié)議。
  • 前兩個(gè)屬性是顯而易見的。至于第三個(gè)屬性,我們需要使用它來動畫化路徑的繪制。每次您需要為路徑設(shè)置動畫時(shí),請確保覆蓋該屬性并返回將更改要設(shè)置動畫的路徑狀態(tài)的屬性。
  • 當(dāng)您遵守Shape協(xié)議時(shí),將需要覆蓋此方法path(in rect: CGRect) -> Path。
  • 在path方法中,設(shè)置了用于繪制圓弧的常量。-90的值。因?yàn)槲蚁M麍D形從90度而不是0(默認(rèn)值)開始。

創(chuàng)建RingView

現(xiàn)在,該創(chuàng)建一個(gè)包含我們新創(chuàng)建RingShape的視圖了。

創(chuàng)建一個(gè)名為Views的新文件夾,并在其中創(chuàng)建一個(gè)名為RingViewswiftUI文件。

import SwiftUI

struct Ring: View {
    @Binding var percent: Double
    var thickness: CGFloat = 35
    var fontSize:CGFloat = 15
    var gradientColors =  [Color.blue, Color.red]
    
    var body: some View {
        return drawRing()
    }
    
    private func drawRing() -> some View{
        let formattedPercent = String(format: "%.f", CGFloat(self.percent))
        
        return ZStack(alignment: .top) {
            
            RingShape(percent: 100)
                .stroke(style: StrokeStyle(lineWidth: self.thickness - 5))
                .fill(Color.gray.opacity(0.2))
            
            RingShape(percent: self.percent)
                .stroke(style: StrokeStyle(lineWidth: self.thickness, lineCap: CGLineCap.round))
                .fill(
                    LinearGradient(
                        gradient: .init(colors: gradientColors), startPoint: .init(x: 0.2, y: 0.4), endPoint:  .init(x: 0.5, y: 1)
                    )
            )
            
            Text("\(formattedPercent)%")
                .multilineTextAlignment(.trailing)
                .font(.system(size: fontSize, weight: .black))
                .offset(y: -thickness / 4)
                .shadow(radius: 10)
        }
    }
}

struct Ring_Previews: PreviewProvider {
    static var previews: some View {
        Ring(percent: .constant(50))
    }
}

這里要注意的一點(diǎn)是我如何創(chuàng)建RingPaths
第一個(gè)是帶有灰色的完整圓圈
第二個(gè)是將指示百分比水平的圓圈。
使用startPointendPoint來完成。

預(yù)覽效果

image

放在一起

創(chuàng)建一個(gè)名為Utils的新文件夾,并在其中創(chuàng)建一個(gè)名為Colors的快速文件。在該文件中添加以下代碼塊:

extension Color {
    static var ring1color1: Color {
        return Color("ring1color1")
    }
    static var ring1color2:Color {
        return Color("ring1color2")
    }
    static var ring2color1:Color {
        return Color("ring2color1")
    }
    static var ring2color2:Color {
        return Color("ring2color2")
    }
    static var ring3color1:Color {
        return Color("ring3color1")
    }
    static var ring3color2:Color {
        return Color("ring3color2")
    }
}

然后創(chuàng)建一個(gè)名為Modifiers的新文件夾,并添加一個(gè)NutrientModifier包含以下代碼塊的文件:

struct NutrientModifier: ViewModifier {
    var color: Color = .red
    func body(content: Content) -> some View {
           content.foregroundColor(color)
           .frame(width: 25, height: 25)
           .cornerRadius(4)
    }

}

這只是一個(gè)簡單的修飾符,我們將在短時(shí)間內(nèi)使用。

RingGraph文件中,將它們添加到結(jié)構(gòu)的頂部:

    @State var percent1: Double = 60
    @State var percent2: Double = 70
    @State var percent3: Double = 80
    
    var gRing1:[Color] = [Color.ring1color1, Color.ring1color2]
    var gRing2:[Color] = [Color.ring2color1, Color.ring2color2]
    var gRing3:[Color] = [Color.ring3color1, Color.ring3color2]
    
    private var thickness: CGFloat = 40

在body內(nèi),所有內(nèi)容并將此代碼放入其中

    var body: some View {
        return NavigationView {
                VStack {
                    
                    Text("今天你已經(jīng)消耗了 \(String(format: "%.1f", CGFloat((self.percent1 + self.percent2 + self.percent3) / 3)))%")
                        .font(.title)
                        .fontWeight(.bold)
                        .lineLimit(2)
                        .multilineTextAlignment(.center)
                        .padding(.horizontal, 30)
                        .frame(height: 70)
                    
                    Text("保持好你的身體")
                        .multilineTextAlignment(.center)
                        .padding(.bottom, 30)
 
                        
                    self.createGrapth().frame(minWidth: 0.0, maxWidth: .infinity)
                    Spacer()
                    HStack {
                        HStack{
                            Rectangle().modifier(NutrientModifier(color: .ring1color1) )
                            Text("碳水化合物")
                            
                        }
                        Spacer()
                        HStack{
                            Rectangle().modifier(NutrientModifier(color: .ring2color2) )
                            Text("蛋白")
                            
                        }
                        Spacer()
                        HStack{
                            Rectangle().modifier(NutrientModifier(color: .ring3color2) )
                            Text("脂肪")
                        }
                    }
                    
                }.padding().navigationBarTitle(Text("SwiftUI仿寫運(yùn)動"), displayMode: .inline).navigationBarItems(trailing: self.trailingButton())
            
        }
    }

這段代碼有錯誤,因?yàn)闆]有createGraphtrailingButton功能,現(xiàn)在創(chuàng)建這些:

將其放在RingGraph結(jié)構(gòu)內(nèi)但在body塊下面:

    private func createGrapth() -> some View{
        let width = UIScreen.main.bounds.width - 20
        return
            ZStack {
                Ring(percent: self.$percent1, thickness: self.thickness, fontSize: 15, gradientColors: gRing1).frame(width: width - thickness, height: width - thickness )
                Ring(percent: self.$percent2, thickness:  self.thickness, fontSize: 15, gradientColors: gRing2).frame(width: width - thickness * 3, height: width - thickness * 3)
                Ring(percent: self.$percent3, thickness:  self.thickness, fontSize: 15, gradientColors: gRing3).frame(width: width - thickness * 5, height: width - thickness * 5)
            }
    }
    
    private func trailingButton() -> some View{
          return Button(action: {
              withAnimation(.easeInOut(duration: 1)) {
                  self.percent1 = Double.random(in: 1...100)
                  self.percent2 = Double.random(in: 1...100)
                  self.percent3 = Double.random(in: 1...100)
              }
          }) {
              Image(systemName: "arrow.clockwise")
                  .resizable()
                  .frame(width: 25, height: 30)
                .foregroundColor(.ring3color2)
                  .aspectRatio(contentMode: ContentMode.fit)
          }
      }

如您所見,這些是2個(gè)獨(dú)立的函數(shù)。
第一個(gè)創(chuàng)建3個(gè)環(huán)
第二個(gè)創(chuàng)建為每個(gè)環(huán)生成1到100之間的3個(gè)隨機(jī)數(shù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容