废话不多说,首先看看下面实现的效果图。请参照源代码和文章一块看,文章写得有些乱。附上源代码下载链接地址:https://github.com/ZhengYaWei1992/ZWPopView
说明一点,效果图中所弹出的绿色的边框(包括小三角形)不是通过图片处理,而是绘制出来的,具体的背景颜色和边框宽度都可随意调节。另外小三角形的位置支持三种显示模式:左上角、中间顶部、右上角显示,外部通过设置枚举值即可简单调用。第一个效果图中的popView中不仅仅只能显示文字,还能显示图片加文字,因为内部是一个tableView,具体内容可以通过自定义tableViewCell自行调整。第二个效果图中popView中的感叹号按钮是一个自定义控件,外部调用时可以自定义里面的内容,然后添加点击事件即可。
首先先看一下使用popView外部的简单调用方法。rightBarButtonItem监听事件中的方法:
- (void)optionClick:(UIButton *)sender{
NSArray *menus = @[@"发起群聊", @"添加朋友",@"扫一扫",@"收付款"];
//这里的44是tableView默认的行高
ZWCustomPopView *pView = [[ZWCustomPopView alloc]initWithBounds:CGRectMake(0, 0, 120, 44 * menus.count) titleMenus:menus maskAlpha:0.0];
pView.delegate = self;
//可以用来调节边界线的颜色
pView.containerBackgroudColor = RGBCOLOR(0, 100, 14);
//设置popView在哪个控件下面显示,以及小三角显示的位置
[pView showFrom:sender alignStyle:CPAlignStyleRight];
}
下面这是代理方法,负责监听点击了哪一个cell
- (void)popOverView:(ZWCustomPopView *)pView didClickMenuIndex:(NSInteger)index
{
NSArray *menus = @[@"发起群聊", @"添加朋友",@"扫一扫",@"收付款"];
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:[NSString stringWithFormat:@"你点击了: %@", menus[index]] message:nil delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil];
[alert show];
}
使用起来还是很简单的,接下来简单看看具体是实现思路。
首先创建一个继承于UIView的ZWCustomPopView类,这个类主要是负责整个视图的显示,将该view放在window上。然后将第带有小三角形layer层对应的UIView实例子对象放置在这个view上。这个view类主要是充当蒙板作用,借助UIWindow可以实现坐标的转换,在特定按钮下弹出这个绿色的layer层。最后layer层上在放置相应的控件,如上面效果图中的tableView。
在ZWCustomPopView这个类的.m文件中,添加一个继承于UIView的ZWCustomPopContainerView类扩展,这个类主要是负责带有小三角形的绿色的layer层的显示。提示:这种写法叫做类扩展,用于构建不公开的属性和方法。写在.h里面,谁都知道谁都可以改,写在.m里面只要自己知道,别人看不到也拿不到。这里我们不需要对外可见,写在.m文件中即可。
接着,在ZWCustomPopContainerView这个类扩展中initWithFrame:方法中添加,写上如下代码:[self addObserver:self forKeyPath:@"frame" options:0 context:NULL];主要是通过KVO监听这个类扩展的frame属性值。只要ZWCustomPopContainerView的实例对象的frame值发生变化,就会调用[self setLayerFrame:newFrame];方法。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change: (NSDictionary*)change context:(void *)context{
if([keyPath isEqualToString:@"frame"]) {
CGRect newFrame = CGRectNull;
if([object valueForKeyPath:keyPath] != [NSNull null]) {
newFrame = [[object valueForKeyPath:keyPath] CGRectValue];
//这个方法是实现绘制的绿色背景的主要方法
[self setLayerFrame:newFrame];
}
}
}
绘制这个绿色layer层的思路如下:
绘制这个layer首先要获取关键点的坐标,然后根据关键点的绘制路径,最后填充。仔细观察上面的效果图,不仅仅有小三角形,矩形的四周还有圆角设置。绘制这个layer层路径的关键点是从小三角形定点开始,从左到右逆时针围绕layer层外边的七个点,除了这起个点之外,还有矩形四个顶角圆弧所在的中心圆的中心点和圆弧所圆的半径。另外,我们还要知道三角形的高和底边长度。接下来看一下layer层的实现代码,先定义三个宏分别为三角形的默认高度和宽度,以及圆弧半径。
#define kTriangleHeight 8.0
#define kTriangleWidth 10.0
#define kPopOverLayerCornerRadius 5.0
再看一下[self setLayerFrame:newFrame];方法的主要实现。
//只要有三角形顶点坐标,再加上自己的宽和高,便可确定位置,所以小三角的顶点坐标设置为属性
- (void)setLayerFrame:(CGRect)frame
{
float apexOfTriangelX;
if (_apexOftriangelX == 0) {
apexOfTriangelX = frame.size.width - 60;
}else
{
apexOfTriangelX = _apexOftriangelX;
}
//从三角形的顶点开始画线,刚好是7个点,即0-6
if (apexOfTriangelX > frame.size.width - kPopOverLayerCornerRadius) {
apexOfTriangelX = frame.size.width - kPopOverLayerCornerRadius - 0.5 * kTriangleWidth;
}else if (apexOfTriangelX < kPopOverLayerCornerRadius) {
apexOfTriangelX = kPopOverLayerCornerRadius + 0.5 * kTriangleWidth;
}
//这里是从三角形的顶点到三角形的坐标的点开始画线
CGPoint point0 = CGPointMake(apexOfTriangelX, 0);
CGPoint point1 = CGPointMake(apexOfTriangelX - 0.5 * kTriangleWidth, kTriangleHeight);
CGPoint point2 = CGPointMake(kPopOverLayerCornerRadius, kTriangleHeight);
//圆弧所在元对应的圆心坐标
CGPoint point2_center = CGPointMake(kPopOverLayerCornerRadius, kTriangleHeight + kPopOverLayerCornerRadius);
CGPoint point3 = CGPointMake(0, frame.size.height - kPopOverLayerCornerRadius);
//圆弧所在元对应的圆心坐标
CGPoint point3_center = CGPointMake(kPopOverLayerCornerRadius, frame.size.height - kPopOverLayerCornerRadius);
CGPoint point4 = CGPointMake(frame.size.width - kPopOverLayerCornerRadius, frame.size.height);
//圆弧所在元对应的圆心坐标
CGPoint point4_center = CGPointMake(frame.size.width - kPopOverLayerCornerRadius, frame.size.height - kPopOverLayerCornerRadius);
CGPoint point5 = CGPointMake(frame.size.width, kTriangleHeight + kPopOverLayerCornerRadius);
//圆弧所在元对应的圆心坐标
CGPoint point5_center = CGPointMake(frame.size.width - kPopOverLayerCornerRadius, kTriangleHeight + kPopOverLayerCornerRadius);
CGPoint point6 = CGPointMake(apexOfTriangelX + 0.5 * kTriangleWidth, kTriangleHeight);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:point0];
[path addLineToPoint:point1];
[path addLineToPoint:point2];
[path addArcWithCenter:point2_center radius:kPopOverLayerCornerRadius startAngle:3*M_PI_2 endAngle:M_PI clockwise:NO];
[path addLineToPoint:point3];
[path addArcWithCenter:point3_center radius:kPopOverLayerCornerRadius startAngle:M_PI endAngle:M_PI_2 clockwise:NO];
[path addLineToPoint:point4];
[path addArcWithCenter:point4_center radius:kPopOverLayerCornerRadius startAngle:M_PI_2 endAngle:0 clockwise:NO];
[path addLineToPoint:point5];
[path addArcWithCenter:point5_center radius:kPopOverLayerCornerRadius startAngle:0 endAngle:3*M_PI_2 clockwise:NO];
[path addLineToPoint:point6];
[path closePath];
self.popLayer.path = path.CGPath;
//如果设置_layerColor就显示_layerColor的颜色,否者默认为greenColor
self.popLayer.fillColor = _layerColor? _layerColor.CGColor : [UIColor greenColor].CGColor;
}
接着还要在这个类别中要重写一个很重要系统的方法:- (void)didMoveToSuperview。
- (void)didMoveToSuperview{
[super didMoveToSuperview];
// animations support
//设置刚出来时的动画效果显示1.2倍,然后变回原来大小
self.transform = CGAffineTransformMakeScale(1.2,1.2);
self.alpha = 0;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.2];
self.transform = CGAffineTransformMakeScale(1.0,1.0);
self.alpha = 1;
[UIView commitAnimations];
}
另外还要写类扩展ZWCustomPopContainerView两个属性的set方法,apexOftriangelX和layerColor属性,前者是小三角形的顶点坐标,后者是layer层的颜色属性。在设置小三角的顶点属性以及layer层的属性时,调用上面的[self setLayerFrame:self.frame];方法。主要目的是为了当外部调用- (void)showFrom:(UIView *)from alignStyle:(CPAlignStyle)style(对外提供的接口)方法时,设置小三角形的不同位置,重新调用[self setLayerFrame:self.frame];方法。
- (void)setApexOftriangelX:(CGFloat)apexOftriangelX
{
_apexOftriangelX = apexOftriangelX;
[self setLayerFrame:self.frame];
}
- (void)setLayerColor:(UIColor *)layerColor
{
_layerColor = layerColor;
[self setLayerFrame:self.frame];
}
至于ZWCustomPopView这个类中具体实现的方法在,就不在详细说明了。里面都是一些很常规的布局,当外部调用- (instancetype)initWithBounds:(CGRect)bounds titleMenus:(NSArray *)titles maskAlpha:(CGFloat)alpha;方法时,就将popView内部视图设置为tableView;当调用+ (instancetype)popOverView;方法时,就不设置popView内部视图,完全是使用者自定义。具体如何使用以及实现逻辑,可以参照这篇文章和源代码一块学习。源代码链接见上方。