【SwiftUI模块】0033、SwiftUI创建用户双击帖子时的心形动画

SwiftUI模块系列 - 已更新33篇
SwiftUI项目 - 已更新3个项目
往期Demo源码下载

技术:SwiftUI、SwiftUI4.0、双击动画、心形动画、动画
运行环境:
SwiftUI4.0 + Xcode14 + MacOS12.6 + iPhone Simulator iPhone 14 Pro Max

概述

使用SwiftUI创建用户双击帖子时的心形动画

详细

一、运行效果

请添加图片描述

二、项目结构图

在这里插入图片描述

三、程序实现 - 过程

思路:

  1. 构建列表滚动模块 - 使用ScrollView搭建
  2. 构建双击心形动画 HeartLike - 封装起来 包含点击的次数、是否点击、背景动画、是否重置动画、是否烟花动画、动画是否结束、动画是否完成等参数

1.创建一个项目命名为 SpotifyResponvieUI

在这里插入图片描述
在这里插入图片描述

1.1.引入资源文件和颜色

2. 创建一个虚拟文件New Group 命名为 View

在这里插入图片描述
在这里插入图片描述

3. 创建一个虚拟文件New Group 命名为 Model

在这里插入图片描述
在这里插入图片描述

4. 创建一个文件New File 选择SwiftUI View类型 命名为Post 删除预览图 并且继承Identifiable 作为模型

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

5. 创建一个文件New File 选择SwiftUI View类型 命名为Home

主要是: 展示 列表视图 和 双击时的心形烟花释放动画

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

Code

ContentView - 主窗口

主要是展示主窗口Home

//
//  ContentView.swift
//  LikedAnimation
//
//  Created by 李宇鸿 on 2022/9/27.
//

import SwiftUI

struct ContentView: View {
    
    
    var body: some View {
    
    
        Home()
    }
}

struct ContentView_Previews: PreviewProvider {
    
    
    static var previews: some View {
    
    
        ContentView()
    }
}

Home - 主页

思路

  1. 构建列表滚动模块 - 使用ScrollView搭建
  2. 构建双击心形动画 HeartLike - 封装起来 包含点击的次数、是否点击、背景动画、是否重置动画、是否烟花动画、动画是否结束、动画是否完成等参数
//
//  Home.swift
//  LikedAnimation
//
//  Created by 李宇鸿 on 2022/9/27.
//

import SwiftUI

struct Home: View {
    
    
  
    // 
    @State var posts : [Post] = [
        Post(imageName: "Pic1"),
        Post(imageName: "Pic2"),
        Post(imageName: "Pic3"),
        Post(imageName: "Pic4"),
        Post(imageName: "Pic5"),

    ]
    
    var body: some View {
    
    

        NavigationView{
    
    
            ScrollView(.vertical,showsIndicators: false) {
    
    
                VStack(alignment: .leading,spacing:16) {
    
    
                    
                    ForEach(posts){
    
    post in
                        VStack(alignment: .leading,spacing: 12) {
    
    
                            GeometryReader{
    
    proxy in
                                Image(post.imageName)
                                    .resizable()
                                    .aspectRatio(contentMode: .fill)
                                    .cornerRadius(15)
                            }
                            .frame(height:280)
                            .overlay(
                                HeartLike(isTapped:$posts[getIndex(post: post)].isLiked,taps: 2)
                            )
                            .cornerRadius(15)
                            
                            Button  {
    
    
                                posts[getIndex(post: post)].isLiked.toggle()
                            } label: {
    
    
                                Image(systemName: post.isLiked ? "suit.heart.fill" : "suit.heart")
                                    .font(.title2)
                                    .foregroundColor(post.isLiked ? .red : .gray)
                            }

                        }
                    }
                }
                .padding()
                
                
            }
            .navigationTitle("Heart Animation")
        }
    }
    
    
    // 获取索引
    func getIndex(post : Post)-> Int {
    
    
        let index = posts.firstIndex{
    
     currentPost in
            return currentPost.id == post.id
        } ?? 0
        
        return index
    }
}

struct Home_Previews: PreviewProvider {
    
    
    static var previews: some View {
    
    
        ContentView()
    }
}


struct HeartLike:View {
    
    
    // 在弹出的菜单动画……
    @Binding var isTapped : Bool
    
    @State var startAnimation = false
    
    @State var bgAnimation = false
    
    // 重置Bg……
    @State var resetBG = false
    // 烟花动画
    @State var fireworkAnimation = false
    // 动画是否结束
    @State var animationEnded = false
    
    // 避免在动画中轻拍…
    @State var tapComplete = false
    
    
    // 设置多少次点击…
    var taps : Int = 1
    
