直接上完整代码
//
// 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.xgravityY:motion.gravity.ygravityZ:motion.gravity.z];}];
}- (void)updateWithGravityX:(double)gravityXgravityY:(double)gravityYgravityZ:(double)gravityZ
{//因为在斜向上45度角的时候,gravity的值是-0.5,设计要求以这个位置为基准,所以要减去-0.5gravityY -= (-0.5);gravityY *= 2;//最大的便宜量是maxoffset,所以gravityY最大为1gravityY = 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