動畫實作

透過 SwiftUI 實作一個新擬物化風格的切換按鈕動畫

開發者都很喜歡使用 SwiftUI 框架進行編程,因為它可以讓我們輕鬆為視圖變化設置動畫。在這篇文章中,Sarah 會帶大家在 SwiftUI 中,實作一個客製化新擬物風格切換按鈕,並為切換視圖製作動畫。
透過 SwiftUI 實作一個新擬物化風格的切換按鈕動畫
透過 SwiftUI 實作一個新擬物化風格的切換按鈕動畫
In: 動畫實作, SwiftUI 框架

本篇原文(標題:Neumorphic Animated Toggle in SwiftUI)刊登於作者 Medium,由 Sarah 所著,並授權翻譯及轉載。

在前一篇文章中,我們用了新擬物化設計風格 (neumorphic design principle),搭配 Michal Malewicz 的文章 Neumorphism in user interfaces 中所用的調色板,構建了一個音樂播放器。

你可以參閱這個連結,來閱讀完整教學和看看當時所用的顏色。

在這篇文章中,我們會一起在 SwiftUI 中,實作一個屬於你的新擬物化設計風格切換按鈕,並為切換視圖製作動畫。

在開始之前,請先把新擬物化顏色添加到 Asset,並為 3 種顏色建立一個結構 (struct)。

struct Colors {
    public static let mainColor = "mainColor"
    public static let lightShadow = "lightShadow"
    public static let darkShadow = "darkShadow"
}

凹視圖 (Concave View)

首先,讓我們創建一個凹視圖。我們會利用圓角矩形作為切換按鈕 (toggle) 視圖的形狀,並以 shadow(color, radius, x, y) 修飾符來創建內面的陰影:

  • color:使用 .fill() 修飾符。
  • Radius:使用 blur 修飾符。
  • x 和 y:使用 offset 修飾符。

因為我們想突出矩形的圓角,所以會用另一個圓角矩形作遮罩 (mask)。我們可以參考以下的程式碼,來創建一個淺 (shallow) 的凹視圖。我們可以為 roundedRectanglecornerRadius 創建一個變數,從客製化的切換按鈕視圖傳遞cornerRadius,來提高視圖客製化的彈性 。

淺的凹視圖:

struct ShallowConcaveView : View  {
    let cornerRadius : CGFloat
    
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: cornerRadius)
                .fill(Color(Colors.mainColor))
            RoundedRectangle(cornerRadius: cornerRadius)
                .stroke(Color(Colors.darkShadow), lineWidth: 2)
                .blur(radius: 0.5)
                .offset(x: 1, y: 0.5)
                .mask(RoundedRectangle(cornerRadius: cornerRadius).fill(LinearGradient(colors: [Color(Colors.darkShadow), Color.clear], startPoint: .top, endPoint: .bottom)))
            RoundedRectangle(cornerRadius: cornerRadius)
                .stroke(Color(Colors.lightShadow), lineWidth: 2)
                .blur(radius: 0.5)
                .offset(x: -1, y: -1.5)
                .mask(RoundedRectangle(cornerRadius: cornerRadius).fill(LinearGradient(colors: [Color.clear, Color(Colors.lightShadow)], startPoint: .top, endPoint: .bottom)))
        }
    }
}

如果我們想要實作一個深的凹視圖,可以再添加兩個圓角矩形,並把 offset 數值增加,比前面淺陰影的 offset 更大。

深的凹視圖:

struct DeepConcaveView : View  {
    let cornerRadius : CGFloat
    
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: cornerRadius)
                .fill(Color(Colors.mainColor))
            RoundedRectangle(cornerRadius: cornerRadius)
                .stroke(Color(Colors.darkShadow), lineWidth: 2)
                .blur(radius: 0.5)
                .offset(x: 1, y: 1)
                .mask(RoundedRectangle(cornerRadius: cornerRadius).fill(LinearGradient(colors:[Color(Colors.darkShadow), Color.clear], startPoint: .top, endPoint: .bottom)))
            RoundedRectangle(cornerRadius: cornerRadius)
                .stroke(Color(Colors.darkShadow), lineWidth: 6)
                .blur(radius: 3)
                .offset(x: 3, y: 3)
                .mask(RoundedRectangle(cornerRadius: cornerRadius).fill(LinearGradient(colors: [Color(Colors.darkShadow), Color.clear], startPoint: .top, endPoint: .bottom)))
            RoundedRectangle(cornerRadius: cornerRadius)
                .stroke(Color(Colors.lightShadow), lineWidth: 2)
                .blur(radius: 0.5)
                .offset(x: -1, y: -1)
                .mask(RoundedRectangle(cornerRadius: cornerRadius).fill(LinearGradient(colors: [Color.clear, Color(Colors.lightShadow)], startPoint: .top, endPoint: .bottom)))
            RoundedRectangle(cornerRadius: cornerRadius)
                .stroke(Color(Colors.lightShadow), lineWidth: 6)
                .blur(radius: 3)
                .offset(x: -3, y: -3)
                .mask(RoundedRectangle(cornerRadius: cornerRadius).fill(LinearGradient(colors: [Color.clear, Color(Colors.lightShadow)], startPoint: .top, endPoint: .bottom)))
        }
    }
}

