前两天看了自如客APP裸眼3D效果的实现,感觉实现的 banner 设计的很有创意,效果很是惊艳。又看到拿去吧你!Flutter 仿自如 App 裸眼 3D 效果,然后决定使用 Swift 实现一下。
最后实现的项目为 Banner3D,可以点击查看运行效果。
原理
平时的 banner 都是使用的一张图片,为了达到 3D 的效果,需要将一张图片分成三张,也就是将原来的一张 banner 图片分成三个图层,再将图层分别导出为相同大小的图片。将这三张图片叠加在一起,从后往前分为背景、中景和前景。
当手机的旋转时,中景始终保持不变,给背景和中景位置添加相反的偏移量,就可以达到 3D 的效果了。
原理很简单,接下里就是背景和前景根据什么进行偏移?加速计?还是磁场?陀螺仪?还是重力?
我们先来看各个名词的定义:
明白这几个定义之后,很明显我们应该选择 重力
的变化来修改图片的位移。再具体点就是,通过重力在设备 x 轴上的变化来修改图片左右的位移距离,通过重力在设备 y 轴上的变化来修改图片上下的位移距离。
实现
CMMotionManager 负责获取和处理手机的运动信息,是 Core Motion 库的核心类,可以使用 CMMotionManager 来获取重力信息,
import CoreMotion
lazy var motionManager: CMMotionManager = {
let manager = CMMotionManager()
manager.deviceMotionUpdateInterval = deviceMotionUpdateInterval
return manager
}()
复制代码
使用 motionManager 时,有几点需要注意:
获取设备信息时,有 pull 和 push 两种方式,苹果更推荐使用 pull;
motionManager 尽可能使用单利来管理,不要创建多个实例。
这里就不详细介绍了,可以看 iOS传感器与CMMotionManager。
开启获取设备信息更新回调,获取重力方向。
private func startMotion() {
// 检查是否可用
guard motionManager.isDeviceMotionAvailable else {
return
}
motionManager.startDeviceMotionUpdates(to: OperationQueue.main) { [weak self] deviceMotion, error in
guard let motion = deviceMotion else {
return
}
// motion.gravity 为重力方向
self?.update(gravityX: motion.gravity.x,
gravityY: motion.gravity.y,
gravityZ: motion.gravity.z)
}
}
复制代码
根据重力方向给前景和背景添加位移动画。
func update(gravityX: Double, gravityY: Double, gravityZ: Double) {
let timeInterval = sqrt(pow((gravityX - lastGravityX), 2) + pow((gravityY - lastGravityY), 2)) * deviceMotionUpdateInterval
let animationKey = "positionAnimation"
let newBackImageViewCenter = self.center.with {
$0.x += CGFloat(gravityX) * maxOffset
$0.y += CGFloat(gravityY) * maxOffset
}
let backImageViewAnimation = CABasicAnimation(keyPath: "position").then {
$0.fromValue = NSValue(cgPoint: self.backImageViewCenter)
$0.toValue = NSValue(cgPoint: newBackImageViewCenter)
$0.duration = timeInterval
$0.fillMode = .forwards
$0.isRemovedOnCompletion = false
}
backImageView.layer.do {
$0.removeAnimation(forKey: animationKey)
$0.add(backImageViewAnimation, forKey: animationKey)
}
let newFrontImageViewCenter = self.center.with {
$0.x -= CGFloat(gravityX) * maxOffset
$0.y -= CGFloat(gravityY) * maxOffset
}
let frontImageViewAnimation = CABasicAnimation(keyPath: "position").then {
$0.fromValue = NSValue(cgPoint: self.frontImageViewCenter)
$0.toValue = NSValue(cgPoint: newFrontImageViewCenter)
$0.duration = timeInterval
$0.fillMode = .forwards
$0.isRemovedOnCompletion = false
}
frontImageView.layer.do {
$0.removeAnimation(forKey: animationKey)
$0.add(frontImageViewAnimation, forKey: animationKey)
}
self.backImageViewCenter = newBackImageViewCenter
self.frontImageViewCenter = newFrontImageViewCenter
}
复制代码
到此就可以实现 3D 的效果了。
最后可以查看完成的代码Banner3D。
参考资料
使用到了拿去吧你!Flutter 仿自如 App 裸眼 3D 效果中的图片
评论