需求背景
日常开发中UIButton的图片与标题默认的布局是固定的,是在水平方向左右排列。但是我们会经常需要更改image和title的位置来实现需求,这是个很常见的需求就不多说了。所以下面就来谈谈如何一步步的实现一个高度自定义的UIButton控件。
实现思路
默认情况下,在button有固定的宽高值的时候,image和title是以相对左右排列,整体居中于button来显示的,如果没有固定的宽高值,即大小自适应的情况下,整个UIButton将自动缩放到刚好可以容纳image和title的大小。如图所示:
自适应大小.png
了解UIButton的各个属性
在准备自定义之前,我们需要了解UIButton的各个属性都是怎么运用和实现的,因为要修改title和image的位置与这些属性是密不可分的。
因为这些都是基本的开发知识,我就不再过多叙述,分别以图片来展示效果:
UIControlContentVerticalAlignment
UIControlContentVerticalAlignment各种效果.png
UIControlContentHorizontalAlignment
UIControlContentHorizontalAlignment各种效果.png
通过上面两张图可以清楚地看到UIControlContentVerticalAlignment和UIControlContentHorizontalAlignment在不同值下的效果,灰色背景的就是一个button的实际大小,center就是系统默认的值,明显的在两种fill值下,图片都出现了拉伸的情况,而且在水平fill下,图片并没有像垂直情况下水平铺满整个控件,image和title还重叠到了一起去。
UIEdgeInsets
UIButton的另一个重要的属性就是这个了,称之为偏移量,他分别有contentEdgeInsets,imageEdgeInsets,titleEdgeInsets三个相关属性。
默认情况下:
contentEdgeInsets的top、left、bottom、right都是相对于button本身,控制着image和title整体的偏移量;
imageEdgeInsets的top、left、bottom相对于button,right相对于title,控制着image的相对偏移量;
titleEdgeInsets的top、bottom、right相对于button,left相对于image,控制着title的相对偏移量;
我用一张图来解释一下:
偏移量.png
上图的正负值就代表偏移方向
我们想要的效果
写到这里,我们需要考虑一下我们的需求,我们并不是来分析button的实现原理,而是要实现一个自定义的button,自定义image和title的相对位置是最起码的要求。相信看到这里大家也知道我们只需要修改imageEdgeInsets和titleEdgeInsets的值就可以随意布局image和title的相对位置。比如左title右image,上image下title。
要实现这个需求有两种方式:
新建一个UIButton的分类UIButton + xx,在layoutSubviews里修改imageEdgeInsets和titleEdgeInsets的值。这种方式可以简单实现我们的基本需求,但如果想要添加更多的自定义属性还需要通过runtime来实现,好处就是拥有调用系统API的舒爽,直接UIButton调用,没什么代码侵入性。
封装一个继承自UIButton的CustomButton,可以自由添加自定义方法、属性,在layoutSubviews里重置image和title的frame来实现不同的布局方式。
其实这两种方式都可以实现自定义的效果,具体选用哪个就看你自己的需求了,我这里就第二种方式来实现一下。
上面所说到的contentEdgeInsets,imageEdgeInsets,titleEdgeInsets默认值都是zero,但是我们现在假设他们都有一个默认值x;
button简易图示.png
这里实现的思路就是通过self.bounds减去四周的偏移量先获取整个content的实际size,再由contentSize减去image和title的对应偏移量来获取image和title的实际size。总之就是先获取image和title的实际大小,然后根据不同的布局重置image和title的x、y坐标和bound,从而得到对应的frame,就可以实现自由布局了。
上面所说的是button有固定宽高值的情况,如果button的宽高自适应,即调用sizeToFit方法时,我们需要在- (CGSize)sizeThatFits:(CGSize)size内针对不同情况重新计算出button的size,不然的话,系统会根据image和title的大小默认返回它们左右排列的size,此时的size是错误的,如图:
没做适配的结果.png
具体的布局分析思路就是这些了,因为代码太多,就不在这里粘贴详细代码了,如果需要代码的可以在文章底部找到demo的下载链接,demo里面也有详细的注释说明。
但是,到这里我们只是自定义了image和title的相对布局,我们的目的是自定义整个UIButton,所以系统默认的点击效果,CALayer的所有默认动画都需要移除掉,替换成我们自定义的layer效果。比如说系统button的默认高亮状态下图片颜色也会加深,这个其实很恶心,所以我们应该移除掉,就像图下所示:
系统button高亮状态.png
ok,现在我们来整理一下需要的常用属性,分别为normal、highlighted、disabled这几种状态下的背景色,透明度变化,图片的tintColor,边框线的颜色,我们就针对这几个点进行修改。
下面粘贴几块代码段大概展示一下:
highlighted逻辑
- (void)setHighlighted:(BOOL)highlighted { [super setHighlighted:highlighted]; if (highlighted && !self.originBorderColor) { // 手指按在按钮上会不断触发setHighlighted:,所以这里做了保护,设置过一次就不用再设置了 self.originBorderColor = [UIColor colorWithCGColor:self.layer.borderColor]; } // 渲染背景色 if (self.highlightedBackgroundColor || self.highlightedBorderColor) { [self adjustsButtonHighlighted]; } // 如果此时是disabled,则disabled的样式优先 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; }]; } }
移除系统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; } }
因为需要大量的自定义属性来代替系统默认属性,虽然我很想在这里解释每个属性的用处,但是太麻烦了,所以还是建议直接下载demo,配合代码看文章,代码有详细的注释
这里就直接展示一下demo的效果图:
FSCustomButtonDemo.gif
以前项目用到的时候,我也是直接网上找的一个库,不过那个库包含内容太多,很多都没用,所以我将其中的部分代码抽离了出来直接在项目中运用,效果还可以很稳定,所以最近抽时间将代码从项目中抽离封装了一下,写了一个demo上传在github,需要的可以直接前往下载:
文章和demo中涉及到的知识点:
如果对你有所帮助,就点个赞吧
作者:PURE蓝胖子
链接:http://www.jianshu.com/p/4603e9bbba56