客製化切換按鈕動畫

在這個部分,我們會完成切換按鈕視圖,並為視圖製作動畫。首先,讓我們來設定會傳遞到 ContentView 的常數變數 (constant variable):

  • width:切換按鈕視圖的寬度
  • height:切換按鈕視圖的高度
  • toggleWidthOffset:切換按鈕的 roundedRectangle 的寬度
  • cornerRadius:用來客製化所有圓角矩形的圓度 (roundness)
  • padding:用來客製化切換按鈕與凹視圖的間距
  • isToggled:用來將切換按鈕打開或關閉
  • switchWidth:拉長或縮短按鈕,來製作打開或關閉按鈕的動畫

讓我們在 ZStack 中添加其中一個凹視圖,然後在 HStack 中添加 “on” 和 “off” 文本、或符號。

接著,讓我們來實作切換的開關。添加一個圓角矩形,把框架設置為等於或小於按鈕的高度,我們可以使用 onAppear 來設定 switchWidth 大小。然後,為圓角矩形添加陰影,讓開關凸出。

.onTapGesture() 中,把 isToggled 更改為相反的數值,並為 switchWidth 設置兩次動畫。在動畫中,開關會拉長及縮短,然後在第二個動畫結束時恢復原來的大小。

接下來,我們要添加一個由 isToggled 數值控制的 Spacer(),把開關向左或向右移動。最後,讓我們添加一個 animation( .spring) 到 roundedRectangle

struct CustomToggle : View  {
    let width : CGFloat
    let height : CGFloat
    let toggleWidthOffset : CGFloat
    let cornerRadius : CGFloat
    let padding : CGFloat
    
    @State var isToggled = false
    @State var switchWidth : CGFloat = 0.0
    
    var body: some View {
        ZStack {
            
            DeepConcaveView(cornerRadius: cornerRadius)
                .frame(width: width, height: height)
            
            HStack {
                Text("ON")
                    .bold()
                    .foregroundColor(.green)
                
                Spacer()
                
                Text("OFF")
                    .bold()
                    .foregroundColor(.red)
                
            }
            .padding()
            .frame(width: width, height: height)
            
            HStack {
                if isToggled {
                    Spacer()
                }
                
                
                RoundedRectangle(cornerRadius: cornerRadius)
                    .padding(padding)
                    .frame(width: switchWidth + toggleWidthOffset, height: height)
                    .animation(.spring(response: 0.5), value: isToggled)
                    .foregroundColor(Color(Colors.mainColor))
                    .shadow(color: Color(Colors.lightShadow), radius: 2, x: -3, y: -3)
                    .shadow(color: Color(Colors.darkShadow), radius: 3, x: 3, y: 3)
                
                
                if !isToggled {
                    Spacer()
                    
                }
            }
            
        }
        .frame(width: width, height: height)
        .onTapGesture {
            isToggled = !isToggled
            withAnimation(.easeInOut(duration: 0.2)) {
                switchWidth = width
            }
            withAnimation(.easeInOut(duration: 0.4)) {
                switchWidth = height
            }
        }
        .onAppear {
            switchWidth = height
        }
        
    }
}

ContentView

我們需要把背景顏色與切換按鈕視圖的主顏色相同,這樣才可以符合新擬物化設計風格的要求。填入自己想要的數值,來添加客製化的切換按鈕。讓我們看看不同數值的例子:

有間距的按鈕

struct ContentView: View {
    var body: some View {
        ZStack {
            Color(Colors.mainColor)
            CustomToggle(width: 170, height: 60, toggleWidthOffset: 20, cornerRadius: 30, padding: 10)
        }
    }
}
swiftui-Padded Switch

沒有間距的按鈕

struct ContentView: View {
    var body: some View {
        ZStack {
            Color(Colors.mainColor).ignoresSafeArea()
            CustomToggle(width: 150, height: 60, toggleWidthOffset: 20, cornerRadius: 30, padding: 0)
        }
    }
}
swiftui-No padding

方型的按鈕

struct ContentView: View {
    var body: some View {
        ZStack {
            Color(Colors.mainColor).ignoresSafeArea()
            CustomToggle(width: 200, height: 80, toggleWidthOffset: 20, cornerRadius: 10, padding: 10)
        }
    }
}
swiftui-squarish

ShallowConcaveView 的例子

謝謝你的閱讀。

本篇原文(標題:Neumorphic Animated Toggle in SwiftUI)刊登於作者 Medium,由 Sarah 所著,並授權翻譯及轉載。

作者簡介:Sarah,App 開發者,熱愛科技。

譯者簡介:Kelly Chan-AppCoda 編輯小姐。

作者
AppCoda 編輯團隊
此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。
評論
更多來自 AppCoda 中文版
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。