直接上完整代码
//
// BannerView.m
// Test
//
// Created by liubo on 2023/7/20.
//
#import "LB3DBannerView.h"
#import <Masonry/Masonry.h>
#import <CoreMotion/CoreMotion.h>
@interface LB3DBannerView ()
{
CGFloat maxOffset;
CGFloat lastGravigyX;
CGFloat lastGravityY;
NSTimeInterval deviceMotionUpdateInterval;
CGPoint frontImageViewCenter;
CGPoint secondFrontImageViewCenter;
CGPoint backImageViewCenter;
}
@property (nonatomic, strong) UIImageView *frontImageView;
@property (nonatomic, strong) UIImageView *secondFrontImageView;
@property (nonatomic, strong) UIImageView *middleImageView;
@property (nonatomic, strong) UIImageView *backImageView;
@property (nonatomic, strong) CMMotionManager *motionManager;
@end
@implementation LB3DBannerView
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self setUpUI];
[self setUpContraints];
self.clipsToBounds = YES;
}
return self;
}
- (void)setUpUI
{
maxOffset = 15;
deviceMotionUpdateInterval = 1 / 120.0;
frontImageViewCenter = CGPointZero;
backImageViewCenter = CGPointZero;
secondFrontImageViewCenter = CGPointZero;
[self addSubview:self.backImageView];
[self addSubview:self.middleImageView];
[self addSubview:self.secondFrontImageView];
[self addSubview:self.frontImageView];
}
- (void)setUpContraints {
[self.backImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(self.mas_width).with.offset(maxOffset * 2);
make.height.equalTo(self.mas_height).with.offset(maxOffset * 2);
make.centerX.mas_equalTo(0);
make.centerY.mas_equalTo(0);
}];
[self.middleImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(self.mas_width).with.offset(maxOffset * 2/3);
make.height.equalTo(self.mas_height).with.offset(maxOffset * 2/3);
make.centerX.mas_equalTo(0);
make.centerY.mas_equalTo(0);
}];
[self.secondFrontImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(self.mas_width).with.offset(maxOffset);
make.height.equalTo(self.mas_height).with.offset(maxOffset);
make.centerX.mas_equalTo(0);
make.centerY.mas_equalTo(0);
}];
[self.frontImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(self.mas_width).with.offset(maxOffset * 2);
make.height.equalTo(self.mas_height).with.offset(maxOffset * 2);
make.centerX.centerY.mas_equalTo(0);
}];
}
- (void)layoutSubviews
{
[super layoutSubviews];
backImageViewCenter = self.center;
frontImageViewCenter = self.center;
}
- (void)start
{
[self startMotion];
}
- (void)stop
{
[self.motionManager stopDeviceMotionUpdates];
}
- (void)updateWithArray:(NSArray *)imageNames
{
self.frontImageView.image = [UIImage imageNamed:imageNames[0]];
self.secondFrontImageView.image = [UIImage imageNamed:imageNames[1]];
self.middleImageView.image = [UIImage imageNamed:imageNames[2]];
self.backImageView.image = [UIImage imageNamed:imageNames[3]];
}
- (void)startMotion
{
if (!self.motionManager.isDeviceMotionAvailable) {
return;
}
__weak LB3DBannerView *weakSelf = self;
[self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) {
if (!motion) {
return;
}
[weakSelf updateWithGravityX:motion.gravity.x
gravityY:motion.gravity.y
gravityZ:motion.gravity.z];
}];
}
- (void)updateWithGravityX:(double)gravityX
gravityY:(double)gravityY
gravityZ:(double)gravityZ
{
//因为在斜向上45度角的时候,gravity的值是-0.5,设计要求以这个位置为基准,所以要减去-0.5
gravityY -= (-0.5);
gravityY *= 2;
//最大的便宜量是maxoffset,所以gravityY最大为1
gravityY = MIN(1, MAX(-1, gravityY));
gravityX *= 2;
gravityX = MIN(1, MAX(-1, gravityX));
double timeInterval = sqrt(pow((gravityX - lastGravigyX),2) + pow((gravityY - lastGravityY), 2)) * deviceMotionUpdateInterval;
NSString *animationKey = @"positionAnimation";
CGPoint newBackImageViewCenter = self.backImageView.center;
newBackImageViewCenter.x = (newBackImageViewCenter.x - gravityX * maxOffset);
newBackImageViewCenter.y = (newBackImageViewCenter.y + gravityY * maxOffset);
CABasicAnimation *backImageViewAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
backImageViewAnimation.fromValue = [NSValue valueWithCGPoint:backImageViewCenter];
backImageViewAnimation.toValue = [NSValue valueWithCGPoint:newBackImageViewCenter];
backImageViewAnimation.duration = timeInterval;
backImageViewAnimation.fillMode = kCAFillModeForwards;
backImageViewAnimation.removedOnCompletion = NO;
[self.backImageView.layer removeAnimationForKey:animationKey];
[self.backImageView.layer addAnimation:backImageViewAnimation forKey:animationKey];
CGPoint newFrontImageViewCenter = self.frontImageView.center;
newFrontImageViewCenter.x += gravityX * maxOffset;
newFrontImageViewCenter.y -= gravityY * maxOffset;
CABasicAnimation *frontImageViewAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
frontImageViewAnimation.fromValue = [NSValue valueWithCGPoint:frontImageViewCenter];
frontImageViewAnimation.toValue = [NSValue valueWithCGPoint:newFrontImageViewCenter];
frontImageViewAnimation.duration = timeInterval;
frontImageViewAnimation.fillMode = kCAFillModeForwards;
frontImageViewAnimation.removedOnCompletion = NO;
[self.frontImageView.layer removeAnimationForKey:animationKey];
[self.frontImageView.layer addAnimation:frontImageViewAnimation forKey:animationKey];
CGPoint newSecondFrontImageViewCenter = self.middleImageView.center;
newSecondFrontImageViewCenter.x -= gravityX * maxOffset/3;
newSecondFrontImageViewCenter.y += gravityY * maxOffset/3;
CABasicAnimation *secondfrontImageViewAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
secondfrontImageViewAnimation.fromValue = [NSValue valueWithCGPoint:secondFrontImageViewCenter];
secondfrontImageViewAnimation.toValue = [NSValue valueWithCGPoint:newSecondFrontImageViewCenter];
secondfrontImageViewAnimation.duration = timeInterval;
secondfrontImageViewAnimation.fillMode = kCAFillModeForwards;
secondfrontImageViewAnimation.removedOnCompletion = NO;
[self.middleImageView.layer removeAnimationForKey:animationKey];
[self.middleImageView.layer addAnimation:secondfrontImageViewAnimation forKey:animationKey];
backImageViewCenter = newBackImageViewCenter;
frontImageViewCenter = newFrontImageViewCenter;
secondFrontImageViewCenter = newSecondFrontImageViewCenter;
}
#pragma mark - lazy load
- (UIImageView *)frontImageView
{
if (!_frontImageView) {
_frontImageView = [[UIImageView alloc] init];
_frontImageView.contentMode = UIViewContentModeScaleAspectFill;
}
return _frontImageView;
}
- (UIImageView *)secondFrontImageView
{
if (!_secondFrontImageView) {
_secondFrontImageView = [[UIImageView alloc] init];
_secondFrontImageView.contentMode = UIViewContentModeScaleAspectFill;
}
return _secondFrontImageView;
}
- (UIImageView *)middleImageView
{
if (!_middleImageView) {
_middleImageView = [[UIImageView alloc] init];
_middleImageView.contentMode = UIViewContentModeScaleAspectFill;
}
return _middleImageView;
}
- (UIImageView *)backImageView
{
if (!_backImageView) {
_backImageView = [[UIImageView alloc] init];
_backImageView.contentMode = UIViewContentModeScaleAspectFill;
}
return _backImageView;
}
- (CMMotionManager *)motionManager
{
if (!_motionManager) {
_motionManager = [[CMMotionManager alloc] init];
_motionManager.deviceMotionUpdateInterval = deviceMotionUpdateInterval;
}
return _motionManager;
}
@end