自定义 UIViewController 转换动画: 开始

原文:Custom UIViewController Transitions: Getting Started
作者:Richard Critz
译者:kmyhy

更新说明: 本教程由 Richard Critz 更新至 iOS11 和 Swift 4。原文作者是 József Vesza。

iOS 内置了一些好看的 View Controller 转换动画——push、pop、cover vertically——这些都是现成的,但创建自己的动画岂不更有趣呢?自定义 UIViewController 转换能大大地提高用户体验,并让你的 app 明显超出其它 app 一大截。如果你曾经因为这个过程太难而不愿意自定义转换动画,你会发现其实它并没有你想象中的那么难。

在本教程中,我们将为一个简单的猜谜游戏添加自定义 UIViewController 转换动画。在最后,你将学习到:

  • Transitioning API 的结构
  • 如何用自定义转换动画呈现、解散 View Controller
  • 如何构建交互式转换

注意:本教程中演示的转换动画使用的是 UIView 动画,你需要对此有所了解。如果你需要帮助,请参考我们的 iOS Animation 以便快速进入我们的主题。

开始

下载开始项目。Build & run,你会看到:

这个 app 用一个 page view controller 来展现几个不同的卡片。每张卡片显示一段关于宠物的描述,当你点击一张卡片显示它所描述的是什么样的宠物。

你的任务是猜猜这是什么宠物?猫、狗、还是鱼?试完一下 app,看看你猜得准不准?

导航逻辑是写好的,但 app 给人的感觉平淡无奇。我们将通过自定义转换动画来为它增添一些色彩。

介绍 Tansitioning API

Transitioning API 是一个协议集。它允许你为你的 app 选择最合适的一种实现方式:使用负责管理转换动画的现成对象或者创建专门的对象。这一节结束,你将了解每个协议的作用及其相互间的关联。下图显示了 API 的组成部分:

组成部分

尽管图很复杂,但一旦你理解如何将各部分组装起来之后就会变得很简单了。

Transitioning Delegate

每个 view controller 都有一个 transitioningDelegate 属性,这个对象实现了 UIViewControllerTransitioningDelegate 协议。

当你呈现或解散一个 view controller 时,UIKit 会询问 transitioning delegate 对象要使用哪一个 animation controller。要将默认的动画替换成你自己的动画,你必须实现一个 transitioning delegate 并通过它返回一个特定的动画控制器。

Animation Controller

transitioning delegate 对象所返回的 animation controller 对象则实现了 UIViewControllerAnimatedTransitioning 协议。它负责实现转换动画的“重体力活”。

Transitioning Context

Transitioning contenxt 对象实现了 UIViewControllerContextTransitioning 协议并负责转换过程中的一个重要角色:它封装了和动画相关的视图和视图控制器的信息。

如你在上图中所见,你不需要自己实现这个协议。UIKit 会为你创建和配置 transitioning context 并在动画发生时传递给你的 animation controller。

转换动画的工作流程

在呈现动画中包括:

  1. 触发动画,无论是以编码方式还是 segue 方式。
  2. UIKit 会询问 “to” view controlle(即将呈现的 view controller)它的 transitioning delegate 是谁。如果没有提供,UIKit 会使用标准的、内置的转换动画。
  3. 然后 UIKit 会通过 animationController(forPresented:presenting:source:) 方法向 transitioning delegate 对象索要一个 animation controller。如果返回 nil,转换过程将使用默认的动画。
  4. UIKit 会创建 transitioning context。
  5. UIKit 会通过 transitionDuration(using:) 方法向 animation controller 询问动画需要的时长。
  6. UIKit 调用 animation controller 的 animateTransition(using:) 方法,以执行转换动画。
  7. 最后,animation controller 调用 transitioning context 上的 completeTransition(_:) 方法,通知动画已经完成。

解散过程与此类似。只不过,UIKit 是向 “from” view controller(即将被解散的控制器)索要 transitioning delegate 对象。而 transitioning delegate 对象是通过 animationController(forDismissed:) 方法返回 animation controller。

创建自定义呈现动画

到了真枪实干的时候了!我们的目的是实现这个动画:

  • 当用户点击卡片,它会翻过第二个视图,第二个视图缩小到卡片的尺寸。
  • 翻转动作完成后,第二个视图放大到整屏。

创建 Animator

开始来创建 animation controller。

