需求背景
日常開發(fā)中UIButton
的圖片與標(biāo)題默認(rèn)的布局是固定的铝宵,是在水平方向左右排列捉超。但是我們會(huì)經(jīng)常需要更改image
和title
的位置來實(shí)現(xiàn)需求,這是個(gè)很常見的需求就不多說了况芒。所以下面就來談?wù)勅绾我徊讲降膶?shí)現(xiàn)一個(gè)高度自定義的UIButton
控件绝骚。
實(shí)現(xiàn)思路
默認(rèn)情況下祠够,在button
有固定的寬高值的時(shí)候古瓤,image
和title
是以相對(duì)左右排列落君,整體居中于button
來顯示的,如果沒有固定的寬高值皮获,即大小自適應(yīng)的情況下洒宝,整個(gè)UIButton
將自動(dòng)縮放到剛好可以容納image
和title
的大小。如圖所示:
了解UIButton的各個(gè)屬性
在準(zhǔn)備自定義之前宏浩,我們需要了解UIButton
的各個(gè)屬性都是怎么運(yùn)用和實(shí)現(xiàn)的绘闷,因?yàn)橐薷?code>title和image
的位置與這些屬性是密不可分的印蔗。
因?yàn)檫@些都是基本的開發(fā)知識(shí)华嘹,我就不再過多敘述耙厚,分別以圖片來展示效果:
UIControlContentVerticalAlignment
UIControlContentHorizontalAlignment
通過上面兩張圖可以清楚地看到UIControlContentVerticalAlignment
和UIControlContentHorizontalAlignment
在不同值下的效果薛躬,灰色背景的就是一個(gè)button
的實(shí)際大小型宝,center
就是系統(tǒng)默認(rèn)的值趴酣,明顯的在兩種fill
值下岖寞,圖片都出現(xiàn)了拉伸的情況柜蜈,而且在水平fill
下淑履,圖片并沒有像垂直情況下水平鋪滿整個(gè)控件,image
和title
還重疊到了一起去岁疼。
UIEdgeInsets
UIButton
的另一個(gè)重要的屬性就是這個(gè)了捷绒,稱之為偏移量暖侨,他分別有contentEdgeInsets
,imageEdgeInsets
京郑,titleEdgeInsets
三個(gè)相關(guān)屬性些举。
默認(rèn)情況下:
-
contentEdgeInsets
的top
户魏、left
叼丑、bottom
扛门、right
都是相對(duì)于button
本身论寨,控制著image
和title
整體的偏移量; -
imageEdgeInsets
的top
、left
沮明、bottom
相對(duì)于button
窍奋,right
相對(duì)于title
琳袄,控制著image
的相對(duì)偏移量; -
titleEdgeInsets
的top
址否、bottom
碎紊、right
相對(duì)于button
樊诺,left
相對(duì)于image
词爬,控制著title
的相對(duì)偏移量权均;
我用一張圖來解釋一下:
上圖的正負(fù)值就代表偏移方向
我們想要的效果
寫到這里恋沃,我們需要考慮一下我們的需求蛇尚,我們并不是來分析button
的實(shí)現(xiàn)原理取劫,而是要實(shí)現(xiàn)一個(gè)自定義的button
,自定義image
和title
的相對(duì)位置是最起碼的要求炮捧。相信看到這里大家也知道我們只需要修改imageEdgeInsets
和titleEdgeInsets
的值就可以隨意布局image
和title
的相對(duì)位置咆课。比如左title
右image
书蚪,上image
下title
迅栅。
要實(shí)現(xiàn)這個(gè)需求有兩種方式:
- 新建一個(gè)
UIButton
的分類UIButton + xx
读存,在layoutSubviews
里修改imageEdgeInsets
和titleEdgeInsets
的值让簿。這種方式可以簡單實(shí)現(xiàn)我們的基本需求,但如果想要添加更多的自定義屬性還需要通過runtime
來實(shí)現(xiàn)莲祸,好處就是擁有調(diào)用系統(tǒng)API
的舒爽虫给,直接UIButton
調(diào)用抹估,沒什么代碼侵入性。 - 封裝一個(gè)繼承自
UIButton
的CustomButton
瓷式,可以自由添加自定義方法贸典、屬性廊驼,在layoutSubviews
里重置image
和title
的frame
來實(shí)現(xiàn)不同的布局方式惋砂。
其實(shí)這兩種方式都可以實(shí)現(xiàn)自定義的效果西饵,具體選用哪個(gè)就看你自己的需求了,我這里就第二種方式來實(shí)現(xiàn)一下期虾。
上面所說到的contentEdgeInsets
镶苞,imageEdgeInsets
茂蚓,titleEdgeInsets
默認(rèn)值都是zero
谢澈,但是我們現(xiàn)在假設(shè)他們都有一個(gè)默認(rèn)值x
锥忿;
這里實(shí)現(xiàn)的思路就是通過self.bounds
減去四周的偏移量先獲取整個(gè)content
的實(shí)際size
敬鬓,再由contentSize
減去image
和title
的對(duì)應(yīng)偏移量來獲取image
和title
的實(shí)際size
钉答。總之就是先獲取image
和title
的實(shí)際大小仑性,然后根據(jù)不同的布局重置image
和title
的x
诊杆、y
坐標(biāo)和bound
晨汹,從而得到對(duì)應(yīng)的frame
贷盲,就可以實(shí)現(xiàn)自由布局了巩剖。
上面所說的是button
有固定寬高值的情況,如果button
的寬高自適應(yīng)氧骤,即調(diào)用sizeToFit
方法時(shí)筹陵,我們需要在- (CGSize)sizeThatFits:(CGSize)size
內(nèi)針對(duì)不同情況重新計(jì)算出button
的size
朦佩,不然的話庐氮,系統(tǒng)會(huì)根據(jù)image
和title
的大小默認(rèn)返回它們左右排列的size
弄砍,此時(shí)的size
是錯(cuò)誤的,如圖:
具體的布局分析思路就是這些了慨畸,因?yàn)榇a太多寸士,就不在這里粘貼詳細(xì)代碼了弱卡,如果需要代碼的可以在文章底部找到demo的下載鏈接,demo里面也有詳細(xì)的注釋說明瓮具。
但是搭综,到這里我們只是自定義了image
和title
的相對(duì)布局划栓,我們的目的是自定義整個(gè)UIButton
忠荞,所以系統(tǒng)默認(rèn)的點(diǎn)擊效果,CALayer
的所有默認(rèn)動(dòng)畫都需要移除掉堂油,替換成我們自定義的layer
效果碧绞。比如說系統(tǒng)button
的默認(rèn)高亮狀態(tài)下圖片顏色也會(huì)加深讥邻,這個(gè)其實(shí)很惡心,所以我們應(yīng)該移除掉系宜,就像圖下所示:
ok盹牧,現(xiàn)在我們來整理一下需要的常用屬性汰寓,分別為normal
苹粟、highlighted
、disabled
這幾種狀態(tài)下的背景色俺孙,透明度變化睛榄,圖片的tintColor
想帅,邊框線的顏色,我們就針對(duì)這幾個(gè)點(diǎn)進(jìn)行修改旨剥。
下面粘貼幾塊代碼段大概展示一下:
highlighted邏輯
- (void)setHighlighted:(BOOL)highlighted {
[super setHighlighted:highlighted];
if (highlighted && !self.originBorderColor) {
// 手指按在按鈕上會(huì)不斷觸發(fā)setHighlighted:轨帜,所以這里做了保護(hù)蚌父,設(shè)置過一次就不用再設(shè)置了
self.originBorderColor = [UIColor colorWithCGColor:self.layer.borderColor];
}
// 渲染背景色
if (self.highlightedBackgroundColor || self.highlightedBorderColor) {
[self adjustsButtonHighlighted];
}
// 如果此時(shí)是disabled毛萌,則disabled的樣式優(yōu)先
if (!self.enabled) {
return;
}
// 自定義highlighted樣式
if (self.adjustsButtonWhenHighlighted) {
if (highlighted) {
self.alpha = 0.5f;
} else {
[UIView animateWithDuration:0.25f animations:^{
self.alpha = 1;
}];
}
}
}
enabled邏輯
- (void)setEnabled:(BOOL)enabled {
[super setEnabled:enabled];
if (!enabled && self.adjustsButtonWhenDisabled) {
self.alpha = 0.5f;
} else {
[UIView animateWithDuration:0.25f animations:^{
self.alpha = 1;
}];
}
}
移除系統(tǒng)layer阁将,添加自定義layer
- (void)adjustsButtonHighlighted {
if (self.highlightedBackgroundColor) {
if (!self.highlightedBackgroundLayer) {
self.highlightedBackgroundLayer = [CALayer layer];
[self.highlightedBackgroundLayer FS_removeDefaultAnimations];
[self.layer insertSublayer:self.highlightedBackgroundLayer atIndex:0];
}
self.highlightedBackgroundLayer.frame = self.bounds;
self.highlightedBackgroundLayer.cornerRadius = self.layer.cornerRadius;
self.highlightedBackgroundLayer.backgroundColor = self.highlighted ? self.highlightedBackgroundColor.CGColor : [UIColor colorWithRed:1 green:1 blue:1 alpha:0].CGColor;
}
if (self.highlightedBorderColor) {
self.layer.borderColor = self.highlighted ? self.highlightedBorderColor.CGColor : self.originBorderColor.CGColor;
}
}
因?yàn)樾枰罅康淖远x屬性來代替系統(tǒng)默認(rèn)屬性缤削,雖然我很想在這里解釋每個(gè)屬性的用處吹榴,但是太麻煩了,所以還是建議直接下載demo吨拗,配合代碼看文章劝篷,代碼有詳細(xì)的注釋
這里就直接展示一下demo的效果圖:
以前項(xiàng)目用到的時(shí)候娇妓,我也是直接網(wǎng)上找的一個(gè)庫活鹰,不過那個(gè)庫包含內(nèi)容太多,很多都沒用着绷,所以我將其中的部分代碼抽離了出來直接在項(xiàng)目中運(yùn)用荠医,效果還可以很穩(wěn)定,所以最近抽時(shí)間將代碼從項(xiàng)目中抽離封裝了一下兼贡,寫了一個(gè)demo上傳在github遍希,需要的可以直接前往下載:
FSCustomButtonDemo
文章和demo中涉及到的知識(shí)點(diǎn):
有條線叫“一個(gè)像素”
CALayer
關(guān)于UIButton的UIEdgeInsets屬性
如果對(duì)你有所幫助里烦,就點(diǎn)個(gè)喜歡吧