欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SwiftUI使用Paths和AnimatableData實(shí)現(xiàn)酷炫的顏色切換動(dòng)畫

 更新時(shí)間:2020年05月10日 10:22:29   作者:CodingSuccess  
這篇文章主要介紹了SwiftUI使用Paths和AnimatableData實(shí)現(xiàn)酷炫的顏色切換動(dòng)畫,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧

老鐵們,是時(shí)候燥起來了!本文中我們將學(xué)習(xí)如何使用 SwiftUI 中的 PathsAnimatableData 來制作顏色切換動(dòng)畫。

這些快速切換的動(dòng)畫是怎么實(shí)現(xiàn)的呢?讓我們來看下文吧!

基礎(chǔ)

要實(shí)現(xiàn)動(dòng)畫的關(guān)鍵是在 SwiftUI 中創(chuàng)建一個(gè)實(shí)現(xiàn) Shape 協(xié)議的結(jié)構(gòu)體。我們把它命名為 SplashShape 。在 Shape 協(xié)議中,有一個(gè)方法叫做 path(in rect: CGRect) -> Path ,這個(gè)方法可以用來設(shè)置圖形的外觀。我們就用這個(gè)方法來實(shí)現(xiàn)本文中的各種動(dòng)畫。

創(chuàng)建 SplashShape 結(jié)構(gòu)體

下面我們創(chuàng)建一個(gè)叫做 SplashStruct 的結(jié)構(gòu)體,它繼承于 Shape 協(xié)議。

import SwiftUI

struct SplashShape: Shape {
 
 func path(in rect: CGRect) -> Path {
 return Path()
 }
}

我們首先創(chuàng)建兩種動(dòng)畫類型: leftToRightrightToLeft ,效果如下所示:

 

Splash 動(dòng)畫

我們創(chuàng)建一個(gè)名為 SplashAnimation枚舉 來定義動(dòng)畫類型,便于以后更方便地?cái)U(kuò)展新動(dòng)畫(文章末尾可以驗(yàn)證?。?。

import SwiftUI

struct SplashShape: Shape {
 
 public enum SplashAnimation {
 case leftToRight
 case rightToleft
 }
 
 func path(in rect: CGRect) -> Path {
 return Path()
 }
}

path() 方法中,我們可以選擇需要使用的動(dòng)畫,并且返回動(dòng)畫的 Path 。但是首先,我們必須創(chuàng)建變量來存儲(chǔ)動(dòng)畫類型,記錄動(dòng)畫過程。

import SwiftUI

struct SplashShape: Shape {
 
 public enum SplashAnimation {
 case leftToRight
 case rightToleft
 }
 
 var progress: CGFloat
 var animationType: SplashAnimation
 
 func path(in rect: CGRect) -> Path {
 return Path()
 }
}

progress 的取值范圍在 0 和 1 之間,它代表整個(gè)動(dòng)畫的完成進(jìn)度。當(dāng)我們編寫 path() 方法時(shí),它就會(huì)派上用場。

編寫 path() 方法

跟之前說的一樣,為了返回正確的 Path ,我們需要明確正在使用哪一種動(dòng)畫。在 path() 方法中編寫 switch 語句,并且用上我們之前定義的 animationType 。

func path(in rect: CGRect) -> Path {
 switch animationType {
 case .leftToRight:
  return Path()
 case .rightToLeft:
  return Path()
 }
}

現(xiàn)在這個(gè)方法只會(huì)返回空 paths。我們需要?jiǎng)?chuàng)建產(chǎn)生真實(shí)動(dòng)畫的方法。

實(shí)現(xiàn)動(dòng)畫方法

在 path() 方法的下面,創(chuàng)建兩個(gè)新的方法: leftToRight() 和 rightToLeft() ,每個(gè)方法表示一種動(dòng)畫類型。在每個(gè)方法體內(nèi),我們會(huì)創(chuàng)建一個(gè)矩形形狀的 Path ,它會(huì)根據(jù) progress 變量的值隨時(shí)間發(fā)生變換。

func leftToRight(rect: CGRect) -> Path {
 var path = Path()
 path.move(to: CGPoint(x: 0, y: 0)) // Top Left
 path.addLine(to: CGPoint(x: rect.width * progress, y: 0)) // Top Right
 path.addLine(to: CGPoint(x: rect.width * progress, y: rect.height)) // Bottom Right
 path.addLine(to: CGPoint(x: 0, y: rect.height)) // Bottom Left
 path.closeSubpath() // Close the Path
 return path
}

