自绘控件:自定义Tab Bar控件

来源:互联网 发布:网络博客vs88.com 编辑:程序博客网 时间:2024/04/29 01:07

用户的需求千奇百怪,总是让你不得不对iPhone一些控件的外观或功能做一些改变。众所周知,苹果自带的控件可定制性着实很差,这着让人很恼火,却又别无他法。幸好有网络的存在,我们可以找到许多别人已经做好的控件。CocoaChina会员 “jordenwu”做了一个自定义的Tab Bar控件,笔者的代码也是在jordenwu的基础上修改来的。修改的目的有两个:一、jordenwu把许多本来应该在TabBar中的代码放到Controller中来了,这样虽然更便于程序员从各方面定制Tab Bar的外观,比如背景图、按钮图标(各种状态下的),但代码过于分散,不利于调用。其实很多人都希望控件的使用越简单越好,而不是要自己去写很多用于控制和定制的语句;二、原来的控件功能写得比较多,但也使得代码比较复杂,代码比较凌乱,不利于学习和理解,因此我决定对它进行简化,把核心的部分抽离出来,便于大家参考和学习。

代码中有大量的Core Graphics绘图函数的使用,正是所谓的“自绘制”控件,而不是简单的几个现有控件的组合。对于习惯Objective C的人来说,代码有些难懂,但没有关系,多看几遍,自然就熟悉了。

一、Tab Bar控件的实现

新建window-based application,新建类MyTabBar,继承自UIView。

#import <Foundation/Foundation.h>

@protocol MyTabBarDelegate

 

-(void)touchUpInsideItemAtIndex:(int)index;

 

@end

 

@interface MyTabBar : UIView

{

NSObject <MyTabBarDelegate> *delegate;

NSMutableArray* buttons;

NSMutableArray* itemImages;

float height,width;

}

@property (nonatomic, retain) NSMutableArray*buttons,*itemImages;

@property (assign)float height;

 

- (id) initWithImages:(NSArray*)images delegate:(NSObject <MyTabBarDelegate>*)myDelegate;

@end

首先声明协议MyTabBarDelegate,并在其中定义了一个方法。这个方法会委托delegate来相应Tab Bar按钮的touch upinside事件。

然后声明必要的变量。Delegate负责实现MyTabBarDelegate,即实现Tab Bar按钮的触摸事件处理。在后面,我将MyTabBar的delegate设置为一个ViewController,这样这个ViewController就变成了TabBar Controller。

buttons用于存放Tab Bar上的所有按钮实例。itemImages用于存放按钮图标。height、width用于保存Tab Bar控件的高、宽。

实例化方法initWithImages:delegate:用指定的按钮图片(数组)和delegate对象初始化Tab Bar实例。

下面看实现。

#import "MyTabBar.h"

 

#defineARROW_IMAGE_TAG 2394859

 

@interface MyTabBar(PrivateMethods)

-(UIButton*)createButton:(int)i width:(float)width;

-(UIImage*)doImageMask:(UIImage*)upImage size:(CGSize)targetSize downImage:(UIImage*)downImage;

-(UIImage*) createDownImage:(CGSize)size downImage:(UIImage*)downImage;

-(UIImage*) fillImageWhite:(UIImage*)originImage;

- (UIImage*) selectedItemImage;

-(void) dimAllButtonsExcept:(UIButton*)selectedButton;

- (void) addArrowAtIndex:(NSUInteger)itemIndex;

@end

 

@implementation MyTabBar

@synthesizebuttons,itemImages,height;

- (id) initWithImages:(NSArray*)images delegate:(NSObject <MyTabBarDelegate>*)myDelegate

{

if (self = [super init])

{

itemImages=[[NSMutableArray alloc]initWithArray:images];

delegate = myDelegate;

CGRect rect=[[UIScreen mainScreen] bounds];

width=rect.size.width;

UIImage* topImage = [UIImage imageNamed:@"TabBarGradient.png"];

height=topImage.size.height*2;

//==================开始core graphic绘图===================

// 初始化上下文

UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, topImage.size.height*2), NO, 0.0);

// 原图2x22,拉伸为320x22,帽宽、高为0

UIImage* stretchedTopImage = [topImage stretchableImageWithLeftCapWidth:0 topCapHeight:0];

[stretchedTopImagedrawInRect:CGRectMake(0, 0, width, topImage.size.height)];

// tab bar的下半部分是黑色,设置为当前上下文的填充色和边框色为黑色,然后填充tab bar下半部分

[[UIColor blackColor] set];

CGContextFillRect(UIGraphicsGetCurrentContext(),CGRectMake(0, topImage.size.height, width, topImage.size.height));

// 将当前绘制的内容设置为背景图

UIImage* backgroundImage = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

//====================绘图结束====================

UIImageView* backgroundImageView =[[[UIImageView alloc] initWithImage:backgroundImage] autorelease];

