iOS 悬浮球效果实现,悬浮按钮,拖拽,贴边,隐藏,显示,旋转屏幕适配
1.悬浮球与设备刘海的安全距离
无论是横屏还是竖屏,悬浮球距离有刘海的一边会留出安全距离
设备方向的上下两边,也有安全距离
2.贴边吸附方向和距离
悬浮球只能贴设备方向的左右两边,需要贴上下两边自行调整
距离边缘的数值自行调整
3.切换横竖屏,悬浮球自适应
悬浮球位置切换横竖屏后,等比例转换的。
4.隐藏和显示
拖到屏幕中间ImageView范围内可以隐藏悬浮窗,在范围内会由蓝色变红色,可自定义图片或者大小
使用说明:
悬浮球点击事件代理方法- (void)suspendViewButtonClick:(UIButton*)sender;
悬浮球在ImageView范围内提示是否隐藏悬浮窗- (void)showHideAlertView;
显示悬浮窗- (void)showSuspendView;
隐藏悬浮窗- (void)dismissSuspendView;
demo下载地址:
https://github.com/longypjiangxi/XLUIDragButton
简书地址:
https://www.jianshu.com/p/30aeb1d506d3
#import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @protocol SuspendViewDelegate <NSObject> - (void)suspendViewButtonClick:(UIButton*)sender; - (void)showHideAlertView; @end @interface SuspendView : UIView { CGPoint lastPoint;/**存储悬浮球最后的移动完位置*/ BOOL isChangePosition;/**悬浮球是否改变了位置*/ CGFloat changeHig;//按钮高度位置比例 CGFloat changeWid;//按钮宽度位置比例 } @property (nonatomic, retain) UIButton *btn;/**<#name#>*/ @property (nonatomic, strong) NSTimer *_Nullable timer; @property (nonatomic, retain) UIImageView *imageView; @property (nonatomic, assign) UIInterfaceOrientation orientation; @property (nonatomic, weak) id<SuspendViewDelegate> delegate; - (void)showSuspendView; - (void)dismissSuspendView; @end NS_ASSUME_NONNULL_END #import "SuspendView.h" #define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width #define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height #define ViewSize 50 #define KHeightFit(w) (((w) / 667.0) * SCREEN_HEIGHT) #define LRString [NSString stringWithFormat:@"%s", __FILE__].lastPathComponent #define DLog(...) {\ NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];\ [dateFormatter setDateFormat:@"YYYY-MM-dd hh:mm:ss"];\ NSString *dateString = [dateFormatter stringFromDate:[NSDate date]];\ printf("%s %s 第%d行:%s\n\n",[dateString UTF8String],[LRString UTF8String] ,__LINE__, [[NSString stringWithFormat:__VA_ARGS__] UTF8String]);} @implementation SuspendView - (instancetype)init{ self = [super init]; if (self) { self.backgroundColor = UIColor.redColor; self.layer.masksToBounds = YES; self.layer.cornerRadius = ViewSize/2; self.alpha = 0.5; //获取设备方向 self.orientation = [[UIApplication sharedApplication] statusBarOrientation]; if (self.orientation == UIInterfaceOrientationLandscapeRight){//横向home键在右侧,设备左转,刘海在左边 self.frame = CGRectMake(SCREEN_WIDTH - [self vg_safeDistanceTop] - ViewSize - 20, KHeightFit(80) + ViewSize/2, ViewSize, ViewSize); }else{ self.frame = CGRectMake(SCREEN_WIDTH - ViewSize/2, KHeightFit(80) + ViewSize/2, ViewSize, ViewSize); } self.btn = [UIButton buttonWithType:UIButtonTypeCustom]; self.btn.frame = CGRectMake(5, 5, 40, 40); self.btn.backgroundColor = UIColor.greenColor; self.btn.layer.cornerRadius = 20; [self.btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:self.btn]; //获取按钮与屏幕初始宽高比例 [self changeCoordinateScale]; //是否改变了悬浮窗初始位置 isChangePosition = NO; //添加手势 UIPanGestureRecognizer *panRcognize=[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; [panRcognize setMinimumNumberOfTouches:1]; [panRcognize setEnabled:YES]; [panRcognize delaysTouchesEnded]; [panRcognize cancelsTouchesInView]; [self addGestureRecognizer:panRcognize]; //监听屏幕旋转 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeStatusBarOrientation) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; } return self; } - (void)didChangeStatusBarOrientation { self.orientation = [UIApplication sharedApplication].statusBarOrientation; self.imageView.frame = CGRectMake((SCREEN_WIDTH - 50)/2, (SCREEN_HEIGHT - 50)/2, 50, 50); // DLog(@"===%zd=====%zd",[[UIDevice currentDevice] orientation],[UIApplication sharedApplication].statusBarOrientation); //请注意,UIInterfaceOrientationAndScapeLeft等于UIDeviceOrientation AndScapeRight(反之亦然)。 //这是因为向左旋转设备需要向右旋转内容。 /** UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown, UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait, UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown, UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight, UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft */ /** UIDeviceOrientationUnknown, UIDeviceOrientationPortrait, // Device oriented vertically, home button on the bottom UIDeviceOrientationPortraitUpsideDown, // Device oriented vertically, home button on the top UIDeviceOrientationLandscapeLeft, // Device oriented horizontally, home button on the right UIDeviceOrientationLandscapeRight, // Device oriented horizontally, home button on the left UIDeviceOrientationFaceUp, // Device oriented flat, face up UIDeviceOrientationFaceDown // Device oriented flat, face down */ switch ([UIDevice currentDevice].orientation) { case UIDeviceOrientationPortraitUpsideDown: // DLog(@"设备倒垂直,home在上") [self locationChange:@"Down"]; break; case UIDeviceOrientationLandscapeLeft:{ // DLog(@"设备横屏,左转,home在右") [self locationChange:@"left"]; } break; case UIDeviceOrientationLandscapeRight:{ // DLog(@"设备横屏,右转,home在左") [self locationChange:@"right"]; } break; case UIDeviceOrientationPortrait:{ // DLog(@"设备垂直,home在下"); [self locationChange:@"Portrait"]; } break; default: break; } } //根据屏幕宽高改变按钮位置比例 - (void)locationChange:(NSString *)message{ // NSLog(@"changeHig == %f,changeWid == %f",changeHig,changeWid); if (SCREEN_HEIGHT > SCREEN_WIDTH) { //屏幕方向上 if ([message isEqualToString:@"Portrait"]) { NSLog(@"安全区在上边"); self.center = CGPointMake(changeWid * SCREEN_WIDTH, changeHig * SCREEN_HEIGHT); }else{//下 NSLog(@"安全区在下边"); self.center = CGPointMake(changeWid * SCREEN_WIDTH, changeHig * SCREEN_HEIGHT - [self vg_safeDistanceTop]); } }else{ if ([message isEqualToString:@"left"]) {//左 NSLog(@"安全区在左边"); self.center = CGPointMake(changeWid * SCREEN_WIDTH + [self vg_safeDistanceTop] + ViewSize, changeHig * SCREEN_HEIGHT); }else{//右 NSLog(@"安全区在右边"); self.center = CGPointMake(changeWid * SCREEN_WIDTH - [self vg_safeDistanceTop] - ViewSize, changeHig * SCREEN_HEIGHT); } } // NSLog(@"lastPoint == %@, self.center == %@",NSStringFromCGPoint(lastPoint),NSStringFromCGPoint(self.center)); [self changeCoordinateScale]; } //旋转屏幕后修改悬浮窗相对于屏幕的宽高比例以及坐标位置 - (void)changeCoordinateScale{ changeHig = self.center.y/SCREEN_HEIGHT; changeWid = self.center.x/SCREEN_WIDTH; //判断设备旋转方向 if (self.orientation == UIInterfaceOrientationLandscapeRight) {//横向home键在右侧,设备左转,刘海在左边,刘海在左边 //判断悬浮窗坐标x在屏幕的左边还是右边 if (self.center.x > SCREEN_WIDTH/2) {//大于中心x,在右边 //修改悬浮窗的坐标在最右边 self.center = CGPointMake(SCREEN_WIDTH, self.center.y); }else{ //修改悬浮窗的坐标在最左边 self.center = CGPointMake([self vg_safeDistanceTop] + ViewSize + 20, self.center.y); } }else if(self.orientation == UIInterfaceOrientationLandscapeLeft){//横向home键在左侧,设备右转,刘海在右边 if (self.center.x > SCREEN_WIDTH/2) {//大于中心x,在右边 //修改悬浮窗的坐标在最右边,留出顶部安全距离 self.center = CGPointMake(SCREEN_WIDTH - [self vg_safeDistanceTop] - ViewSize - 20, self.center.y); }else{ //修改悬浮窗的坐标在最左边 self.center = CGPointMake(0, self.center.y); } }else{ //大于中心x,在右边 if (self.center.x > SCREEN_WIDTH/2) { self.center = CGPointMake(SCREEN_WIDTH, self.center.y); }else{ self.center = CGPointMake(0, self.center.y); } } // NSLog(@"changeHig == %f,changeWid == %f",changeHig,changeWid); // NSLog(@"设备宽度 == %f, 设备高度== %f, 按钮坐标==%@",SCREEN_WIDTH,SCREEN_HEIGHT,NSStringFromCGPoint(self.center)); } - (void)showSuspendView{ self.hidden = NO; NSLog(@"显示悬浮窗"); } - (void)dismissSuspendView{ self.hidden = YES; NSLog(@"隐藏悬浮窗"); } /// 悬浮窗按钮点击放法 /// @param button 点击之后完全显示悬浮窗,改变按钮位置 - (void)btnClick:(UIButton *)button{ if (self.delegate && [self.delegate respondsToSelector:@selector(suspendViewButtonClick:)]) { [self.delegate suspendViewButtonClick:button]; } // DLog(@"lastPoint == %@",NSStringFromCGPoint(lastPoint)); //如果没有改变过位置,lastPoint初始值(0,0) //判断是否移动过悬浮窗 if (!isChangePosition) { //悬浮窗初始位置在右上角,只有屏幕向右旋转,才需要留出iphone刘海的位置,设备左转刘海在左边,所以不需要做判断 if (self.orientation == UIInterfaceOrientationLandscapeLeft) {//横向home键在左侧,设备右转,刘海在右边 //修改点击后悬浮窗的位置,留出安全距离 [UIView animateWithDuration:0.5 animations:^{ self.center = CGPointMake(SCREEN_WIDTH - [self vg_safeDistanceTop] - ViewSize - 20 - 20, self.center.y); }]; }else{ [UIView animateWithDuration:0.5 animations:^{ self.center = CGPointMake(SCREEN_WIDTH - ViewSize, self.center.y); }]; } }else{ // 判断最后的坐标是靠左还是靠右 if (self.orientation == UIInterfaceOrientationLandscapeRight) {//横向home键在右侧,设备左转,刘海在左边 if (self.center.x > SCREEN_WIDTH/2) {//悬浮窗在屏幕右侧 [UIView animateWithDuration:0.5 animations:^{ self.center = CGPointMake(SCREEN_WIDTH - ViewSize, self.center.y); }]; }else{ //左转刘海在左边,留出安全距离 [UIView animateWithDuration:0.5 animations:^{ self.center = CGPointMake([self vg_safeDistanceTop] + ViewSize + 20 + 20, self.center.y); }]; } }else if(self.orientation == UIInterfaceOrientationLandscapeLeft){//横向home键在左侧,设备右转,刘海在右边 if (self.center.x > SCREEN_WIDTH/2) {//悬浮窗在屏幕右侧,留出刘海安全距离 [UIView animateWithDuration:0.5 animations:^{ self.center = CGPointMake(SCREEN_WIDTH - [self vg_safeDistanceTop] - ViewSize - 20 - 20, self.center.y); }]; }else{//左侧显示 [UIView animateWithDuration:0.5 animations:^{ self.center = CGPointMake(ViewSize, self.center.y); }]; } }else{ if (self.center.x < SCREEN_WIDTH/2) {//悬浮窗在屏幕右侧 [UIView animateWithDuration:0.5 animations:^{ self.center = CGPointMake(ViewSize, self.center.y); }]; }else{ [UIView animateWithDuration:0.5 animations:^{ self.center = CGPointMake(SCREEN_WIDTH - ViewSize, self.center.y); }]; } } } self.alpha = 1; //三秒后隐藏悬浮窗,贴边展示一半 self.timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(timerAction) userInfo:nil repeats:NO]; } - (void)timerAction{ //隐藏悬浮球 self.alpha = 0.5; //判断是否移动过悬浮窗 if (!isChangePosition) { //悬浮窗初始位置在右上角,只有屏幕向右旋转,才需要留出iphone刘海的位置,设备左转刘海在左边,所以不需要做判断 if (self.orientation == UIInterfaceOrientationLandscapeLeft) {//横向home键在左侧,设备右转,刘海在右边 [UIView animateWithDuration:0.5 animations:^{ self.center = CGPointMake(SCREEN_WIDTH - [self vg_safeDistanceTop] - ViewSize - 20, self.center.y); }]; }else{ [UIView animateWithDuration:0.5 animations:^{ self.center = CGPointMake(SCREEN_WIDTH, self.center.y); }]; } }else{ if (self.orientation == UIInterfaceOrientationLandscapeRight) {//横向home键在右侧,设备左转,刘海在左边 if (self.center.x > SCREEN_WIDTH/2) {//悬浮窗在屏幕右侧 [UIView animateWithDuration:0.5 animations:^{ self.center = CGPointMake(SCREEN_WIDTH, self.center.y); }]; }else{ //悬浮窗在屏幕左侧,留出刘海安全距离 [UIView animateWithDuration:0.5 animations:^{ self.center = CGPointMake([self vg_safeDistanceTop] + ViewSize + 20, self.center.y); }]; } }else if(self.orientation == UIInterfaceOrientationLandscapeLeft){//横向home键在左侧,设备右转,刘海在右边 if (self.center.x > SCREEN_WIDTH/2) {//悬浮窗在屏幕右侧 //悬浮窗在屏幕左侧,留出刘海安全距离 [UIView animateWithDuration:0.5 animations:^{ self.center = CGPointMake(SCREEN_WIDTH - [self vg_safeDistanceTop] - ViewSize - 20, self.center.y); }]; }else{ [UIView animateWithDuration:0.5 animations:^{ self.center = CGPointMake(0, self.center.y); }]; } }else{ if (self.center.x > SCREEN_WIDTH/2) {//悬浮窗在屏幕右侧 [UIView animateWithDuration:0.5 animations:^{ self.center = CGPointMake(SCREEN_WIDTH, self.center.y); }]; }else{ [UIView animateWithDuration:0.5 animations:^{ self.center = CGPointMake(0, self.center.y); }]; } } } //销毁定时器 [self.timer invalidate]; self.timer = nil; } /// pan手势 /// @param recognizer recognizer description - (void)handlePanGesture:(UIPanGestureRecognizer *)recognizer { //移动状态 UIGestureRecognizerState recState = recognizer.state; isChangePosition = YES; switch (recState) { case UIGestureRecognizerStateBegan: self.alpha = 1; self.imageView.hidden = NO; break; case UIGestureRecognizerStateChanged://移动中 { self.alpha = 1; CGPoint translation = [recognizer translationInView:self]; recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x, recognizer.view.center.y + translation.y); CGRect rect = [self convertRect:self.frame toView:self]; if (CGRectIntersectsRect(self.imageView.frame, rect)) {//在范围内 self.imageView.backgroundColor = UIColor.redColor; }else{ self.imageView.backgroundColor = UIColor.blueColor; } } break; case UIGestureRecognizerStateEnded://移动结束 { self.alpha = 0.5; CGPoint stopPoint = CGPointMake(0, SCREEN_HEIGHT / 2); //判断按钮贴靠在屏幕的左边还是右边 if (recognizer.view.center.x < SCREEN_WIDTH / 2) { stopPoint = CGPointMake(ViewSize/2, recognizer.view.center.y); }else{ //贴靠在右边 stopPoint = CGPointMake(SCREEN_WIDTH - ViewSize/2,recognizer.view.center.y); } DLog(@"stopPoint == %@",NSStringFromCGPoint(stopPoint)); if (stopPoint.y - ViewSize/2 <= 0) { DLog(@"上"); //加上电池栏的高度 if (stopPoint.x - ViewSize/2 <= SCREEN_WIDTH/2) { stopPoint = CGPointMake(0, stopPoint.y + [self vg_safeDistanceTop] + ViewSize); DLog(@"左上"); }else{ DLog(@"右上"); stopPoint = CGPointMake(SCREEN_WIDTH, stopPoint.y + [self vg_safeDistanceTop] + ViewSize); } } //如果按钮超出屏幕边缘 if (stopPoint.y + ViewSize + 20 >= SCREEN_HEIGHT) { DLog(@"下"); //减去底部状态栏的高度 if (stopPoint.x - ViewSize/2 <= SCREEN_WIDTH/2) { DLog(@"左下"); stopPoint = CGPointMake(0, stopPoint.y - [self vg_safeDistanceBottom] - ViewSize/2); }else{ DLog(@"右下"); stopPoint = CGPointMake(SCREEN_WIDTH, stopPoint.y - [self vg_safeDistanceBottom] - ViewSize/2); } // DLog(@"超出屏幕下方"); } if (stopPoint.x - ViewSize/2 <= 0) { DLog(@"左"); // stopPoint = CGPointMake(ViewSize/2, stopPoint.y); //缩进去一半 stopPoint = CGPointMake(0, stopPoint.y); } if (stopPoint.x + ViewSize/2 >= SCREEN_WIDTH) { DLog(@"右"); // stopPoint = CGPointMake(SCREEN_WIDTH - ViewSize/2, stopPoint.y); stopPoint = CGPointMake(SCREEN_WIDTH, stopPoint.y); } //保存最后的位置 lastPoint = stopPoint; //隐藏悬浮球 CGRect rect = [self convertRect:self.frame toView:self]; if (CGRectIntersectsRect(self.imageView.frame, rect)) {//在范围内 DLog(@"悬浮窗在中心imageview内,提示是否隐藏悬浮窗"); // [self showAlertView]; [self.delegate showHideAlertView]; } // NSLog(@"self.orientation == %ld",(long)self.orientation); if (self.orientation == UIInterfaceOrientationLandscapeRight) {//横向home键在右侧,设备左转,刘海在左边 if (stopPoint.x > SCREEN_WIDTH/2) {//悬浮窗在屏幕右侧 [UIView animateWithDuration:0.5 animations:^{ recognizer.view.center = CGPointMake(SCREEN_WIDTH, stopPoint.y); }]; }else{ //悬浮窗在屏幕左侧,留出刘海安全距离 [UIView animateWithDuration:0.5 animations:^{ recognizer.view.center = CGPointMake([self vg_safeDistanceTop] + ViewSize + 20, stopPoint.y); }]; } }else if(self.orientation == UIInterfaceOrientationLandscapeLeft){//横向home键在左侧,设备右转,刘海在右边 if (stopPoint.x > SCREEN_WIDTH/2) {//悬浮窗在屏幕右侧 //悬浮窗在屏幕左侧,留出刘海安全距离 [UIView animateWithDuration:0.5 animations:^{ recognizer.view.center = CGPointMake(SCREEN_WIDTH - [self vg_safeDistanceTop] - ViewSize - 20, stopPoint.y); }]; }else{ [UIView animateWithDuration:0.5 animations:^{ recognizer.view.center = CGPointMake(0, stopPoint.y); }]; } }else{ [UIView animateWithDuration:0.5 animations:^{ recognizer.view.center = stopPoint; }]; } [self changeCoordinateScale]; self.imageView.hidden = YES; } break; default: break; } [recognizer setTranslation:CGPointMake(0, 0) inView:self]; } //获取头部安全区高度 - (CGFloat)vg_safeDistanceTop { if (@available(iOS 13.0, *)) { NSSet *set = [UIApplication sharedApplication].connectedScenes; UIWindowScene *windowScene = [set anyObject]; UIWindow *window = windowScene.windows.firstObject; return window.safeAreaInsets.top; } else if (@available(iOS 11.0, *)) { UIWindow *window = [UIApplication sharedApplication].windows.firstObject; return window.safeAreaInsets.top; } return 0; } //获取设备底部安全区高度 - (CGFloat)vg_safeDistanceBottom { if (@available(iOS 13.0, *)) { NSSet *set = [UIApplication sharedApplication].connectedScenes; UIWindowScene *windowScene = [set anyObject]; UIWindow *window = windowScene.windows.firstObject; return window.safeAreaInsets.bottom; } else if (@available(iOS 11.0, *)) { UIWindow *window = [UIApplication sharedApplication].windows.firstObject; return window.safeAreaInsets.bottom; } return 0; } - (UIImageView *)imageView{ if (!_imageView) { _imageView = [[UIImageView alloc]initWithFrame:CGRectMake((SCREEN_WIDTH - 50)/2, (SCREEN_HEIGHT - 50)/2, 50, 50)]; _imageView.backgroundColor = UIColor.blueColor; _imageView.hidden = YES; [[UIApplication sharedApplication].keyWindow addSubview:_imageView]; } return _imageView; } @end