func rightToLeft(rect: CGRect) -> Path {
 var path = Path()
 path.move(to: CGPoint(x: rect.width, y: 0))
 path.addLine(to: CGPoint(x: rect.width - (rect.width * progress), y: 0))
 path.addLine(to: CGPoint(x: rect.width - (rect.width * progress), y: rect.height))
 path.addLine(to: CGPoint(x: rect.width, y: rect.height))
 path.closeSubpath()
 return path
}

然后在 path() 方法中調(diào)用上面兩個(gè)新方法。

func path(in rect: CGRect) -> Path {
 switch animationType {
 case .leftToRight:
  return leftToRight(rect: rect)
 case .rightToLeft:
  return rightToLeft(rect: rect)
 }
}

動(dòng)畫數(shù)據(jù)

為了確保 Swift 知道在更改 progress 變量時(shí)如何對(duì) Shape 進(jìn)行動(dòng)畫處理,我們需要指定一個(gè)響應(yīng)動(dòng)畫的變量。在 progress 和 animationType 變量下面,定義 animatableData 。這是一個(gè)基于 Animatable 協(xié)議 的變量,它可以通知 SwiftUI 在數(shù)據(jù)改變時(shí),對(duì)視圖進(jìn)行動(dòng)畫處理。

var progress: CGFloat
var animationType: SplashAnimation

var animatableData: CGFloat {
 get { return progress }
 set { self.progress = newValue}
}

 

顏色切換時(shí)產(chǎn)生動(dòng)畫

到目前為止,我們已經(jīng)創(chuàng)建了一個(gè) Shape ,它將隨著時(shí)間的變化而變化。接下來,我們需要將它添加到視圖中,并在視圖顏色改變時(shí)自動(dòng)對(duì)其進(jìn)行動(dòng)畫處理。這時(shí)候我們引入 SplashView 。我們將創(chuàng)建一個(gè) SplashView 來自動(dòng)更新 SplashShape 的 progress 變量。當(dāng) SplashView 接收到新的 Color 時(shí),它將觸發(fā)動(dòng)畫。

首先,我們創(chuàng)建 SplashView 結(jié)構(gòu)體。

import SwiftUI

struct SplashView: View {

 var body: some View {
 // SplashShape Here
 }
}

SplashShape 需要使用 SplashAnimation 枚舉作為參數(shù),所以我們會(huì)把它作為參數(shù)傳遞給 SplashView 。另外,我們要在視圖的背景顏色變化時(shí)設(shè)置動(dòng)畫,所以我們也要傳遞 Color 參數(shù)。這些細(xì)節(jié)會(huì)在我們的初始化方法中詳細(xì)說明。

ColorStore 是自定義的 ObservableObject。它用來監(jiān)聽 SplashView 結(jié)構(gòu)體中 Color 值的改變,以便我們可以初始化 SplashShape 動(dòng)畫,并最終改變背景顏色。我們稍后展示它的工作原理。

struct SplashView: View {
 
 var animationType: SplashShape.SplashAnimation
 @State private var prevColor: Color // Stores background color
 @ObservedObject var colorStore: ColorStore // Send new color updates

 
 init(animationType: SplashShape.SplashAnimation, color: Color) {
 self.animationType = animationType
 self._prevColor = State<Color>(initialValue: color)
 self.colorStore = ColorStore(color: color)
 }

 var body: some View {
 // SplashShape Here
 }

}

class ColorStore: ObservableObject {
 @Published var color: Color
 
 init(color: Color) {
 self.color = color
 }
}

構(gòu)建 SplashView body

在 body 內(nèi)部,我們需要返回一個(gè) Rectangle ,它和 SplashView 當(dāng)前的顏色保持一致。然后使用之前定義的 ColorStore ,以便于我們接收更新的顏色值來驅(qū)動(dòng)動(dòng)畫。

var body: some View {
 Rectangle()
 .foregroundColor(self.prevColor) // Current Color
 .onReceive(self.colorStore.$color) { color in
  // Animate Color Update Here
 }
}

當(dāng)顏色改變時(shí),我們需要記錄 SplashView 中正在改變的顏色和進(jìn)度。為此,我們定義 layers 變量。

@State var layers: [(Color,CGFloat)] = [] // New Color & Progress

