状态栏提示控件的实现原理

来源:互联网 发布:nancymeng 孟燕 知乎 编辑:程序博客网 时间:2024/05/20 23:06

转自:http://www.cnblogs.com/smileEvday/archive/2013/05/30/statusBarTips.html

现在很多流行的软件都加入了状态栏提示的功能,比如手机qq,微信等,今天我们就一起来看看状态栏提示控件的原理与实现。

 

一、状态栏提示的实现原理

 

  不知道大家看到状态栏提示控件,第一感觉它是怎么实现的呢?

  我们知道即使平时写的view是充满全屏的,也始终不会显示到statusBar的上层的。也就是说statusBar应该是一个特殊的view,始终位于程序的topLevel,这就容易联想到UIKit中一个特殊的view-----UIWindow。UIWindow有一个windowLevel的属性刚好能实现一直保持在上层的功能,于是方向就比较明确了。我较早写的两篇博客UIWindowLevel详解以及关于UIWindow的一点儿思考中对windowLevel有过详细的介绍和验证。

  确定了使用UIWindow来实现状态栏提示控件,好像问题就全部解决了,真的是这样吗?

  如果你的程序仅仅支持portrait方向的话,那么最主要的功能就结束了,剩下的事情就是文本框布局和简单动画的实现了。但如果你的控件要支持其他三个方向的话,就还需要处理window的旋转。

  IOS中完整的旋转流程如下: 设备检测到方向旋转->UIApplication收到旋转事件->通知window设备发生了旋转->window通知它的rootViewController进行旋转->viewController会调整自身view的transform。观察发现自始至终window本身的位置和方向是没有发生变化的,也就是说如果自己创建一个window用于展示提示,我们需要自己处理该window的旋转,根据不同的方向调整window的位置和transform。

  综上要实现状态栏提示控件我们需要做以下两件事:

1、创建一个UIWindow,指定它的frame为statusBar的frame,并且设置该window的windowLevel级别略高于statusBar的windowLevel。

2、注册系统的旋转通知,监测设备方向变化,根据当前设备的方向做出相应的调整。

  

  整个过程中主要用到了UIWindow和transfrom的知识,这两部分知识我前面写的博客都有涉及,难点主要集中在自己旋转window这一块。

 

二、Window的旋转

 

  UIKit通过UIWindow和UIViewContoller为我们提供了一套旋转支持的框架,在方向变化以后viewController中view的坐标系统就已经被转到正确的方向了,我们只需要简单的重新布局就可以了。我们现在是直接通过UIWindow实现状态栏提示控件,因此需要自己完成对该window进行旋转的操作。

  我们知道对当前view设置的transform是针对它的父view的,window本身就是一种特殊的view。你可能会疑问window不就是最底层的view,它还有父view吗?

  答案是YES,不信的话你可以打印一下window的superView看看。window默认方向是portrait方向,向下y坐标增加,向右x坐标增加。因此Portrait方向我们只需要向普通的view那样布局就可以了,其它几个方向我们就需要用到transform和设置位置来搞定了。

  下面我们看一看从Portrait方向转到landscapeRight方向的图示:

  上图中从左到右展示了如何将初始位置(Portrait方向),旋转到目标位置(landscapeRight方向)的过程。

1、原始window位置位于屏幕最上方(与statusBar的位置一样)。

2、首先我们对这个window做顺时针90°旋转,变化后到达绿色绘制位置。

3、接着我们再修改window的center到屏幕最右边并且上下居中,达到红色虚线所示的位置。

4、最后对该window的bound进行设置,使该window充满屏幕最右边的区域。注意这个时候由于window的transform已经转了90°,所以设置时width代表着高度,height代表这宽度。

 

下面是完整的处理旋转到四个方向的代码:

