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 向上弹起又落下,下面是动画曲线大致的样子:

三角函数是 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)
  }
}

实际效果如下:

到此这篇关于SwiftUI 中创建反弹动画的实现的文章就介绍到这了,更多相关SwiftUI 反弹动画内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • iOS swift实现转场动画的方法示例

    转场动画介绍 转场动画在我们日常开发中是经常遇到的,所谓转场动画,就是一个控制器的view切到另一个控制器的view上过程中过的动画效果.本例子是实现了在导航控制器的titleView边上慢慢弹出一个控制器.下面话不多说,来一起看看详细的介绍: 效果图: 专场前 专场后 示例代码 首先自定义一个animator类.在需要转场的控制器内,设置代理 //需要设置转场动画的控制器titleViewVc.transitioningDelegate = aniamator//这里的animator是ani

  • Swift语言实现地图坐标弹跳动画

    下面应用Swift语言做地图坐标实现弹簧跳动的效果,具体实现方法如下所示: iOS动画大概分这几种: CoreAnimation动画(基础)UIView动画(animateWithDuration开头的函数,带弹簧特效)UIDynamic动画(有物理引擎) 上次写弹跳动画,由于刚开始学,就用了最基础的CA动画,但是因为自己不会写KeyFrame,所以还引用了一个开源库来实现下坠的弹跳效果,这样固然是实现了需求,但是一点都不优雅--从iOS7开始,有了2.3两种动画,就不再需要用老的CA动画去一点

  • 利用Swift实现各类的CATransition动画详解

    前言 最近因为工作的需要,打算用swift重构现有项目,实现过程中发现用到了很多系统的动画实现,因为之前的都是用OC实现的,这里主要是把一些动画效果来通过swift实现一下. 这篇文章主要是实现一下CATransition动画的不同效果. 第一步 创建一个swift文件并声明相应的枚举类型 enum TransitionAnimType : Int { case fade = 0, //淡入淡出 push, //推挤 reveal, //揭开 moveIn, //覆盖 cube, //立方体 s

  • Swift4.1转场动画实现侧滑抽屉效果

    实现使用了Modal转场动画,原因是项目多由导航控制器和标签控制器作为基类,为了不影响导航控制器的代理,转场动画使用模态交互. 代码使用SnapKit进行布局,能够适应屏幕旋转.手势速率大于300或进度超过30%的时候直接完成动画,否则动画回滚取消,具体数值可以修改对应的常量.抽屉出现的时候,主控制有遮罩,对应关键字是mask. 实现文件只有两个 DrawerControl:控制抽屉出现,一行代码即可调用 Animator:负责动画实现,包括了交互式的代理事件和非交互式的代理事件 // // D

  • Swift洗牌动画效果的实现方法

    目标效果: 点击动画按钮之后每张牌各自旋转 散开到屏幕上半部分的任意位置之后回到初始位置 比较像LOL男刀的技能动画 : ) 1: 创建卡牌对象 for _ in 0...49 { let cardSet = UIImageView(image: UIImage(named: "cardBackLandscape")) self.view.addSubview(cardSet) cardSet.frame = self.landscapeCardBack.frame self.card

  • iOS Swift控制器转场动画示例代码

    前言 在IOS开发中,我们model另外一个控制器的时候,一般都使用默认的转场动画.本文将给大家详细介绍关于iOS Swift控制器转场动画的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 返回效果也可更改 四种转场动画 1. move:源图片位置移动到目标图片位置: 2. circle:根据源控件大小创建圆形或者椭圆形path路径,放大展示目标: 3. tier:源左右,目标由小到大缩放: 4. middle:源的中心点开始放大,返回是缩回到中心. 代码解析 给UI

  • IOS swift中的动画的实例详解

    IOS swift中的动画的实例详解 UIView的通用动画 let view = UIView(frame: CGRectMake(10.0, 10.0, 100.0, 40.0)) self.view.addSubview(view) view.backgroundColor = UIColor.lightGrayColor() // 位置改变 var frame = view.frame UIView.animateWithDuration(0.6, delay: 2.0, options

  • swift3.0实现图片放大缩小动画效果

    一. 内容说明 跟我之前这篇类似,只不过那篇是OC版本,这篇是Swift版本 OC版本链接地址 目的:通过kingfisher请求5张图片,展示出来.然后利用图片放大缩小管理类展示图片,多张图片可以滑动浏览 效果图如下,想看动态的效果图,请看上面链接中的OC版本效果图,跟这篇是一样的. 本demo,只加载本地图片的demo下载链接 ,需要加载网络图片的,需要下载Kingfisher 二.源码展示 0. 图片测试demo源码 import Foundation import UIKit class

  • SwiftUI 中创建反弹动画的实现

    SwiftUI 中的动画 在写动画之前呢先简单回顾一下 SwiftUI 中动画的几个要点: 动画是 view 发生变化时的渐变效果 SwiftUI 动画分为隐式动画( .animation() )与显式动画( withAnimation() )两种 隐式动画是给 view 加动画,view 所有的能动画的变化都能被隐式动画影响 显式动画是针对某个变化进行动画,能精准控制. view 的插入和移除通过过渡( transition )来做效果,可以组合多个过渡或自定义过渡 要构建自定义动画,我们需要

  • 详解在Python中创建条形图追赶动画

    目录 前言 方法一:使用pause()函数 方法二:使用FuncAnimation()函数 线性图动画 Python中的条形图追赶动画 Python中的散点图动画: 条形图追赶的水平移动 前言 动画是使可视化更具吸引力和用户吸引力的好方法.它帮助我们以有意义的方式展示数据可视化.Python 帮助我们使用现有的强大 Python 库创建动画可视化.Matplotlib是一个非常流行的数据可视化库,通常用于数据的图形表示以及使用内置函数的动画. 使用 Matplotlib 创建动画有两种方法: 使

  • JavaScript反弹动画效果的实现代码

    代码如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> #box{ width:200px; height:200px; position: absolute; top:0; left:200px; background:lightblue

  • SwiftUI中TabView组件的常规使用

    目录 前言 TabView常规用法1 TabView常规用法2 TabView常规用法3 TabView常规用法4---做轮播图 总结 前言 在UIKit中设置多个tabbar展示需要使用到UITabBarController 在SwiftUI中 由TabView组件来进行实现,同时TabView也可以实现PageViewController的效果, TabView常规用法1 import SwiftUI struct ZTMinePageView: View { var body: some

  • 在AngularJS应用中实现一些动画效果的代码

    在Angular当中,CSS和JavaScript之间唯一的区别就是它们的定义.没有什么区别妨碍到被定义的动画被使用.首先,我们需要加载ngAnimate模块到我们应用的root模块当中. angular.module('coursesApp', ['ngAnimate']); 而所有将被处理的JavaScript动画事件依然保持不变.以下是一个直接支持的动画列表和它们对应的不同行为: 指令事件集 ng-view ng-include ng-switch ng-if  enter leave n

  • Android中显示GIF动画的实现代码

    本文实例讲述了Android中显示GIF动画的实现代码.分享给大家供大家参考,具体如下: gif图动画在android中还是比较常用的,比如像新浪微博中,有很多gif图片,而且展示非常好,所以我也想弄一个.经过我多方的搜索资料和整理,终于弄出来了,其实github上有很多开源的gif的展示代码,我下载过几个,但是都不是很理想,不是我完全想要的.所以有时候就得自己学会总结,把开源的东西整理成自己的,现在无聊,也正好有朋友需要,所以现在整理了一下,留着以后备用! 废话不多说,直接上图: 在这里主要用

  • 用Move.js配合创建CSS3动画的入门指引

    在网站上,CSS3 的过渡和动画是当前创建轻量级动画的首选方法.不幸的是,很多开发者发现他们自己的语法和复杂和混乱的.如果这听起来像你自己,对你来说,或许Move.js是完美的方案.Move.js是使用简单函数创建 CSS3 动画的一个简单的JavaScript库.本教程将探讨Move.js的基础知识,并提供一个在线demo. 基础知识 Move.js 提供了创建 CSS3 动画的最简单的 JavaScript API.让我们假设有一个带类 box 的 div 元数,当鼠标 移动到 div 上面

  • Android中编写属性动画PropertyAnimation的进阶实例

    0.基础回顾 PropertyAnimation,属性动画,顾名思义就是利用对象的属性变化形成动画的效果.属性动画的类可以用Animator这个抽象类来表示,通常使用它的子类:AnimatorSet和ValueAnimator,同时ValueAnimator有两个子类分别是ObjectAniamtor和TimeAnimator. 定义属性动画的XML资源的时候通常可以是如下三个元素之一作为根元素: <set>元素:该资源元素代表的是AniamtorSet类,这个类可以包含<set>

  • Android中创建类似Instagram的渐变背景效果

    我在我最近的项目用到这个效果,给大家分享下 https://github.com/zhaoweihaoChina/hnuplus 1. 在drawable文件夹创建一些渐变颜色的资源 color1.xml <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <

  • 使用Angular 6创建各种动画效果的方法

    就技术角度而言,动画可以被定义为从初始状态到最终状态的转换过程.如今它已是各种Web应用不可或缺的组成部分.通过动画,我们不仅能创建出各种酷炫的UI,同时它们也能增加应用程序的趣味性.因此,设计精美的动画在吸引用户眼球的同时,也增强了他们的浏览体验. Angular能够让我们创建出具有原生表现效果的动画.我们将通过本文学习到如何使用Angular 6来创建各种动画效果.在此,我们将使用Visual Studio Code来进行示例演示. 准备工作 安装Visual Studio Code和Ang

随机推荐