backgroundImageView.frame = CGRectMake(0, 0, backgroundImage.size.width, backgroundImage.size.height);

[self addSubview:backgroundImageView];

self.frame = backgroundImageView.frame;

self.buttons = [[NSMutableArray alloc] initWithCapacity:images.count];

CGFloat offsetX = 0;

for (NSUInteger i = 0 ; i < itemImages.count ; i++)

{

float itemWidth=self.frame.size.width/itemImages.count;

//生成tab bar的按钮

UIButton* button = [self createButton:i width:itemWidth];

//添加action

[button addTarget:self action:@selector(touchUpInsideAction:)forControlEvents:UIControlEventTouchUpInside];

[buttons addObject:button];

//重设按钮的 x 坐标

button.frame = CGRectMake(offsetX, 0.0, button.frame.size.width, button.frame.size.height);

[self addSubview:button];

//Advance the horizontal offset

offsetX += itemWidth;

}

}

return self;

}

-(UIButton*)createButton:(int)i width:(float)_width{

// 创建合适大小的UIButton

UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom];

button.frame = CGRectMake(0.0, 0.0, _width, self.frame.size.height);

// 从itemImage中获取按钮图片

UIImage* rawButtonImage = [UIImage imageNamed:[itemImages objectAtIndex:i]];

// 将rawButtonImage与backgroundImage合成一张图片。backgroudImage为空时,制造一张灰色图片

UIImage* buttonImage = [self doImageMask:rawButtonImage size:button.frame.size downImage:nil];

// backgroudImage不为空,将制造一张rawButtonImage与backgroundImage通过Mask合成的图片

UIImage* buttonPressedImage = [self doImageMask:rawButtonImage size:button.frame.size downImage:[UIImage imageNamed:@"TabBarItemSelectedBackground.png"]];

// 将上述两张合成图片作为按钮的Normal和Select状态时的图片

[button setImage:buttonImage forState:UIControlStateNormal];

[button setImage:buttonPressedImage forState:UIControlStateHighlighted];

[button setImage:buttonPressedImage forState:UIControlStateSelected];

// 设置按钮两个状态时的背景图

[button setBackgroundImage:[self selectedItemImage] forState:UIControlStateHighlighted];

[button setBackgroundImage:[self selectedItemImage] forState:UIControlStateSelected];

button.adjustsImageWhenHighlighted =NO;// 当为YES时,按钮按下时会自动变暗。

return button;

}

// 使用遮罩技术,将两张图片进行合成

-(UIImage*)doImageMask:(UIImage*)upImage size:(CGSize)targetSize downImage:(UIImage*)downImage

{

// 选中时,背景为蓝色,未选中时,背景为灰色

UIImage* backgroundImage = [self createDownImage:upImage.size downImage:downImage];

// 将上层图片转换为白色背景黑色前景(用于做遮罩)

UIImage* bwImage = [self fillImageWhite:upImage];

// 用黑白两色的upImage图片创建一个遮罩

CGImageRef imageMask = CGImageMaskCreate(CGImageGetWidth(bwImage.CGImage),

 CGImageGetHeight(bwImage.CGImage),

 CGImageGetBitsPerComponent(bwImage.CGImage),

 CGImageGetBitsPerPixel(bwImage.CGImage),

 CGImageGetBytesPerRow(bwImage.CGImage),

 CGImageGetDataProvider(bwImage.CGImage), NULL, YES);

// 用这个遮罩和下层图片合成按钮图片

CGImageRef tabBarImageRef = CGImageCreateWithMask(backgroundImage.CGImage, imageMask);

UIImage* tabBarImage = [UIImage imageWithCGImage:tabBarImageRefscale:upImage.scale

 orientation:upImage.imageOrientation];

// Cleanup

CGImageRelease(imageMask);

CGImageRelease(tabBarImageRef);

// ==============重新开始绘制按钮图片==============

UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0.0);

// 将按钮图片绘制在targetSize的中心对齐

[tabBarImage drawInRect:CGRectMake((targetSize.width/2.0) - (upImage.size.width/2.0),

  (targetSize.height/2.0) - (upImage.size.height/2.0),

  upImage.size.width,

  upImage.size.height)];

// 将绘制的图片返回

UIImage* resultImage = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

return resultImage;

}

// 创建背景图片

-(UIImage*) createDownImage:(CGSize)size downImage:(UIImage*)downImage

{

//=================开始 Core Graphics 绘图=================

UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);

if (downImage)//如果使用了背景图片,说明是选中状态,绘制背景图片(即TabBarItemSelectedBackground.png)

{

// 在上层图片的中心对齐绘制背景图片

[downImagedrawInRect:CGRectMake((size.width - CGImageGetWidth(downImage.CGImage)) / 2,

 (size.height - CGImageGetHeight(downImage.CGImage)) / 2,

 CGImageGetWidth(downImage.CGImage),

 CGImageGetHeight(downImage.CGImage))];

}

