自定義形狀按鈕的實現
對于大多數iOS開發來說,這個是很少遇到的,畢竟一個不規則的按鈕再移動端不常見,但是免不了會遇到某些特殊的需求,比如一個餅狀圖,比如一個扇形圖,比如中國地圖中的每個省.....
我們很可能在某一次開發中需要實現不規則形狀的按鈕,那么我們怎么去實現呢?
其實這只是一個很小很小的知識點,卻有很多人不會去考慮,所以就遇到了這樣的情況
偶然有一天,朋友給了我一套產品原型,他說叫我幫他寫幾個按鈕,很簡單的按鈕,我當時感覺有坑,但是也沒有想太多,想著本來這段時間有點閑,就幫下忙,
于是他發來原型圖
喲,就是很簡單的4+1個按鈕蠻
先寫4個正方形的九宮格式的按鈕,再在中心加一個圓形按鈕就好了呀.
于是接下來他又發來產品設計圖
果然,有了設計之后,瞬間好看了好多,
但是想象中也不復雜呀.
先用一個view當底圖,然后5個按鈕加在這個view上,然后view來個半徑,不就結束了嗎?
但是事實真的是這么簡單嗎?
顯然不是,一個很明顯的結果就是,你會發現你使用了圓形半徑的不顯示的部分仍然能夠響應按鈕的點擊.
這個是什么鬼,其實很簡單的原理,就是雖然我們設置了半徑,讓圓形外的圖層不顯示,但是并不是代表其不存在,其仍然是可以點擊的,這樣在某些情況下就不符合按鈕的設計標準了,因為我們想要的是所見即所得
那么所見即所得便成了我要實現的目標.
還得從按鈕上下手,不要想得這么簡單,雖然這個案例是一個很簡單的比較偏正常形狀的一個按鈕,但是我需要將其想象成一個很復雜的不規則的圖形來處理
1.因為是自定義形狀的按鈕,那么說白了,它還是一個按鈕,我們沒有必要自定義復雜的控件,繼承UIButton即可
2.構思如何實現特殊形狀
其實這塊很容易想到,實現一個圓形按鈕,就是對layer進行修改形狀而已
如果你有閱讀過iOS核心動畫高級技巧的話,你很容易發現我們想要的東西
是的,這里我們能看到幾個關鍵點,一個原來的背景+一個mask遮罩 = 一個自定義形狀的圖形
引用到我們想要的按鈕上,我們就會發現同樣適用,
那么我們必須要在適用圖片來制作mask嗎?
很顯然,我們在開發中不可能這么去做,那么我們如何自定義一個形狀呢?
于是我們能夠很方便的使用UIBezierPath來繪制成我們想要的形狀,然后使用CAShapeLayer來根據形狀創建圖層的路徑
創建完成之后,我們使用這樣的圖層來當做按鈕的mask即可
所以對于我們來說比較有用的就是一個貝塞爾曲線的定制
于是很簡單的一個不規則按鈕的類即可實現
#import <UIKit/UIKit.h>
@interface IrregularButton : UIButton
@property(nonatomic, strong)UIBezierPath *maskPath;
@end
#import "IrregularButton.h"
@implementation IrregularButton
-(void)setMaskPath:(UIBezierPath *)maskPath{
_maskPath = maskPath;
CAShapeLayer *layer = [[CAShapeLayer alloc]init];
layer.path = maskPath.CGPath;
self.layer.mask = layer;
}
@end
當我們需要定制一個特殊按鈕的時候,我們只要繼承該類 ,或者直接使用該類
,并給maskPath賦予一個我們想要的路徑即可
于是上面的四個半圓按鈕甚至全圓形的按鈕我們就可以輕松實現
#import "IrregularButton.h"
typedef NS_ENUM(NSUInteger, QuarterCircleType) {
QuarterCircleTypeTopNone,
QuarterCircleTypeTopLeft,
QuarterCircleTypeTopRight,
QuarterCircleTypeBottomLeft,
QuarterCircleTypeBottomRight,
QuarterCircleTypeAllCircle,
};
@interface QuarterCircleButton : IrregularButton
@property(nonatomic, assign)QuarterCircleType circleType;
@end
#import "QuarterCircleButton.h"
@implementation QuarterCircleButton
-(void)setFrame:(CGRect)frame{
[super setFrame:frame];
self.circleType = self.circleType;
}
-(void)setCircleType:(QuarterCircleType)circleType{
_circleType = circleType;
CGFloat width = self.frame.size.width;
CGFloat height = self.frame.size.height;
switch (circleType) {
case QuarterCircleTypeTopLeft:{
CGPoint bottomRightPoint = CGPointMake(width, height);
CGPoint bottomLeftPoint = CGPointMake(0, height);
UIBezierPath *path = [[UIBezierPath alloc]init];
[path moveToPoint:bottomRightPoint];
[path addLineToPoint:bottomLeftPoint];
[path addArcWithCenter:bottomRightPoint radius:MAX(width, height) startAngle:M_PI endAngle:-M_PI_2 clockwise:YES];
[path addLineToPoint:bottomRightPoint];
[path closePath];
self.maskPath = path;
}
break;
case QuarterCircleTypeTopRight:{
CGPoint bottomLeftPoint = CGPointMake(0, height);
CGPoint topLeftPoint = CGPointMake(0, 0);
UIBezierPath *path = [[UIBezierPath alloc]init];
[path moveToPoint:bottomLeftPoint];
[path addLineToPoint:topLeftPoint];
[path addArcWithCenter:bottomLeftPoint radius:MAX(width, height) startAngle:-M_PI endAngle:0 clockwise:YES];
[path addLineToPoint:bottomLeftPoint];
[path closePath];
self.maskPath = path;
}
break;
case QuarterCircleTypeBottomLeft:{
CGPoint topRightPoint = CGPointMake(width, 0);
CGPoint bottomRightPoint = CGPointMake(width, height);
UIBezierPath *path = [[UIBezierPath alloc]init];
[path moveToPoint:topRightPoint];
[path addLineToPoint:bottomRightPoint];
[path addArcWithCenter:topRightPoint radius:MAX(width, height) startAngle:M_PI_2 endAngle:M_PI clockwise:YES];
[path addLineToPoint:topRightPoint];
[path closePath];
self.maskPath = path;
}
break;
case QuarterCircleTypeBottomRight:{
CGPoint topLeftPoint = CGPointMake(0, 0);
CGPoint topRightPoint = CGPointMake(width, 0);
UIBezierPath *path = [[UIBezierPath alloc]init];
[path moveToPoint:topLeftPoint];
[path addLineToPoint:topRightPoint];
[path addArcWithCenter:topLeftPoint radius:MAX(width, height) startAngle:0 endAngle:M_PI_2 clockwise:YES];
[path addLineToPoint:topLeftPoint];
[path closePath];
self.maskPath = path;
}
break;
case QuarterCircleTypeAllCircle:{
CGPoint centerPoint = CGPointMake(width/2, height/2);
UIBezierPath *path = [[UIBezierPath alloc]init];
[path addArcWithCenter:centerPoint radius:MAX(width/2, height/2) startAngle:0 endAngle:M_PI * 2 clockwise:YES];
[path closePath];
self.maskPath = path;
}
break;
default:
break;
}
}
于是,很輕松的我創建了這樣的圖形
但是問題仍然不可忽視的,又來了,在半圓形外面的四個區域仍然可以相應按鈕點擊,
解釋下來也就是mask外面的區域雖然看不到,但是其仍然屬于按鈕的區域,仍然可以響應點擊,
所以我們需要規避
那么如何規避呢?
其畢竟仍然是一個不規則的形狀啊?
其實我們前期已經做好準備了....
3.規避不規則區域外的點擊
我們只要簡單的實現這個點擊是否響應的方法即可
于是完整的不規則按鈕的實現是這樣的
#import <UIKit/UIKit.h>
@interface IrregularButton : UIButton
@property(nonatomic, strong)UIBezierPath *maskPath;
@end
#import "IrregularButton.h"
@implementation IrregularButton
-(void)setMaskPath:(UIBezierPath *)maskPath{
_maskPath = maskPath;
CAShapeLayer *layer = [[CAShapeLayer alloc]init];
layer.path = maskPath.CGPath;
self.layer.mask = layer;
}
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
BOOL res = [super pointInside:point withEvent:event];
if (res) {
if (!self.maskPath || [self.maskPath containsPoint:point]) {
return YES;
}
return NO;
}
return res;
}
@end
我們只要保證點擊的point在我們的mask的區域內即可(這里要保證我們的mask是一個封閉的區域),在mask區域內允許點擊,區域外則不允許點擊,
于是一個maskPath被我們使用了兩次,第一次是設置遮罩顯示不規則按鈕,第二次是判斷點擊時候的響應區域
至此,能夠適應各種自定義形狀的基類按鈕便完成了
當我們想要實現的時候,我們只要根據原型中的不規則形狀繪制我們想要的圖形來創造一個貝塞爾曲線即可(這部分因為形狀不同,我們只能自己繪制)
如果你覺得繪制一個不規則形狀太麻煩的話(偷懶)
paintCode也可以幫你的忙,你只要動動鼠標既可以跟類似ps一樣的繪制一個不規則圖形,而這個工具會自動幫你把貝塞爾曲線的代碼給寫好,你只要復制進去即可
好了demo奉上吧...
https://github.com/spicyShrimp/IrregularButton