用户的需求千奇百怪,总是让你不得不对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中并呈现的(实际上你可能想子类化它),它的实际效果如下。
