服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - Android仿新版微信浮窗效果

Android仿新版微信浮窗效果

2022-03-03 15:01juejin Android

在新版微信中,可以把浏览的文章缩小为浮窗.点击浮窗继续阅读.这篇文章主要介绍了Android仿新版微信浮窗效果,需要的朋友可以参考下

阅读公众号或其他文章,经常需要暂时退出文章.

在新版微信中,可以把浏览的文章缩小为浮窗.点击浮窗继续阅读.对于经常在微信里阅读的人来说,这简直就是人类之光.

微信效果如下

Android仿新版微信浮窗效果

微信效果

对于这功能我进行了仿写.

效果如下

Android仿新版微信浮窗效果

仿写效果

微信的大佬一定用了了不起的技术,我这里只是实现效果.

简单写了一个库,一句代码即可实现效果

github.com/SherlockQi/…

?
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

延伸 · 阅读

精彩推荐