現(xiàn)在回到 body 變量內(nèi)部,我們給 layers 變量添加新接收的 Colors 。添加的時(shí)候我們把進(jìn)度設(shè)置為 0 。然后,在半秒之內(nèi)的動(dòng)畫過程中,我們把進(jìn)度設(shè)置為 1 。

var body: some View {
 Rectangle()
 .foregroundColor(self.prevColor) // Current Color
 .onReceive(self.colorStore.$color) { color in
  // Animate Color Update Here
  self.layers.append((color, 0))
  
  withAnimation(.easeInOut(duration: 0.5)) {
  self.layers[self.layers.count-1].1 = 1.0
  }
 }
}

現(xiàn)在在這段代碼中, layers 變量中添加了更新后的顏色,但是顏色并沒有展示出來。為了展示顏色,我們需要在 body 變量內(nèi)部為 Rectangle 的每一個(gè)圖層添加一個(gè)覆蓋層。

var body: some View {
 Rectangle()
 .foregroundColor(self.prevColor)
 .overlay(
  ZStack {
  ForEach(layers.indices, id: \.self) { x in
   SplashShape(progress: self.layers[x].1, animationType: self.animationType)
   .foregroundColor(self.layers[x].0)
  }

  }

  , alignment: .leading)
 .onReceive(self.colorStore.$color) { color in
  // Animate color update here
  self.layers.append((color, 0))

  withAnimation(.easeInOut(duration: 0.5)) {
  self.layers[self.layers.count-1].1 = 1.0
  }
 }
}

測試效果

你可以在模擬器中運(yùn)行下面的代碼。這段代碼的意思是,當(dāng)你點(diǎn)擊 ContentView 中的按鈕時(shí),它會(huì)計(jì)算 index 來選擇 SplashView 中的顏色,同時(shí)也會(huì)觸發(fā) ColorStore 內(nèi)部的更新。所以,當(dāng) SplashShape 圖層添加到 SplashView 時(shí),就會(huì)觸發(fā)動(dòng)畫。

import SwiftUI

struct ContentView: View {
 var colors: [Color] = [.blue, .red, .green, .orange]
 @State var index: Int = 0
 
 @State var progress: CGFloat = 0
 var body: some View {
 VStack {
  
  SplashView(animationType: .leftToRight, color: self.colors[self.index])
  .frame(width: 200, height: 100, alignment: .center)
  .cornerRadius(10)
  .shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 4)
  
  Button(action: {
  self.index = (self.index + 1) % self.colors.count
  }) {
  Text("Change Color")
  }
  .padding(.top, 20)
 }
 
 }
}

 

還沒有完成!

我們還有一個(gè)功能沒實(shí)現(xiàn)。現(xiàn)在我們持續(xù)地把圖層添加到 SplashView 上,但是沒有刪除它們。因此,我們需要在動(dòng)畫完成時(shí)把這些圖層清理掉。

在 SplashView 結(jié)構(gòu)體 body 變量的 onReceive() 方法內(nèi)部,做如下改變:

.onReceive(self.colorStore.$color) { color in
 self.layers.append((color, 0))

 withAnimation(.easeInOut(duration: 0.5)) {
 self.layers[self.layers.count-1].1 = 1.0
 DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  self.prevColor = self.layers[0].0 // Finalizes background color of SplashView
  self.layers.remove(at: 0) // removes itself from layers array
 }
 }
}

這行代碼能讓我們刪除 layers 數(shù)組中使用過的值,并確保 SplashView 基于最新更新的值顯示正確的背景色。

展示成果!

 

GitHub 源碼

您可以在我的 Github 上查看本教程的源代碼 !除了顯示的示例外,還包括 SplashShape 和 SplashView 的完整源代碼。 ....但是等等,還有更多!

彩蛋!

如果你熟悉我之前的教程,你應(yīng)該了解我喜歡彩蛋 :wink:。在本文開頭,我說過會(huì)實(shí)現(xiàn)更多動(dòng)畫。此刻終于來了…… 擊鼓 ……。

Splash 動(dòng)畫

哈哈哈?。∵€記得嗎?我說過會(huì)添加更多動(dòng)畫種類。

