中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SwiftUI 中實現創建反彈動畫

發布時間:2020-11-02 15:59:17 來源:億速云 閱讀:180 作者:Leah 欄目:開發技術

SwiftUI 中實現創建反彈動畫?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

SwiftUI 中的動畫

在寫動畫之前呢先簡單回顧一下 SwiftUI 中動畫的幾個要點:

  • 動畫是 view 發生變化時的漸變效果
  • SwiftUI 動畫分為隱式動畫( .animation() )與顯式動畫( withAnimation() )兩種
  • 隱式動畫是給 view 加動畫,view 所有的能動畫的變化都能被隱式動畫影響
  • 顯式動畫是針對某個變化進行動畫,能精準控制。
  • view 的插入和移除通過過渡( transition )來做效果,可以組合多個過渡或自定義過渡
  • 要構建自定義動畫,我們需要實現一個可動畫的 view 修飾器(遵守 AnimatableModier 協議)或者實現一個 GeometryEffect ,并將可動畫的屬性通過 animatableData 暴露出來

 反彈動畫

反彈動畫屬于“起始點和終止點相等”的動畫,所以不能夠通過 SwiftUI 中內建的動畫來實現(因為這個 view 從結果來看沒有發生變化)

我們先來構建反彈動畫修飾器的框架如下:

struct Bounce: AnimatableModifier {
  var animCount: CGFloat = 0
  var amplitude: CGFloat = 10  // 振幅
  
  var animatableData: CGFloat {
    get { animCount }
    set { animCount = newValue }
  }
  
  func body(content: Content) -> some View {
    // change view to animate
  }
}

下面一步一步來

實現動畫曲線

實現 body 方法,好讓 times 每次增加時 view 能以反彈的方式進行動畫。一次反彈就是 view 向上彈起又落下,下面是動畫曲線大致的樣子:

SwiftUI 中實現創建反彈動畫

三角函數是 y = -abs(sin x) ,所以 body 方法這樣實現:

struct Bounce: AnimatableModifier {
  ...
  func body(content: Content) -> some View {
    let offset: CGFloat = -abs(sin(animCount * .pi * 2) * amplitude)
    return content.offset(y: offset)
  }
}

測試代碼:

struct BouncingView: View {
  @State var taps = 0
  
  var body: some View {
    Button(action: {
      withAnimation(Animation.easeInOut(duration: 1)) {
        taps += 1
      }
    }, label: {
      RoundedRectangle(cornerRadius: 15)
        .modifier(Bounce(animCount: CGFloat(taps)))
    })
    .frame(width: 100, height: 100)
  }
}

點擊按鈕,就能彈兩次了~~

這個 ​ @State var taps

​ 其實并沒有什么實際的意義,只是為了觸發動畫。

因為 SwiftUI 里只有 View 的狀態發生變化才會觸發動畫,而無法通過某個事件來觸發;而我們的動畫是一個初始狀態和結束狀態相等的情況,并沒有狀態的變化,所以這里強行把點擊的次數作為 View 狀態的變化來觸發動畫。

我找了好久有沒有更優雅的方式來解決這個問題,然而并沒有找到 = =b

View 擴展

暴露給外面的 ​ animCount ​ 應該是一個 Int 才對,那么就增加一個 ​ ​animValue ​ 來代替它

struct Bounce: AnimatableModifier {
  let animCount: Int
  var animValue: CGFloat
  var amplitude: CGFloat = 10 // 振幅
  
  init(animCount: Int) {
    self.animCount = animCount
    self.animValue = CGFloat(animCount)
  }
  
  var animatableData: CGFloat {
    get { animValue }
    set { animValue = newValue }
  }
  
  func body(content: Content) -> some View {
    let offset: CGFloat = -abs(sin(animValue * .pi * 2) * amplitude)
    return content.offset(y: offset)
  }
}

因為 ​ ​animValue ​ 被隱藏起來了,所以需要初始化方法

為了方便使用,再添加一個 View 的擴展方法:

extension View {
  func bounce(animCount: Int) -> some View {
    self.modifier(Bounce(animCount: animCount))
  }
}

增加彈性

現在雖然能彈了,但是相對還比較生硬,就想在 View “落地“后再給它增加振幅逐步衰減的幾次反彈;一開始嘗試了簡單的減半反彈,實驗證明觀感更差,看起來有點難受。

我們實際生活中的反彈的振幅變化應該是符合 阻尼正弦波 的,參考鏈接里的公式,修改如下:

struct Bounce: AnimatableModifier {
  let animCount: Int
  var animValue: CGFloat
  var amplitude: CGFloat // 振幅
  var bouncingTimes: Int
  
  init(animCount: Int, amplitude: CGFloat = 10, bouncingTimes: Int = 3) {
    self.animCount = animCount
    self.animValue = CGFloat(animCount)
    self.amplitude = amplitude
    self.bouncingTimes = bouncingTimes
  }
  
  var animatableData: CGFloat {
    get { animValue }
    set { animValue = newValue }
  }
  
  func body(content: Content) -> some View {
    let t = animValue - CGFloat(animCount)
    let offset: CGFloat = -abs(pow(CGFloat(M_E), -t) * sin(t * .pi * CGFloat(bouncingTimes)) * amplitude)
    return content.offset(y: offset)
  }
}

extension View {
  func bounce(animCount: Int,
        amplitude: CGFloat = 10,
        bouncingTimes: Int = 3) -> some View {
    self.modifier(Bounce(animCount: animCount,
               amplitude: amplitude,
               bouncingTimes: bouncingTimes))
  }
}

這里我們還增加了 ​ bouncingTimes ​ 作為彈跳次數的參數,振幅也作為參數開放給用戶;

由于阻尼正弦波是逐步衰減的,為了每次點擊的彈跳都一樣,所以得用 ​ ​animValue - CGFloat(animCount) ​ 作為阻尼正弦波函數的參數。

測試代碼修改如下:

struct BouncingView: View {
  @State var taps = 0
  
  var body: some View {
    Button(action: {
      withAnimation(Animation.linear(duration: 1)) {
        taps += 1
      }
    }, label: {
      RoundedRectangle(cornerRadius: 15)
        .bounce(animCount: taps)
    })
    .frame(width: 100, height: 100)
  }
}

看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

连州市| 青神县| 科技| 东平县| 罗田县| 遂溪县| 黔西县| 布拖县| 安乡县| 肇源县| 融水| 永德县| 湖州市| 天祝| 云和县| 大余县| 安泽县| 交城县| 客服| 雷波县| 无锡市| 介休市| 西畴县| 瑞安市| 寻乌县| 肥东县| 鲁山县| 遂昌县| 西昌市| 福海县| 蒲江县| 新乡市| 定西市| 卓尼县| 庆阳市| 浦东新区| 东平县| 黎平县| 喜德县| 柏乡县| 云和县|