File\New\File…,选择 iOS\Source\Cocoa Touch Class 然后点 Next。文件命名为 FlipPresentAnimationController,继承 NSObject ,余元选 Swift。点 Next,勾上 Group to Animation Controllers。点击 Create。

Animation controllers 必须实现 UIViewControllerAnimatedTransitioning 协议。打开 FlipPresentAnimationController.swift 并适当修改类声明。

class FlipPresentAnimationController: NSObject,UIViewControllerAnimatedTransitioning {

}

Xcode 会报错,说 FlipPresentAnimationController 未实现 UIViewControllerAnimatedTransitioning 协议,点击 Fix to 添加对应的空方法。

我们在动画一开始会用到所点击的卡片的 frame。在类的实现中,添加一个属性来保存这个信息。

private let originFrame: CGRect

init(originFrame: CGRect) {
  self.originFrame = originFrame
}

然后,你需要在刚才新增的两个空方法中编写代码。将 transitionDuration(using:) 修改为:

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
  return 2.0
}

正如方法名所暗示的,这个方法用于返回动画时长。将其设置为 2 秒足以让你有足够的时间看到这个动画。

在 animateTransition(using:) 方法中添加:

// 1
guard let fromVC = transitionContext.viewController(forKey: .from),let toVC = transitionContext.viewController(forKey: .to),let snapshot = toVC.view.snapshotView(afterScreenUpdates: true)
  else {
    return
}

// 2
let containerView = transitionContext.containerView
let finalFrame = transitionContext.finalFrame(for: toVC)

// 3
snapshot.frame = originFrame
snapshot.layer.cornerRadius = CardViewController.cardCornerRadius
snapshot.layer.masksToBounds = true

这段代码中做了这些事情:

  • 获取两个 View controller 的引用:将要被替换的 view controller 和将被呈现的 view controller。然后对动画结束时的屏幕内容进行截图。
  • UIKit 将整个转封装到一个容器 view 中,以便简化对视图树和动画的管理。获得对容器视图的引用,然后计算新视图的最终框架 frame 有多大。
  • 配置屏幕截图的 frame 让它和 from 视图的 frame 一致并盖住卡片。

继续在 animateTransition(using:) 方法中添加:

// 1
containerView.addSubview(toVC.view)
containerView.addSubview(snapshot)
toVC.view.isHidden = true

// 2
AnimationHelper.perspectiveTransform(for: containerView)
snapshot.layer.transform = AnimationHelper.yRotation(.pi / 2)
// 3
let duration = transitionDuration(using: transitionContext)

container view 在刚刚被 UIKit 创建时,它只包含了 from 视图。你必须将动画中涉及到的其它视图添加进去。记住 addSubview(_:) 方法会将新的视图添加到视图树的最上面,因此视图树的顺序就是你添加它们的顺序。

  1. 将新的 to 视图添加到视图树然后隐藏它。将截图放到它的前面。
  2. 设置动画的开始状态,将截图的 y 轴旋转 90°,这会导致它以侧向的姿态面对观察者,也就是在动画的一开始它不可见。
  3. 获得动画时长。

注意:AnimatorHelper 是一个工具类,用于给视图添加透视和旋转变形。你可以看看它的实现。如果你想了解 perspectiveTransform 方法的原理,请在完成教程后为这个方法添加注释。

现在前期工作完成了,来执行动画吧!添加这个方法最后的代码:

// 1
UIView.animateKeyframes(
  withDuration: duration,delay: 0,options: .calculationModeCubic,animations: {
    // 2
    UIView.addKeyframe(withRelativeStartTime: 0.0,relativeDuration: 1/3) {
      fromVC.view.layer.transform = AnimationHelper.yRotation(-.pi / 2)
    }

    // 3
    UIView.addKeyframe(withRelativeStartTime: 1/3,relativeDuration: 1/3) {
      snapshot.layer.transform = AnimationHelper.yRotation(0.0)
    }

    // 4
    UIView.addKeyframe(withRelativeStartTime: 2/3,relativeDuration: 1/3) {
      snapshot.frame = finalFrame
      snapshot.layer.cornerRadius = 0
    }
},// 5
  completion: { _ in
    toVC.view.isHidden = false
    snapshot.removeFromSuperview()
    fromVC.view.layer.transform = CATransform3DIdentity
    transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})