    var body: some View {
    
    
        // 心如动画……
        Image(systemName: resetBG ? "suit.heart.fill" : "suit.heart")
            .font(.system(size: 45))
            .foregroundColor(resetBG ? .red : .gray)
        // 扩展……
            .scaleEffect(startAnimation && !resetBG ? 0 : 1)
            .opacity(startAnimation && !animationEnded ? 1 : 0)
            .background(
                ZStack{
    
    
                    CustomShape(radius: resetBG ? 29 : 0)
                        .fill(Color.purple)
                        .clipShape(Circle())
                        // Fixed Size
                        .frame(width:50,height:50)
                        .scaleEffect(bgAnimation ? 2.2 : 0)
                    
                    ZStack{
    
    
                        // 烟花的随机颜色
                        let colors : [Color] = [.red,.purple,.green,.yellow,.pink]
                        
                        ForEach(1...6,id:\.self){
    
     index in
                            Circle()
                                .fill(colors.randomElement()!)
                                .frame(width: 8,height: 8)
                                .offset(x: fireworkAnimation ? 64 : 24)
                                .rotationEffect(.init(degrees: Double(index) * 60))
                            
                        }
                        
                        
                        ForEach(1...6,id:\.self){
    
     index in
                            Circle()
                                .fill(colors.randomElement()!)
                                .frame(width: 12,height: 12)
                                .offset(x: fireworkAnimation ? 80 : 40)
                                .rotationEffect(.init(degrees: Double(index) * 60))
                                .rotationEffect(.init(degrees: -45))
                            
                        }
                    }
                    .opacity(resetBG ? 1 : 0)
                    .opacity(animationEnded ? 0 : 1)
                    
                }
            )
            .frame(maxWidth: .infinity,maxHeight: .infinity,alignment: .center)
            .contentShape(Rectangle())
            .onTapGesture(count:taps) {
    
    
                
                if tapComplete{
    
    
                    updateFields(value: false)
                    return
                }
                
                // 动画正在执行 就不执行
                if startAnimation {
    
    
                    return
                }
                
                
                isTapped = true
                withAnimation(.interactiveSpring(response: 0.5,dampingFraction: 0.6,blendDuration: 0.6)){
    
    
                    startAnimation = true
                }
                
                // 顺列动画……
                // 链动画……
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.25){
    
    
                    
                    withAnimation(.interactiveSpring(response: 0.4,dampingFraction: 0.5,blendDuration: 0.5)){
    
    
                        bgAnimation = true
                    }
                    
                    
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.3){
    
    
                        
                        withAnimation(.interactiveSpring(response: 0.5,dampingFraction: 0.6,blendDuration: 0.6)){
    
    
                            
                            resetBG = true
                        }
                        
                        // 烟花……
                        withAnimation(.spring()) {
    
    
                            fireworkAnimation = true
                        }
                        
                        DispatchQueue.main.asyncAfter(deadline: .now() + 0.4){
    
    
                            withAnimation(.easeOut(duration: 0.4)) {
    
    
                                animationEnded = true
                            }
                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.3){
    
    
                                tapComplete = true
                            }
                        }
                    }
                }
            }
            .onChange(of: isTapped) {
    
     newValue in
                if isTapped && !startAnimation {
    
    
                    // 设置一切为真…
                    updateFields(value: true)

                }
            }
    }
    
    // 更新动画字段
    func updateFields(value: Bool){
    
    
        
        startAnimation = value
        bgAnimation = value
        resetBG = value
        fireworkAnimation = value
        animationEnded = value
        tapComplete = value
        isTapped = value
    }
}


//自定义形状
//从中心重置…
struct CustomShape : Shape {
    
    
    
    // value...
    var radius : CGFloat
    
    
    // 动画路径…
    var animatableData: CGFloat{
    
    
        get {
    
    return radius}
        set {
    
    radius = newValue}
    }
    
    // 可动画路径在预览时无效
    func path(in rect: CGRect) -> Path {
    
    
        return Path {
    
    path in
         
            path.move(to: CGPoint(x: 0, y: 0))
            path.addLine(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: 0))

            // 增加中心圆……
            let center = CGPoint(x: rect.width / 2, y: rect.height / 2)
            path.move(to: center)
            path.addArc(center: center, radius: radius, startAngle: .zero, endAngle: .init(degrees: 360), clockwise:false)
        }
    }
}

Post - 模型

//
//  Post.swift
//  LikedAnimation
//
//  Created by 李宇鸿 on 2022/9/27.
//

import SwiftUI

// Post Model...
struct Post : Identifiable {
    
    
    
    var id = UUID().uuidString
    var imageName : String
    var isLiked : Bool = false
    
}

猜你喜欢

转载自blog.csdn.net/qq_42816425/article/details/126862148