enum SplashAnimation {
 case leftToRight
 case rightToLeft
 case topToBottom
 case bottomToTop
 case angle(Angle)
 case circle
}
func path(in rect: CGRect) -> Path {

 switch self.animationType {
 case .leftToRight:
  return leftToRight(rect: rect)
 case .rightToLeft:
  return rightToLeft(rect: rect)
 case .topToBottom:
  return topToBottom(rect: rect)
 case .bottomToTop:
  return bottomToTop(rect: rect)
 case .angle(let splashAngle):
  return angle(rect: rect, angle: splashAngle)
 case .circle:
  return circle(rect: rect)
 }

}

你肯定會(huì)想…… “哇, 彩蛋也太多了……” 。不必苦惱。我們只需要在 SplashShape 的 path() 方法中添加幾個(gè)方法,就能搞定。

下面我們逐個(gè)動(dòng)畫來搞定……

topToBottom 和 bottomToTop 動(dòng)畫

這些方法與 leftToRight 和 rightToLeft 非常相似,它們從 shape 的底部或頂部開始創(chuàng)建 path ,并使用 progress 變量隨時(shí)間對(duì)其進(jìn)行變換。

func topToBottom(rect: CGRect) -> Path {
 var path = Path()
 path.move(to: CGPoint(x: 0, y: 0))
 path.addLine(to: CGPoint(x: rect.width, y: 0))
 path.addLine(to: CGPoint(x: rect.width, y: rect.height * progress))
 path.addLine(to: CGPoint(x: 0, y: rect.height * progress))
 path.closeSubpath()
 return path
}

func bottomToTop(rect: CGRect) -> Path {
 var path = Path()
 path.move(to: CGPoint(x: 0, y: rect.height))
 path.addLine(to: CGPoint(x: rect.width, y: rect.height))
 path.addLine(to: CGPoint(x: rect.width, y: rect.height - (rect.height * progress)))
 path.addLine(to: CGPoint(x: 0, y: rect.height - (rect.height * progress)))
 path.closeSubpath()
 return path
}

 

circle 動(dòng)畫

如果你還記得小學(xué)幾何知識(shí),就應(yīng)該了解勾股定理。 a^2 + b^2 = c^2

a 和 b 可以視為矩形的 高度 和 寬度 ,我們能夠根據(jù)它們求得 c ,即覆蓋整個(gè)矩形所需的圓的半徑。我們以此為基礎(chǔ)構(gòu)建圓的 path,并使用 progress 變量隨時(shí)間對(duì)它進(jìn)行變換。

func circle(rect: CGRect) -> Path {
 let a: CGFloat = rect.height / 2.0
 let b: CGFloat = rect.width / 2.0

 let c = pow(pow(a, 2) + pow(b, 2), 0.5) // a^2 + b^2 = c^2 --> Solved for 'c'
 // c = radius of final circle

 let radius = c * progress
 // Build Circle Path
 var path = Path()
 path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: radius, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 360), clockwise: true)
 return path
}

 

angle 動(dòng)畫

這個(gè)動(dòng)畫知識(shí)點(diǎn)有點(diǎn)多。你需要使用切線計(jì)算角度的斜率,然后根據(jù)這個(gè)斜率創(chuàng)建一條直線。在矩形上移動(dòng)這條直線時(shí),根據(jù)它來繪制一個(gè)直角三角形。參見下圖,各種彩色的線表示該線隨時(shí)間移動(dòng)時(shí),覆蓋整個(gè)矩形的狀態(tài)。

方法如下:

func angle(rect: CGRect, angle: Angle) -> Path {
 
 var cAngle = Angle(degrees: angle.degrees.truncatingRemainder(dividingBy: 90))

 // Return Path Using Other Animations (topToBottom, leftToRight, etc) if angle is 0, 90, 180, 270
 if angle.degrees == 0 || cAngle.degrees == 0 { return leftToRight(rect: rect)}
 else if angle.degrees == 90 || cAngle.degrees == 90 { return topToBottom(rect: rect)}
 else if angle.degrees == 180 || cAngle.degrees == 180 { return rightToLeft(rect: rect)}
 else if angle.degrees == 270 || cAngle.degrees == 270 { return bottomToTop(rect: rect)}


 // Calculate Slope of Line and inverse slope
 let m = CGFloat(tan(cAngle.radians))
 let m_1 = pow(m, -1) * -1
 let h = rect.height
 let w = rect.width

 // tan (angle) = slope of line
 // y = mx + b ---> b = y - mx ~ 'b' = y intercept
 let b = h - (m_1 * w) // b = y - (m * x)

 // X and Y coordinate calculation
 var x = b * m * progress
 var y = b * progress

 // Triangle Offset Calculation
 let xOffset = (angle.degrees > 90 && angle.degrees < 270) ? rect.width : 0
 let yOffset = (angle.degrees > 180 && angle.degrees < 360) ? rect.height : 0

 // Modify which side the triangle is drawn from depending on the angle
 if angle.degrees > 90 && angle.degrees < 180 { x *= -1 }
 else if angle.degrees > 180 && angle.degrees < 270 { x *= -1; y *= -1 }
 else if angle.degrees > 270 && angle.degrees < 360 { y *= -1 }

 // Build Triangle Path
 var path = Path()
 path.move(to: CGPoint(x: xOffset, y: yOffset))
 path.addLine(to: CGPoint(x: xOffset + x, y: yOffset))
 path.addLine(to: CGPoint(x: xOffset, y: yOffset + y))
 path.closeSubpath()
 return path

}

 