else// 否则为为选中状态,绘制一个灰色填充的矩形

{

[[UIColor lightGrayColor] set];

UIRectFill(CGRectMake(0, 0, size.width, size.height));

}

UIImage* result = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

//===================结束绘图===================

return result;

}

// 将图片设置为白色背景,方便做遮罩

-(UIImage*) fillImageWhite:(UIImage*)originImage

{

CGRect imageRect = CGRectMake(0, 0, CGImageGetWidth(originImage.CGImage), CGImageGetHeight(originImage.CGImage));

// 创建位图上下文

CGContextRef context = CGBitmapContextCreate(NULL, // 内存图片数据

 imageRect.size.width, // 宽

 imageRect.size.height, // 高

 8, // 色深

 0, // 每行字节数

 CGImageGetColorSpace(originImage.CGImage), // 颜色空间

 kCGImageAlphaPremultipliedLast);// alpha通道,RBGA

// 设置当前上下文填充色为白色(RGBA值)

CGContextSetRGBFillColor(context,1,1,1,1);

CGContextFillRect(context,imageRect);

// 用 originImage 作为 clipping mask(选区)

CGContextClipToMask(context,imageRect, originImage.CGImage);

// 设置当前填充色为黑色

CGContextSetRGBFillColor(context,0, 0, 0, 1);

// 在clipping mask上填充黑色

CGContextFillRect(context,imageRect);

CGImageRef newCGImage = CGBitmapContextCreateImage(context);

UIImage* newImage = [UIImage imageWithCGImage:newCGImagescale:originImage.scale orientation:originImage.imageOrientation];

// Cleanup

CGContextRelease(context);

CGImageRelease(newCGImage);

return newImage;

}

// 绘制按钮选中时的背景图片

- (UIImage*) selectedItemImage

{

// 用TabBarGradient 图片的高度计算背景图高度

UIImage* tabBarGradient = [UIImage imageNamed:@"TabBarGradient.png"];

CGSize tabBarItemSize = CGSizeMake(self.frame.size.width/itemImages.count, tabBarGradient.size.height*2);

UIGraphicsBeginImageContextWithOptions(tabBarItemSize, NO, 0.0);

// 创建带圆角的的拉伸图,即左右4个像素的圆角是不用拉伸的

[[[UIImage imageNamed:@"TabBarSelection.png"] stretchableImageWithLeftCapWidth:4.0 topCapHeight:0]

 drawInRect:CGRectMake(0, 4.0, tabBarItemSize.width, tabBarItemSize.height-4.0)]; // 绘图区域下移4像素

// 返回拉伸图

UIImage* selectedItemImage = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

return selectedItemImage;

}

-(void)dealloc{

[buttons release];

[itemImages release];

[super dealloc];

}

- (void)touchUpInsideAction:(UIButton*)button

{

[self dimAllButtonsExcept:button];

if (delegate!=nil && [delegate respondsToSelector:@selector(touchUpInsideItemAtIndex:)])

[delegate touchUpInsideItemAtIndex:[buttons indexOfObject:button]];

}

-(void) dimAllButtonsExcept:(UIButton*)selectedButton

{

for (UIButton* button in buttons)

{

if (button == selectedButton)

{

button.selected = YES;

button.highlighted = button.selected ? NO : YES;

//锦上添花的功能:在选中的按钮上方显示一个小箭头

UIImageView* tabBarArrow = (UIImageView*)[self viewWithTag:ARROW_IMAGE_TAG];

NSUInteger selectedIndex = [buttons indexOfObjectIdenticalTo:button];

if (tabBarArrow)

{//使用动画将小箭头移动至合适位置

[UIView beginAnimations:nil context:nil];

[UIView setAnimationDuration:0.2];

CGRect frame = tabBarArrow.frame;

frame.origin.x = button.center.x;

tabBarArrow.frame = frame;

[UIView commitAnimations];

}

else

{

[self addArrowAtIndex:selectedIndex];

}

}

else

{

button.selected = NO;

button.highlighted = NO;

}

}

}

// 在选定按钮上加一个箭头

- (void) addArrowAtIndex:(NSUInteger)itemIndex

{

UIImage* tabBarArrowImage = [UIImage imageNamed:@"TabBarNipple.png"];

UIImageView* tabBarArrow = [[[UIImageView alloc] initWithImage:tabBarArrowImage] autorelease];

tabBarArrow.tag = ARROW_IMAGE_TAG;

// 箭头将位于按钮上方并稍微下移2个像素的位置

CGFloat y = -tabBarArrowImage.size.height + 2;

// 箭头x坐标将和按钮的中心x坐标对齐

UIButton* btn=[buttons objectAtIndex:itemIndex];

CGFloat x= btn.center.x;

tabBarArrow.frame = CGRectMake(x, y,tabBarArrowImage.size.width, tabBarArrowImage.size.height);

[self addSubview:tabBarArrow];

}