这是详细解释:

  1. 我们使用了标准的 keyframe 动画。动画时长必须必须和转换时长完全一致。
  2. 一开始沿 y 轴旋转 from 视图 90°,隐藏它。
  3. 显示截图,将它从侧向状态旋转回。
  4. 设置截图的框架大小填充全屏。
  5. 截图现在已经完全和 to 视图一致了,因此可以安全第显示真正的 to 视图了。从视图树中删除截图,因为它不再需要。然后将 from 视图恢复原有状态;否则当转换动画结束它会被隐藏。。调用 completeTransition(_:) 告诉 UIKit 动画已经完成。这将确保最终状态是一致的并从容器视图中移除 from 视图。

你的 animation controller 已经准备好了!

使用 animator

UIKit 需要一个 transitioning delegate 对象为它提供 animation controller。因此,你必须用某个对象来实现 UIViewControllerTransitioningDelegate 协议。在本例中,我们用 CardViewController 来充当这个 transitioning delegate>

打开 CardViewController.swift 在文件最后声明一个扩展。

extension CardViewController: UIViewControllerTransitioningDelegate {
  func animationController(forPresented presented: UIViewController,presenting: UIViewController,source: UIViewController)
    -> UIViewControllerAnimatedTransitioning? {
    return FlipPresentAnimationController(originFrame: cardView.frame)
  }
}

我们在这里返回一个自己定义的 animation controller 对象,用当前卡片的 frame 进行初始化。

最后是将 CardViewController 设置为 transitioning delegate。View Controller 有一个 transitioningDelegate 属性,UIKit 会通过它来判断是否要使用自定义的转换动画。

在 prepare(for:sender:) 方法中的对 card 赋值之后添加:

destinationViewController.transitioningDelegate = self

注意,是对被呈现的(presented) view controller 索要 transitioning delegate,而不是对触发呈现动作的(presenting) view controller 进行索要。

Build & run。点击一张卡片,你会看到:

这就是你的第一个自定义转换动画!

好棒!

解散视图控制器

你完成了一个漂亮的呈现动画,但这只完成了一半的工作。你的解散过程仍然是默认的。让我们来搞定它!

打开 File\New\File…,选择 iOS\Source\Cocoa Touch Class,然后点击 Next。文件名为 FlipDismissAnimationController,让它继承 NSObject 并指定语言为 Swift。点击 Next 并将文件夹指定到 Animation Controllers。点击 Create。
将类定义修改成:

class FlipDismissAnimationController: NSObject,UIViewControllerAnimatedTransitioning {

  private let destinationFrame: CGRect

  init(destinationFrame: CGRect) {
    self.destinationFrame = destinationFrame
  }

  func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return 0.6
  }

  func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

  }
}

这个 animation controller 的工作是对呈现动画进行逆向操作,这样 UI 上给人的感觉是对称的。要做到这一点,你需要:

  • 将正在显示的视图缩小到卡片大小;这个值保存在 destinationFrame 中。
  • 翻转视图,显示原来的卡片。

在 animateTransition(using:) 方法中添加代码。

// 1
guard let fromVC = transitionContext.viewController(forKey: .from),let toVC = transitionContext.viewController(forKey: .to),let snapshot = fromVC.view.snapshotView(afterScreenUpdates: false)
  else {
    return
}

snapshot.layer.cornerRadius = CardViewController.cardCornerRadius
snapshot.layer.masksToBounds = true

// 2
let containerView = transitionContext.containerView
containerView.insertSubview(toVC.view,at: 0)
containerView.addSubview(snapshot)
fromVC.view.isHidden = true

// 3
AnimationHelper.perspectiveTransform(for: containerView)
toVC.view.layer.transform = AnimationHelper.yRotation(-.pi / 2)
let duration = transitionDuration(using: transitionContext)

看起来眼熟啊。不同之处在于:

  1. 这次,你要操作的是 from 视图,因此你对它进行了截图。
  2. 再次强调图层顺序的重要性。从后到前,它们应当是:to 视图、from 视图、截屏视图。当然,在本例中这个顺序貌似也不重要,但在某些时候却很重要,尤其是动画可以被取消的情况下。
  3. 将 to 视图旋转到侧立状态,这样在旋转截屏视图时,它不会被马上看到。

然后开始真正的动画。在 animateTransition(using:) 继续编写代码。

