阅读公众号或其他文章,经常需要暂时退出文章.
在新版微信中,可以把浏览的文章缩小为浮窗.点击浮窗继续阅读.对于经常在微信里阅读的人来说,这简直就是人类之光.
微信效果如下
微信效果
对于这功能我进行了仿写.
效果如下
仿写效果
微信的大佬一定用了了不起的技术,我这里只是实现效果.
简单写了一个库,一句代码即可实现效果
1
2
|
//在AppDelegate中将类名传入即可 [HKFloatManager addFloatVcs:@[@ "HKSecondViewController" ]]; |
使用到的技术点
监听侧滑返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
//设置边缘侧滑代理 self.navigationController.interactivePopGestureRecognizer.delegate = self; //当开始侧滑pop时调用此方法 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{ /* 判断是否开启边缘侧滑返回 **/ if (self.navigationController.viewControllers.count > 1 ) { [self beginScreenEdgePanBack:gestureRecognizer]; return YES; } return NO; } /* UIScreenEdgePanGestureRecognizer @property(nullable, nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; /*! This subclass of UIPanGestureRecognizer only recognizes if the user slides their finger in from the bezel on the specified edge. */ //NS_CLASS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED @interface UIScreenEdgePanGestureRecognizer : UIPanGestureRecognizer **/ //利用CADisplayLink 来实现监听返回手势 - ( void )beginScreenEdgePanBack:(UIGestureRecognizer *)gestureRecognizer{ /* * 引用 gestureRecognizer * 开启 CADisplayLink * 显示右下视图 **/ self.edgePan = (UIScreenEdgePanGestureRecognizer *)gestureRecognizer; _link = [CADisplayLink displayLinkWithTarget:self selector: @selector (panBack:)]; [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; [[UIApplication sharedApplication].keyWindow addSubview:self.floatArea]; } //此方法中进行操作 - ( void )panBack:(CADisplayLink *)link { //判断手势状态 if (self.edgePan.state == UIGestureRecognizerStateChanged) { //移动过程 /* * 改变右下视图 frame * 判断手指是否进入右下视图中 **/ //手指在屏幕上的位置 CGPoint tPoint = [self.edgePan translationInView:kWindow]; ...根据tPoint设置右下视图 frame... //手指在右下视图上的位置(若 x>0 && y>0 说明此时手指在右下视图上) CGPoint touchPoint = [kWindow convertPoint:[self.edgePan locationInView:kWindow] toView:self.floatArea]; if (touchPoint.x > 0 && touchPoint.y > 0 ) { ... //由于右下视图是1/4圆 所以需要这步判断 if (pow((kFloatAreaR - touchPoint.x), 2 ) + pow((kFloatAreaR - touchPoint.y), 2 ) <= pow((kFloatAreaR), 2 )) { self.showFloatBall = YES; } ... } else if (self.edgePan.state == UIGestureRecognizerStatePossible) { /* * 停止CADisplayLink * 隐藏右下视图 * 显示/隐藏浮窗 **/ [self.link invalidate]; if (self.showFloatBall) { self.floatBall.iconImageView.image= [self.floatViewController valueForKey:@ "hk_iconImage" ]; [kWindow addSubview:self.floatBall]; } } } |
监听浮窗移动/点击
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
# import "HKFloatBall.h" 类为浮窗视图类 //点击浮窗后让代理push之前保留起来的控制器 - ( void )tap:(UIGestureRecognizer *)tap{ if ([self.delegate respondsToSelector: @selector (floatBallDidClick:)]) { [self.delegate floatBallDidClick:self]; } } - ( void )touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{ ...结束监听... } - ( void )touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ ...结束监听... } - ( void )touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ /* * 改变浮窗 frame * 改变右下视图 frame * 判断浮窗center 是否在右下视图之上 **/ CGPoint center_ball = [kWindow convertPoint:self.floatBall.center toView:self.cancelFloatArea]; if (pow((kFloatAreaR - center_ball.x), 2 ) + pow((kFloatAreaR - center_ball.y), 2 ) <= pow((kFloatAreaR), 2 )) { if (!self.cancelFloatArea.highlight) { self.cancelFloatArea.highlight = YES; } } } } |
自定义push/pop动画
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
//设置navigationController代理 self.navigationController.delegate = self; #pragma UINavigationControllerDelegate //push/pop 时会调用此代理方法 - (nullable id )navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{ ... 判断是否执行动画 若 return nil 则执行原始 push/pop 动画... //HKTransitionPush HKTransitionPop 是自己写的两个动画类,需要实现 if(operation==UINavigationControllerOperationPush) { return [[HKTransitionPush alloc]init]; } else if (operation==UINavigationControllerOperationPop){ return [[HKTransitionPop alloc]init]; } } HKTransitionPush HKTransitionPop 代码类似已HKTransitionPush为例 # import "HKTransitionPush.h" -(NSTimeInterval)transitionDuration:(id)transitionContext{ return kAuration; //动画时间 } - ( void )animateTransition:(id)transitionContext { //获取上下文 self.transitionContext = transitionContext; UIViewController * fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView *contView = [transitionContext containerView]; [contView addSubview:fromVC.view]; [contView addSubview:toVC.view]; //添加遮罩视图 [fromVC.view addSubview:self.coverView]; //浮窗的 frame push时这个是起始 frame ,pop时是结束时的 frame CGRect floatBallRect = [HKFloatManager shared].floatBall.frame; //开始/结束时的曲线 UIBezierPath *maskStartBP = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(floatBallRect.origin.x, floatBallRect.origin.y,floatBallRect.size.width , floatBallRect.size.height) cornerRadius:floatBallRect.size.height/ 2 ]; UIBezierPath *maskFinalBP = [UIBezierPath bezierPathWithRoundedRect:CGRectMake( 0 , 0 ,SCREEN_WIDTH, SCREEN_HEIGHT) cornerRadius:floatBallRect.size.width/ 2 ]; //.layer.mask 是部分显示的原因 CAShapeLayer *maskLayer = [CAShapeLayer layer]; maskLayer.path = maskFinalBP.CGPath; toVC.view.layer.mask = maskLayer; //动画类 CABasicAnimation *maskLayerAnimation = [CABasicAnimation animationWithKeyPath:@ "path" ]; maskLayerAnimation.fromValue = (__bridge id)(maskStartBP.CGPath); maskLayerAnimation.toValue = (__bridge id)((maskFinalBP.CGPath)); maskLayerAnimation.duration = kAuration; maskLayerAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; maskLayerAnimation.delegate = self; [maskLayer addAnimation:maskLayerAnimation forKey:@ "path" ]; //隐藏浮窗 [UIView animateWithDuration:kAuration animations:^{ [HKFloatManager shared].floatBall.alpha = 0 ; }]; } #pragma mark - CABasicAnimation的Delegate //动画完成后代理 - ( void )animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{ [self.transitionContext completeTransition:YES]; [self.transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view.layer.mask = nil; [self.transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view.layer.mask = nil; [self.coverView removeFromSuperview]; } -(UIView *)coverView{ if (!_coverView) { _coverView = [[UIView alloc]initWithFrame:[UIScreen mainScreen].bounds]; _coverView.backgroundColor = [UIColor blackColor]; _coverView.alpha = 0.5 ; }; return _coverView; } |
解耦
将所有代码集中在 #import "HKFloatManager.h" 中
1
2
|
//在AppDelegate中将类名传入即可,在该类控制器侧滑返回时启动浮窗功能(需要在实例化导航控制器之后) [HKFloatManager addFloatVcs:@[@ "HKSecondViewController" ]]; |
若需要设置浮窗头像,设置该控制器的"hk_iconImage"
1
2
|
@property (nonatomic, strong) UIImage *hk_iconImage; Tips |
震动反馈
1
2
3
4
5
|
UIImpactFeedbackGenerator*impactLight = [[UIImpactFeedbackGenerator alloc]initWithStyle:UIImpactFeedbackStyleMedium]; [impactLight impactOccurred]; // UIImpactFeedbackStyleLight, // UIImpactFeedbackStyleMedium, // UIImpactFeedbackStyleHeavy |
分类获取当前控制器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
# import "NSObject+hkvc.h" @implementation NSObject (hkvc) - (UIViewController *)hk_currentViewController { UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; UIViewController *vc = keyWindow.rootViewController; if ([vc isKindOfClass:[UINavigationController class ]]) { vc = [(UINavigationController *)vc visibleViewController]; } else if ([vc isKindOfClass:[UITabBarController class ]]) { vc = [(UITabBarController *)vc selectedViewController]; } return vc; } - (UINavigationController *)hk_currentNavigationController { return [self hk_currentViewController].navigationController; } - (UITabBarController *)hk_currentTabBarController { return [self hk_currentViewController].tabBarController; } @end |
判断控制器是否有"hk_iconImage"属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
- (BOOL)haveIconImage{ BOOL have = NO; unsigned int outCount = 0 ; Ivar *ivars = class_copyIvarList([self.floatViewController class ], &outCount); for (unsigned int i = 0 ; i < outCount; i ++) { Ivar ivar = ivars[i]; const char * nameChar = ivar_getName(ivar); NSString *nameStr =[NSString stringWithFormat:@ "%s" ,nameChar]; if ([nameStr isEqualToString:@ "_hk_iconImage" ]) { have = YES; } } free(ivars); return have; } |
以上便是实现该效果的全部实现.上方含有部分伪代码.全部代码已上传至---Github--- 欢迎(跪求) Star.
以上所述是小编给大家介绍的Android仿新版微信浮窗效果,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对服务器之家网站的支持!
原文链接:https://juejin.im/entry/5b2a05876fb9a00e2d47f725