@end

初始化方法很重要,我们在其中绘制控件(背景图),计算每个按钮的位置,并根据用户传进的按钮图片文件生成Tab Bar按钮。

createButton:width:方法创建了一个按钮。按钮图片都不是直接使用用户指定的图片,而需要进行加工。使用了Core Graphics中的遮罩技术。将原来的图标转换为黑白图片,其中黑色部分表示可以透视的部分,白色部分表示被遮住的部分。把黑白遮罩叠在另一张有颜色的位图上,白色的部分被遮住,黑色的部分透到前面来——这就是所谓的位图遮罩。整个过程分成了3个步骤,用3个方法来完成:

1、fillImageWhite:originImage方法

这个方法负责把一张按钮图片加工成黑白两色的“遮罩”。

2、createDownImage:(CGSize)size downImage:(UIImage*)downImage方法

这个方法负责根据参数产生被遮住的(即叠在下面的那张)图片。如果dowImage不为空,则这张图片由downImage生成。如果为空,直接绘制一个用灰色填充的矩形。

3、doImageMask: size: downImage:方法

这个方法将前面步骤产生的两张图片“叠加”起来。

这3个方法使用了Core Graphics绘图技术,你可能需要回忆一些有关的知识。

二、Tab Bar Controller的实现

MyTabBarController是一个普通的ViewController,不过我们在初始化的时候为它加入了一个Tab Bar控件(位于view的最底部)。为了响应TabBar按钮,它必须实现我们前面所定义的MyTabBarDelegate协议。

#import <UIKit/UIKit.h>

#import "MyTabBar.h"

 

@interface MyTabBarController :UIViewController<MyTabBarDelegate> {

MyTabBar* tabBar;

NSMutableArray* viewControllers,*itemImages;

}

@property (retain,nonatomic)NSMutableArray*viewControllers,*itemImages;

-(void)touchUpInsideItemAtIndex:(int)index;

-(id)initWithViewControllers:(NSArray*)controllersitemImages:(NSArray*)images;

@end

 

#import "MyTabBarController.h"

 

#define SELECTED_TAG 98456345

@implementation MyTabBarController

@synthesize itemImages,viewControllers;

-(id)initWithViewControllers:(NSArray*)controllersitemImages:(NSArray*)images{

self=[super init];

if (self) {

// 准备一些测试数据

UIViewController*controller1 = [[[UIViewController alloc] init] autorelease];

controller1.view.backgroundColor =[UIColor redColor];

UIViewController*controller2 = [[[UIViewController alloc] init] autorelease];

controller2.view.backgroundColor =[UIColor greenColor];

NSArray* controllers=[[NSArray alloc]initWithObjects:controller1,controller2,nil];

NSArray* images=[[NSArray alloc]initWithObjects:@"chat.png",@"compose-at.png",nil];

// ========end

itemImages=[[NSMutableArray alloc]initWithArray:images];

viewControllers=[[NSMutableArray alloc]initWithArray:controllers];

tabBar=[[MyTabBar alloc]initWithImages:itemImages delegate:self];

CGRect rect=[[UIScreen mainScreen]bounds];

self.view=[[UIView alloc]initWithFrame:rect];

rect=CGRectMake(0, rect.size.height-tabBar.height, tabBar.bounds.size.width, tabBar.bounds.size.height);

tabBar.frame=rect;

[self.view addSubview:tabBar];

}

return self;

}

- (void)dealloc {

[itemImages release];

[viewControllers release];

    [super dealloc];

}

-(void)touchUpInsideItemAtIndex:(int)index{

// 删除当前控制器视图

UIView* currentView = [self.view viewWithTag:SELECTED_TAG];

[currentView removeFromSuperview];

// 获取选定的控制器

UIViewController*viewController = [viewControllers objectAtIndex:index];

// 使用TabBarGradient图片高度进行计算

UIImage* tabBarGradient = [UIImage imageNamed:@"TabBarGradient.png"];

// 计算视图的正确尺寸

viewController.view.frame = CGRectMake(0,0,self.view.frame.size.width, self.view.frame.size.height-(tabBarGradient.size.height*2));

// 设置当前视图的tag

viewController.view.tag = SELECTED_TAG;

// 加载当前视图到窗口

[self.view insertSubview:viewController.view belowSubview:tabBar];

}

 

@end

可以看到,为了便于测试,我们直接在初始化方法中加了一些测试代码,你在真正使用时,可能得移除它们。在这里,我们是直接把MyTabBarController实例化在应用程序委托AppDelegate中并呈现的(实际上你可能想子类化它),它的实际效果如下。