UIView.animateKeyframes(
  withDuration: duration,animations: {
    // 1
    UIView.addKeyframe(withRelativeStartTime: 0.0,relativeDuration: 1/3) {
      snapshot.frame = self.destinationFrame
    }

    UIView.addKeyframe(withRelativeStartTime: 1/3,relativeDuration: 1/3) {
      snapshot.layer.transform = AnimationHelper.yRotation(.pi / 2)
    }

    UIView.addKeyframe(withRelativeStartTime: 2/3,relativeDuration: 1/3) {
      toVC.view.layer.transform = AnimationHelper.yRotation(0.0)
    }
},// 2
  completion: { _ in
    fromVC.view.isHidden = false
    snapshot.removeFromSuperview()
    if transitionContext.transitionWasCancelled {
      toVC.view.removeFromSuperview()
    }
    transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})

这实际上是呈现动画的逆过程。

  1. 首先,缩小截屏视图,让它旋转 90° ,让它不可见。然后,将 to 视图从侧立状态旋转回 0°,以便显示它。
  2. 清除你对视图树所做的修改,移除截屏视图,恢复 from 视图的状态。如果转换动画被取消——对于本例而言这不支持,但对于后期来说这是可能的——有一点非常重要,就是在你通知动画完成之前,将你添加到视图中的东西删除。

最后,还要在宠物图片解散时让 transitioning delegate 返回这个 animation controller。

打开 CardViewController.swift 在 UIViewControllerTransitioningDelegate 扩展中添加下列方法。

func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
  guard let _ = dismissed as? RevealViewController else {
    return nil
  }
  return FlipDismissAnimationController(destinationFrame: cardView.frame)
}

确保被解散的 View controller 我们所期望的类型,然后创建 animation controller,提供一个正确的卡片显示时的 frame。

现在不需要将呈现动画的时长设置得那么慢了。打开 FlipPresentAnimationController.swift 将 duration 从 2.0 修改成 0.6,这样它就和你的解散动画相一致了。

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
  return 0.6
}

Build & run。测试一下 app,欣赏一下新的转换动画。

添加交互

你的自定义动画看起来不错。但是,你还可以更进一步,在解散动画中添加与用户交互的能力。iOS 的设置 app 是一个很好的交互式转换动画的例子:

在这一节中,我们的任务是通过从左轻扫的手势返回到卡片背面朝上的状态。转换动画的进度将跟随用户的手指而定。

交互式转换动画的工作机制

一个交互式控制器能够响应触摸事件或者程序输入,它能够加快、减慢甚至反向动画过程。为了使用交互式转换动画,transitioning delegate 必须提供一个交互式控制器。这是另外一种实现了 UIViewControllerInteractiveTransitioning 协议的对象。

你已经创建了一个转换动画。交互式控制器会根据手势的响应来管理动画,而不仅仅是播放一个视频。苹果提供了一个预置的 UIPercentDrivenInteractiveTransition 类,它就是一个交互式控制器的具体实现。你可以用这个类来创建自己的交互式转换动画。

点击 File\New\File…,选择 iOS\Source\Cocoa Touch Class,然后点击 Next。命名文件为 SwipeInteractionController,让它继承 UIPercentDrivenInteractiveTransition ,语言选择 Swift。点击 Next,将文件夹指定为 Interaction Controllers。点击 Create。

在类中编写代码:

var interactionInProgress = false

private var shouldCompleteTransition = false
private weak var viewController: UIViewController!

init(viewController: UIViewController) {
  super.init()
  self.viewController = viewController
  prepareGestureRecognizer(in: viewController.view)
}

这些定义非常易懂。

  • interactionInProgress,正如名称所暗示的,用于表示一个交互是否已经发生。
  • shouldCompleteTransition 用于在内部控制这个动画。后面你会看到。
  • viewController 引用了这个交互式控制器所属的 view controller。

然后是创建手势识别器。