總結(jié)

到此這篇關(guān)于SwiftUI使用Paths和AnimatableData實(shí)現(xiàn)酷炫的顏色切換動(dòng)畫的文章就介紹到這了,更多相關(guān)SwiftUI 顏色切換動(dòng)畫內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • iOS中Swift指觸即開集成Touch ID指紋識(shí)別功能的方法

    iOS中Swift指觸即開集成Touch ID指紋識(shí)別功能的方法

    隨著移動(dòng)支付時(shí)代的到來,Touch ID 指紋驗(yàn)證迅速被支付寶,微信錢包普及,相信各位朋友使用后也大呼方便。下面給大家分享iOS中Swift指觸即開集成Touch ID指紋識(shí)別功能的方法,一起看看吧
    2017-03-03
  • Swift中動(dòng)態(tài)調(diào)用實(shí)例方法介紹

    Swift中動(dòng)態(tài)調(diào)用實(shí)例方法介紹

    這篇文章主要介紹了Swift中動(dòng)態(tài)調(diào)用實(shí)例方法介紹,在Swift中有一類很有意思的寫法,可以讓我們不直接使用實(shí)例來調(diào)用這個(gè)實(shí)例上的方法,而是通過類型取出這個(gè)類型的某個(gè)實(shí)例方法的簽名,然后再通過傳遞實(shí)例來拿到實(shí)際需要調(diào)用的方法,需要的朋友可以參考下
    2015-01-01
  • Swift類和對(duì)象的底層探索分析

    Swift類和對(duì)象的底層探索分析

    這篇文章主要為大家介紹了Swift類和對(duì)象的底層探索分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • Swift?Error重構(gòu)的基礎(chǔ)示例詳解

    Swift?Error重構(gòu)的基礎(chǔ)示例詳解

    這篇文章主要為大家介紹了Swift?Error基礎(chǔ)錯(cuò)誤處理的方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • R.swift的使用與安裝教程

    R.swift的使用與安裝教程

    這篇文章主要給大家介紹了關(guān)于R.swift使用與安裝的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • Swift心得筆記之控制流

    Swift心得筆記之控制流

    控制流(Control Flow)我不想這么譯的。。。我更想叫控制語句,但是想想,這么叫也沒錯(cuò),意指流程控制。大部分用法跟C類似。
    2015-04-04
  • Swift泛型Generics淺析講解

    Swift泛型Generics淺析講解

    泛型代碼讓你能根據(jù)你所定義的要求,寫出可以用于任何類型的靈活的、可復(fù)用的函數(shù)。泛型是 Swift 最強(qiáng)大的特性之一,很多 Swift 標(biāo)準(zhǔn)庫是基于泛型代碼構(gòu)建的
    2022-08-08
  • Swift語言中字符串相關(guān)的基本概念解析

    Swift語言中字符串相關(guān)的基本概念解析

    這篇文章主要介紹了Swift語言中字符串相關(guān)的基本概念解析,是Swift入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2015-11-11
  • 關(guān)于Swift 4.1中的Codable改進(jìn)詳解

    關(guān)于Swift 4.1中的Codable改進(jìn)詳解

    這篇文章主要給大家介紹了關(guān)于Swift 4.1中的Codable改進(jìn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-02-02
  • swift實(shí)現(xiàn)隨機(jī)背景色

    swift實(shí)現(xiàn)隨機(jī)背景色

    這篇文章主要為大家詳細(xì)介紹了swift實(shí)現(xiàn)隨機(jī)背景色,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01

最新評(píng)論