有的时候,我们需要使用非规则形状的按钮。UIButton允许你选择带有alpha通道的图像。比如,我使用下面四个图像:
然后用Interface Builder创建用户定义按钮,你可以透过图像的透明部分看到后面的按钮(假定按钮未定义为opaque)。.然而 UIButton 的点击测试(hit-testing)并未考虑图像的透明性,所以当你将图像重叠放置时,如图所示:
如果你点击此处:
默认的点击测试的结果是绿色菱形按钮被按下,而不是蓝色按钮。当然这可能就是你需要的效果,但大部分情况下并非如你所愿。那么怎样才能让你的程序正常工作?实际上很简单,你只需要一个UIButton的子类并重写点击测试方法。
然而,首先你需要一个方法能确定图像上指定点是透明的。遗憾的是UIImage无法像Cocoa为NSImage提供的 NSBitmapRepresentation 那样方便地访问位图数据。但是每个UIImage都具有一个称为CGImage的属性可以访问内部图像数据,Apple发布了一篇技术文章介绍了怎样通过CGImageRef访问内部位图数据 。
根据这篇文章的介绍,我们很容易就写出一个方法,它以CGPoint为参数,根据该点是否透明(0)与否返回YES或NO。
UIImage-Alpha.h
2
3
4
5
6
7
8
@interface UIImage( Alpha)
- ( NSData * ) ARGBData;
- ( BOOL ) isPointTransparent: ( CGPoint) point;
@end
UIImage-Alpha.m
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
{
CGContextRef context = NULL ;
CGColorSpaceRef colorSpace;
void * bitmapData;
int bitmapByteCount;
int bitmapBytesPerRow;
size_t pixelsWide = CGImageGetWidth( inImage) ;
size_t pixelsHigh = CGImageGetHeight( inImage) ;
bitmapBytesPerRow = ( pixelsWide * 4) ;
bitmapByteCount = ( bitmapBytesPerRow * pixelsHigh) ;
colorSpace = CGColorSpaceCreateDeviceRGB( ) ;
if ( colorSpace == NULL )
return nil ;
bitmapData = malloc ( bitmapByteCount ) ;
if ( bitmapData == NULL )
{
CGColorSpaceRelease( colorSpace ) ;
return nil ;
}
context = CGBitmapContextCreate ( bitmapData,
pixelsWide,
pixelsHigh,
8,
bitmapBytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedFirst) ;
if ( context == NULL )
{
free ( bitmapData) ;
fprintf ( stderr , "Context not created!" ) ;
}
CGColorSpaceRelease( colorSpace ) ;
return context;
}
@implementation UIImage( Alpha)
- ( NSData * ) ARGBData
{
CGContextRef cgctx = CreateARGBBitmapContext( self.CGImage) ;
if ( cgctx == NULL )
return nil ;
size_t w = CGImageGetWidth( self.CGImage) ;
size_t h = CGImageGetHeight( self.CGImage) ;
CGRect rect = { { 0,0} ,{ w,h} } ;
CGContextDrawImage( cgctx, rect, self.CGImage) ;
void * data = CGBitmapContextGetData ( cgctx) ;
CGContextRelease( cgctx) ;
if ( ! data)
return nil ;
size_t dataSize = 4 * w * h; // ARGB = 4 8-bit components
return [ NSData dataWithBytes: data length: dataSize] ;
}
- ( BOOL ) isPointTransparent: ( CGPoint) point
{
NSData * rawData = [ self ARGBData] ; // See about caching this
if ( rawData == nil )
return NO ;
size_t bpp = 4 ;
size_t bpr = self.size.width * 4 ;
NSUInteger index = point.x * bpp + ( point.y * bpr) ;
char * rawDataBytes = ( char * ) [ rawData bytes] ;
return rawDataBytes[ index] == 0 ;
}
@end
一旦我们有能力确定图像中的某点是否透明,我们就可以编写UIButton的子类,重写hitTest:withEvent: 方法。它将返回一个UIView的实例。如果该点在此视图或其子视图中未被点击,那么将返回nil。如果点击在其子视图,那么将返回点击中的子视图,如果 点击中视图,那么返回视图本身。
然而,我们可以进行一些简化,这是因为尽管UIButton继承了UIView,技术上可能具有子视图,但这非常的少见,而且Interface Builder并不支持这样做。所以在本文的实现中并不考虑子视图。
IrregularShapedButton.h
2
3
4
5
6
7
@interface IrregularShapedButton : UIButton {
}
@end
IrregularShapedButton.m
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 "UIImage-Alpha.h"
@implementation IrregularShapedButton
- ( UIView * ) hitTest: ( CGPoint) point withEvent: ( UIEvent * ) event
{
if ( ! CGRectContainsPoint( [ self bounds] , point) )
return nil ;
else
{
UIImage * displayedImage = [ self imageForState: [ self state] ] ;
if ( displayedImage == nil ) // No image found, try for background image
displayedImage = [ self backgroundImageForState: [ self state] ] ;
if ( displayedImage == nil ) // No image could be found, fall back to
return self;
BOOL isTransparent = [ displayedImage isPointTransparent: point] ;
if ( isTransparent)
return nil ;
}
return self;
}
@end
将Interface Builder中的四个图像按钮改为IrregularShapedButton,它们将正常工作了。原文见:Irregularly Shaped UIButton
头文件:
#define kMinimumGestureLength 25 #define kMaximumVariance 5 #import <UIKit/UIKit.h> @interface TouchesViewController : UIViewController { IBOutlet UILabel *label; CGPoint gestureStartPoint; } @end
实现文件:
#import "TouchesViewController.h" @implementation TouchesViewController - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; gestureStartPoint = [touch locationInView:self.view]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ UITouch *touch = [touches anyObject]; CGPoint currentPosition = [touch locationInView:self.view]; CGFloat deltaX = fabsf(gestureStartPoint.x - currentPosition.x); CGFloat deltaY = fabsf(gestureStartPoint.y - currentPosition.y); if(deltaX >= kMinimumGestureLength && deltaY <= kMaximumVariance){ label.text = @"Horizontal Swipe Detected"; [self performSelector:@selector(eraseText) withObject:nil afterDelay:3]; } else if(deltaY >= kMinimumGestureLength && deltaX <= kMaximumVariance){ label.text = @"Vertical Swipe Detected"; [self performSelector:@selector(eraseText) withObject:nil afterDelay:3]; } } - (void)eraseText{ label.text = @""; } - (void)dealloc { [super dealloc]; } @end
Struts1和Struts2的区别和对比:
Action 类:
• Struts1要求Action类继承一个抽象基类。Struts1的一个普遍问题是使用抽象类编程而不是接口。
• Struts 2 Action类可以实现一个Action接口,也可实现其他接口,使可选和定制的服务成为可能。Struts2提供一个ActionSupport基类去 实现 常用的接口。Action接口不是必须的,任何有execute标识的POJO对象都可以用作Struts2的Action对象。
线程模式:
• Struts1 Action是单例模式并且必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。单例策略限制了Struts1 Action能作的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的。
• Struts2 Action对象为每一个请求产生一个实例,因此没有线程安全问题。(实际上,servlet容器给每个请求产生许多可丢弃的对象,并且不会导致性能和垃圾回收问题)
Servlet 依赖:
• Struts1 Action 依赖于Servlet API ,因为当一个Action被调用时HttpServletRequest 和 HttpServletResponse 被传递给execute方法。
• Struts 2 Action不依赖于容器,允许Action脱离容器单独被测试。如果需要,Struts2 Action仍然可以访问初始的request和response。但是,其他的元素减少或者消除了直接访问HttpServetRequest 和 HttpServletResponse的必要性。
可测性:
• 测试Struts1 Action的一个主要问题是execute方法暴露了servlet API(这使得测试要依赖于容器)。一个第三方扩展--Struts TestCase--提供了一套Struts1的模拟对象(来进行测试)。
• Struts 2 Action可以通过初始化、设置属性、调用方法来测试,“依赖注入”支持也使测试更容易。
捕获输入:
• Struts1 使用ActionForm对象捕获输入。所有的ActionForm必须继承一个基类。因为其他JavaBean不能用作ActionForm,开发者经 常创建多余的类捕获输入。动态Bean(DynaBeans)可以作为创建传统ActionForm的选择,但是,开发者可能是在重新描述(创建)已经存 在的JavaBean(仍然会导致有冗余的javabean)。
• Struts 2直接使用Action属性作为输入属性,消除了对第二个输入对象的需求。输入属性可能是有自己(子)属性的rich对象类型。Action属性能够通过 web页面上的taglibs访问。Struts2也支持ActionForm模式。rich对象类型,包括业务对象,能够用作输入/输出对象。这种 ModelDriven 特性简化了taglib对POJO输入对象的引用。
表达式语言:
• Struts1 整合了JSTL,因此使用JSTL EL。这种EL有基本对象图遍历,但是对集合和索引属性的支持很弱。
• Struts2可以使用JSTL,但是也支持一个更强大和灵活的表达式语言--"Object Graph Notation Language" (OGNL).
绑定值到页面(view):
• Struts 1使用标准JSP机制把对象绑定到页面中来访问。
• Struts 2 使用 "ValueStack"技术,使taglib能够访问值而不需要把你的页面(view)和对象绑定起来。ValueStack策略允许通过一系列名称相同但类型不同的属性重用页面(view)。
类型转换:
• Struts 1 ActionForm 属性通常都是String类型。Struts1使用Commons-Beanutils进行类型转换。每个类一个转换器,对每一个实例来说是不可配置的。
• Struts2 使用OGNL进行类型转换。提供基本和常用对象的转换器。
校验:
• Struts 1支持在ActionForm的validate方法中手动校验,或者通过Commons Validator的扩展来校验。同一个类可以有不同的校验内容,但不能校验子对象。
• Struts2支持通过validate方法和XWork校验框架来进行校验。XWork校验框架使用为属性类类型定义的校验和内容校验,来支持chain校验子属性
Action执行的控制:
• Struts1支持每一个模块有单独的Request Processors(生命周期),但是模块中的所有Action必须共享相同的生命周期。
• Struts2支持通过拦截器堆栈(Interceptor Stacks)为每一个Action创建不同的生命周期。堆栈能够根据需要和不同的Action一起使用。