private func prepareGestureRecognizer(in view: UIView) {
  let gesture = UIScreenEdgePanGestureRecognizer(target: self,action: #selector(handleGesture(_:)))
  gesture.edges = .left
  view.addGestureRecognizer(gesture)
}

这个手势识别器在用户从屏幕左边沿轻扫时触发,将它添加到视图中。

最后是 handleGesture(_:) 方法。在类中添加:

@objc func handleGesture(_ gestureRecognizer: UIScreenEdgePanGestureRecognizer) {
  // 1
  let translation = gestureRecognizer.translation(in: gestureRecognizer.view!.superview!)
  var progress = (translation.x / 200)
  progress = CGFloat(fminf(fmaxf(Float(progress),0.0),1.0))

  switch gestureRecognizer.state {

  // 2
  case .began:
    interactionInProgress = true
    viewController.dismiss(animated: true,completion: nil)

  // 3
  case .changed:
    shouldCompleteTransition = progress > 0.5
    update(progress)

  // 4
  case .cancelled:
    interactionInProgress = false
    cancel()

  // 5
  case .ended:
    interactionInProgress = false
    if shouldCompleteTransition {
      finish()
    } else {
      cancel()
    }
  default:
    break
  }
}

这是具体解释:

  1. 声明一个局部变量来跟踪轻扫的进度。首先获得视图中的 translation 并计算出进度。轻扫超过 200 个像素,我们就可以认为整个动画可以算作是完成了。
  2. 当手势开始,设置 interactionInProgress 为 ture,然后触发 view controller 的解散。
  3. 当手势还在移动中,我们不断调用 update(_:) 方法。这是 UIPercentDrivenInteractiveTransition 中的一个方法,它会根据你传入的百分数播放动画。
  4. 如果手势被取消,更新 interactionInProgress 并回滚动画。
  5. 当手势结束,根据当前动画的进度来决定是要 cancel() 还是要 finish() 动画。

现在,你必须来真正创建你的 SwipeInteractionController。打开 RevealViewController.swift 添加下列属性。

var swipeInteractionController: SwipeInteractionController?

然后,在 viewDidLoad() 方法最后添加:

swipeInteractionController = SwipeInteractionController(viewController: self)

当宠物卡片的照片显示时,会创建一个 interaction controller 并赋给这个属性。

打开 FlipDismissAnimationController.swift 在 destinationFrame 后添加属性。

let interactionController: SwipeInteractionController?

将 init(destinationFrame:) 修改成:

init(destinationFrame: CGRect,interactionController: SwipeInteractionController?) {
  self.destinationFrame = destinationFrame
  self.interactionController = interactionController
}

这个 animation controller 必须获得一个 interaction controller 的引用,这样它们两才能成为一对好基友。

打开 CardViewController.swift 将animationController(forDismissed:) 修改为:

func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
  guard let revealVC = dismissed as? RevealViewController else {
    return nil
  }
  return FlipDismissAnimationController(destinationFrame: cardView.frame,interactionController: revealVC.swipeInteractionController)
}

这里将 FlipDismissAnimationController 的创建改成和新的初始化方法相一致。

最后,UIKit 是通过调用 transitioning delegate 对象的interactionControllerForDismissal(using:) 方法来索要 interaction controller 的。在 UIViewConrollerTransitioningDelegate 扩展的最后添加zhege 方法:

func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
  guard let animator = animator as? FlipDismissAnimationController,let interactionController = animator.interactionController,interactionController.interactionInProgress
    else {
      return nil
  }
  return interactionController
}

这首先会检查 animation controller 是否是一个 FlipDismissAnimationController。如果是,获得一个对 interaction controller 的引用,并检查是否处于和用户交互的过程中。如果这些条件任何一个不满足,返回 nil,这样动画将以非交互的方式进行。否则,将 interaction controller 返回给 UIKit,以便它能够执行这种转换。

Build & run。点击一张卡片,然后从屏幕左边沿开始滑动,看看最终效果。

恭喜你!你创建了一个有趣和迷人的交互式转换动画!

接下来做什么?

你可以从这里下载已经完成的项目。

要学习更多动画,请阅读《iOS Animations by Tutorials》第17张“呈现控制器和方向动画” 。

本教程主要介绍了模式呈现和解散动画。有一点需要注意,自定义 UIViewController 转换动画也能用在 container view controller 上:

  • 当使用导航控制器时,负责提供 animation controller 的是它的 delegate,即一个实现了 UINavigationControllerDelegate 的对象。这个委托必须用 navigationController(_:animationControllerFor:from:to:) 方法来提供 animation controller。
  • Tab bar controller 需要用实现 UITabBarControllerDelegate 协议的对象来返回 animation controller,使用的是 tabBarController(_:animationControllerForTransitionFrom:to:) 方法。

希望你喜欢本教程。如果有任何问题和建议,请在论坛中留言。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。