复制代码
- (void)updateOrientation:(NSNotification*)noti{    UIInterfaceOrientation newOrientation = [[noti.userInfo valueForKey:UIApplicationStatusBarOrientationUserInfoKey] integerValue];    NSLog(@"new orientation: %d", newOrientation);        switch (newOrientation) {        case UIInterfaceOrientationPortrait:        {            self.transform = CGAffineTransformIdentity;            self.frame = CGRectMake(0, 0, SCREEN_WIDTH, HEIGHT);                        break;        }        case UIInterfaceOrientationPortraitUpsideDown:        {            // 先转矩阵,坐标系统落在屏幕有右下角,朝上是y,朝左是x            self.transform = CGAffineTransformMakeRotation(M_PI);            self.center = CGPointMake(SCREEN_WIDTH / 2, SCREEN_HEIGHT - HEIGHT / 2);            self.bounds = CGRectMake(0, 0, SCREEN_WIDTH, HEIGHT);                        break;        }        case UIInterfaceOrientationLandscapeLeft:        {            self.transform = CGAffineTransformMakeRotation(-M_PI_2);            // 这个时候坐标轴已经转了90°,调整x相当于调节竖向调节,y相当于横向调节            self.center = CGPointMake(HEIGHT / 2, [UIScreen mainScreen].bounds.size.height / 2);            self.bounds = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.height, HEIGHT);                        break;        }        case UIInterfaceOrientationLandscapeRight:        {            // 先设置transform,在设置位置和大小            self.transform = CGAffineTransformMakeRotation(M_PI_2);            self.center = CGPointMake(SCREEN_WIDTH - HEIGHT / 2, SCREEN_HEIGHT / 2);            self.bounds = CGRectMake(0, 0, SCREEN_HEIGHT, HEIGHT);                        break;        }        default:            break;    }}
复制代码

  

三、状态栏提示控件源码

 

讲了那么多,说好的控件呢?

  下面是完整的控件代码:

复制代码
////  SvStatusBarTipsWindow.h//  SvStatusBarTips////  Created by  maple on 4/21/13.//  Copyright (c) 2013 maple. All rights reserved.//#import <UIKit/UIKit.h>@interface SvStatusBarTipsWindow : UIWindow/* * @brief get the singleton tips window */+ (SvStatusBarTipsWindow*)shareTipsWindow;/* * @brief show tips message on statusBar */- (void)showTips:(NSString*)tips;/* * @brief show tips message on statusBar */- (void)showTips:(NSString*)tips hideAfterDelay:(NSInteger)seconds;/* * @brief show tips icon and message on statusBar */- (void)showTipsWithImage:(UIImage*)tipsIcon message:(NSString*)message;/* * @brief show tips icon and message on statusBar */- (void)showTipsWithImage:(UIImage*)tipsIcon message:(NSString*)message hideAfterDelay:(NSInteger)seconds;/* * @brief hide tips window */- (void)hideTips;@end
复制代码
复制代码
////  SvStatusBarTipsWindow.m//  SvStatusBarTips////  Created by  maple on 4/21/13.//  Copyright (c) 2013 maple. All rights reserved.//#import "SvStatusBarTipsWindow.h"#define SCREEN_WIDTH  ([UIScreen mainScreen].bounds.size.width)#define SCREEN_HEIGHT ([UIScreen mainScreen].bounds.size.height)#define HEIGHT   20#define ICON_WIDTH 20#define TIPMESSAGE_RIGHT_MARGIN 20#define ICON_RIGHT_MARGIN       5@interface SvStatusBarTipsWindow () {    UILabel     *_tipsLbl;    UIImageView *_tipsIcon;        NSTimer     *_hideTimer;}@property (nonatomic, copy)     NSString *tipsMessage;@end@implementation SvStatusBarTipsWindow@synthesize tipsMessage;static SvStatusBarTipsWindow *tipsWindow = nil;+ (SvStatusBarTipsWindow*)shareTipsWindow{    if (!tipsWindow) {        static dispatch_once_t onceToken;        dispatch_once(&onceToken, ^{            tipsWindow = [[super allocWithZone:NULL] init];        });    }        return tipsWindow;}+ (id)copyWithZone:(NSZone *)zone{    return [[self shareTipsWindow] retain];}+ (id)allocWithZone:(NSZone *)zone{    return [[self shareTipsWindow] retain];}- (id)retain{    return tipsWindow;}- (oneway void)release{    return;}- (id)autorelease{    return tipsWindow;}- (id)init{    CGRect frame = [UIApplication sharedApplication].statusBarFrame;    self = [super initWithFrame:frame];    if (self) {                self.autoresizingMask = UIViewAutoresizingFlexibleWidth;        self.windowLevel = UIWindowLevelStatusBar + 10;        self.backgroundColor = [UIColor clearColor];                _tipsIcon = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, ICON_WIDTH, ICON_WIDTH)];        _tipsIcon.autoresizingMask = UIViewAutoresizingFlexibleHeight;        _tipsIcon.backgroundColor = [UIColor redColor];        [self addSubview:_tipsIcon];        [_tipsIcon release];                _tipsLbl = [[UILabel alloc] initWithFrame:self.bounds];        #ifdef NSTextAlignmentRight            _tipsLbl.textAlignment = NSTextAlignmentLeft;            _tipsLbl.lineBreakMode = NSLineBreakByTruncatingTail;        #else            _tipsLbl.textAlignment = 0; // means UITextAlignmentLeft            _tipsLbl.lineBreakMode = 4; //UILineBreakModeTailTruncation;        #endif        _tipsLbl.textColor = [UIColor whiteColor];        _tipsLbl.font = [UIFont systemFontOfSize:12];        _tipsLbl.backgroundColor = [UIColor blackColor];        _tipsLbl.autoresizingMask = UIViewAutoresizingFlexibleHeight;        [self addSubview:_tipsLbl];        [_tipsLbl release];                [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateOrientation:) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil];    }        return self;}- (void)dealloc{    [[NSNotificationCenter defaultCenter] removeObserver:self];    [tipsMessage release];        [super dealloc];}#pragma mark -#pragma mark Notification Handle- (void)updateOrientation:(NSNotification*)noti{    UIInterfaceOrientation newOrientation = [[noti.userInfo valueForKey:UIApplicationStatusBarOrientationUserInfoKey] integerValue];    NSLog(@"new orientation: %d", newOrientation);        switch (newOrientation) {        case UIInterfaceOrientationPortrait:        {            self.transform = CGAffineTransformIdentity;            self.frame = CGRectMake(0, 0, SCREEN_WIDTH, HEIGHT);                        break;        }        case UIInterfaceOrientationPortraitUpsideDown:        {            // 先转矩阵,坐标系统落在屏幕有右下角,朝上是y,朝左是x            self.transform = CGAffineTransformMakeRotation(M_PI);            self.center = CGPointMake(SCREEN_WIDTH / 2, SCREEN_HEIGHT - HEIGHT / 2);            self.bounds = CGRectMake(0, 0, SCREEN_WIDTH, HEIGHT);                        break;        }        case UIInterfaceOrientationLandscapeLeft:        {            self.transform = CGAffineTransformMakeRotation(-M_PI_2);            // 这个时候坐标轴已经转了90°,调整x相当于调节竖向调节,y相当于横向调节            self.center = CGPointMake(HEIGHT / 2, [UIScreen mainScreen].bounds.size.height / 2);            self.bounds = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.height, HEIGHT);                        break;        }        case UIInterfaceOrientationLandscapeRight:        {            // 先设置transform,在设置位置和大小            self.transform = CGAffineTransformMakeRotation(M_PI_2);            self.center = CGPointMake(SCREEN_WIDTH - HEIGHT / 2, SCREEN_HEIGHT / 2);            self.bounds = CGRectMake(0, 0, SCREEN_HEIGHT, HEIGHT);                        break;        }        default:            break;    }}#pragma mark -#pragma mark Tips Method/* * @brief show tips message on statusBar */- (void)showTips:(NSString*)tips{    if (_hideTimer) {        [_hideTimer invalidate];        [_hideTimer release];    }        _tipsIcon.image = nil;    _tipsIcon.hidden = YES;        CGSize size = [tips sizeWithFont:_tipsLbl.font constrainedToSize:CGSizeMake(320, 30)];    size.width += TIPMESSAGE_RIGHT_MARGIN;    if (size.width > self.bounds.size.width - ICON_WIDTH) {        size.width = self.bounds.size.width - ICON_WIDTH;    }        _tipsLbl.frame = CGRectMake(self.bounds.size.width - size.width, 0, size.width, self.bounds.size.height);    _tipsLbl.text = tips;        [self makeKeyAndVisible];}- (void)showTips:(NSString*)tips hideAfterDelay:(NSInteger)seconds{    [self showTips:tips];        _hideTimer = [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(hideTips) userInfo:nil repeats:NO];}/* * @brief show tips icon and message on statusBar */- (void)showTipsWithImage:(UIImage*)tipsIconImage message:(NSString*)message{    if (_hideTimer) {        [_hideTimer invalidate];        [_hideTimer release];    }        CGSize size = [message sizeWithFont:_tipsLbl.font constrainedToSize:self.bounds.size];    size.width += TIPMESSAGE_RIGHT_MARGIN;    if (size.width > self.bounds.size.width - ICON_WIDTH) {        size.width = self.bounds.size.width - ICON_WIDTH;    }        _tipsLbl.frame = CGRectMake(self.bounds.size.width - size.width, 0, size.width, self.bounds.size.height);    _tipsLbl.text = message;        _tipsIcon.center = CGPointMake(self.bounds.size.width - _tipsLbl.bounds.size.width - ICON_WIDTH / 2 - ICON_RIGHT_MARGIN, self.bounds.size.height / 2);    _tipsIcon.image = tipsIconImage;    _tipsIcon.hidden = NO;        [self makeKeyAndVisible];}- (void)showTipsWithImage:(UIImage*)tipsIconImage message:(NSString*)message hideAfterDelay:(NSInteger)seconds{    [self showTipsWithImage:tipsIconImage message:message];        _hideTimer = [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(hideTips) userInfo:nil repeats:NO];}/* * @brief hide tips window */- (void)hideTips{    self.hidden = YES;}@end
复制代码

  该状态栏提示控件实现了添加提示图标和提示文字,以及自动隐藏等功能。显示和隐藏动画实现起来比较简单,控件中就没有实现,大家可以根据需要随意发挥。该控件使用单例模式,接口非常简单,使用起来很方便。上面代码相信大家都能看得懂,这里就不展开讲了,有什么问题欢迎讨论。

 

注: 转载请注明出去,有什么不对的地方,欢迎指正。

0 0
原创粉丝点击