仿新版微信浮窗效果

閱讀公眾號或其他文章,經常需要暫時退出文章.
在新版微信中,可以把瀏覽的文章縮小為浮窗.點擊浮窗繼續(xù)閱讀.對于經常在微信里閱讀的人來說,這簡直就是人類之光.

微信效果如下


微信效果

對于這功能我進行了仿寫.
效果如下

仿寫效果

微信的大佬一定用了了不起的技術,我這里只是實現(xiàn)效果.

簡單寫了一個庫,一句代碼即可實現(xiàn)效果
https://github.com/SherlockQi/WeChatFloat

//在AppDelegate中將類名傳入即可
[HKFloatManager addFloatVcs:@[@"HKSecondViewController"]];

使用到的技術點

監(jiān)聽側滑返回
//設置邊緣側滑代理
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 來實現(xiàn)監(jiān)聽返回手勢
- (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 {
    //判斷手勢狀態(tài)
    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];
         }
    } 
}
監(jiān)聽浮窗移動/點擊
#import "HKFloatBall.h" 類為浮窗視圖類
//點擊浮窗后讓代理push之前保留起來的控制器
- (void)tap:(UIGestureRecognizer *)tap{
    if ([self.delegate respondsToSelector:@selector(floatBallDidClick:)]) {
        [self.delegate floatBallDidClick:self];
     }
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
  ...結束監(jiān)聽...
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
  ...結束監(jiān)聽...
}
- (void)touchesMoved:(NSSet<UITouch *> *)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動畫
 //設置navigationController代理
 self.navigationController.delegate = self;

#pragma UINavigationControllerDelegate
//push/pop 時會調用此代理方法
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                            animationControllerForOperation:(UINavigationControllerOperation)operation
                                                         fromViewController:(UIViewController *)fromVC
                                                           toViewController:(UIViewController *)toVC{
    ... 判斷是否執(zhí)行動畫 若 return nil 則執(zhí)行原始 push/pop 動畫...
   //HKTransitionPush HKTransitionPop 是自己寫的兩個動畫類,需要實現(xiàn)<UIViewControllerAnimatedTransitioning>
    if(operation==UINavigationControllerOperationPush)  {
        return [[HKTransitionPush alloc]init];
    } else if(operation==UINavigationControllerOperationPop){
        return [[HKTransitionPop alloc]init];
    }
}
HKTransitionPush HKTransitionPop 代碼類似已HKTransitionPush為例
#import "HKTransitionPush.h"
-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
    return kAuration;//動畫時間
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)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" 中
//在AppDelegate中將類名傳入即可,在該類控制器側滑返回時啟動浮窗功能(需要在實例化導航控制器之后)
[HKFloatManager addFloatVcs:@[@"HKSecondViewController"]];
若需要設置浮窗頭像,設置該控制器的"hk_iconImage"
@property (nonatomic, strong) UIImage *hk_iconImage;

Tips

  • 震動反饋
UIImpactFeedbackGenerator*impactLight = [[UIImpactFeedbackGenerator alloc]initWithStyle:UIImpactFeedbackStyleMedium]; 
[impactLight impactOccurred];
 //    UIImpactFeedbackStyleLight,
 //    UIImpactFeedbackStyleMedium,
 //    UIImpactFeedbackStyleHeavy
  • 分類獲取當前控制器
#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"屬性
- (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;
}

以上便是實現(xiàn)該效果的全部實現(xiàn).上方含有部分偽代碼.全部代碼已上傳至 ---Github--- 歡迎(跪求) Star.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末遇绞,一起剝皮案震驚了整個濱河市吃引,隨后出現(xiàn)的幾起案子爸业,更是在濱河造成了極大的恐慌廊酣,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機旅挤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來峦阁,“玉大人谦铃,你說我怎么就攤上這事±莆簦” “怎么了驹闰?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵瘪菌,是天一觀的道長。 經常有香客問我嘹朗,道長师妙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任屹培,我火速辦了婚禮默穴,結果婚禮上,老公的妹妹穿的比我還像新娘褪秀。我一直安慰自己蓄诽,他們只是感情好,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布媒吗。 她就那樣靜靜地躺著仑氛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪闸英。 梳的紋絲不亂的頭發(fā)上锯岖,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機與錄音甫何,去河邊找鬼出吹。 笑死,一個胖子當著我的面吹牛辙喂,可吹牛的內容都是我干的捶牢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼巍耗,長吁一口氣:“原來是場噩夢啊……” “哼叫确!你這毒婦竟也來了?” 一聲冷哼從身側響起芍锦,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎飞盆,沒想到半個月后娄琉,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡吓歇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年孽水,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片城看。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡女气,死狀恐怖,靈堂內的尸體忽然破棺而出测柠,到底是詐尸還是另有隱情炼鞠,我是刑警寧澤缘滥,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站谒主,受9級特大地震影響朝扼,放射性物質發(fā)生泄漏。R本人自食惡果不足惜霎肯,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一擎颖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧观游,春花似錦搂捧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至提佣,卻和暖如春吮蛹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拌屏。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工潮针, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人倚喂。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓每篷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親端圈。 傳聞我的和親對象是個殘疾皇子焦读,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內容

  • 1舱权、通過CocoaPods安裝項目名稱項目信息 AFNetworking網絡請求組件 FMDB本地數據庫組件 SD...
    陽明先生_X自主閱讀 15,969評論 3 119
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,743評論 25 707
  • 承認別人比自己優(yōu)秀是件特別難的事宴倍,比如很多人都會說张症,你考那么多證有什么用啊鸵贬?學生會有什么用八姿?拿獎學金有什么用兆衅?考...
    小一凡的日記本閱讀 90評論 0 2
  • 01 張愛玲說,也許每一個男子全都有過這樣兩個女人羡亩,至少是兩個摩疑。娶了紅玫瑰,久而久之未荒,紅的變了墻上的一抹蚊子血,白...
    木囈閱讀 2,894評論 10 17
  • 本周學習了《干法》 事先“看見完成時的狀態(tài)”就能成功這一節(jié)片排,無論生活還是工作,只有事先精心準備、周密計劃倚搬,并...
    付中強閱讀 501評論 0 1