iOS不规则Button响应点击事件

iOS不规则Button响应点击事件

需求

利用图片,实现点击区域是否响应点击事件,有图片内容的则响应,无内容的不响应。
参考:掘金:iOS不规则button点击一

问题描述:

描述:下载了JTShapedButton源码,查看后,存在的问题:
1.如果把设置图片改为背景图片时,改变按钮大小时,界面显示的图片大小与按钮实际大小不一致(为了适配,在不同机型上按钮的实际大小可能会等比缩放,导致实际显示的大小与图片实际大小不一致),点击图片范围事件错误。不该响应的地方也响应,该响应的地方不响应。
2.图片资源只支持1x的图片,改为2x图片,则响应不对。

解决方法:

在获取该点在图片上所在颜色时,实际的点与图片上的点,要等比例计算,得到在原图上该点的实际像素点

//获取在实际原图上该点的值

size_t pixelsWide = CGImageGetWidth(image.CGImage);
size_t pixelsHigh = CGImageGetHeight(image.CGImage);
CGFloat rateWidth = self.bounds.size.width / pixelsWide;
CGFloat rateHeight = self.bounds.size.height / pixelsHigh;
CGPoint realPoint = CGPointMake(point.x / rateWidth, point.y / rateHeight);`
事件传递
  1. 当用户触摸实际屏幕时,会生成一个Touch Event,将此事件添加到UIApplication管理的事件队列之中。
  2. UIApplication从事件队列之中按顺序取出事件分发到视图去处理。
  3. 当事件被发出以后,会从keywindow开始,一次向上传递,包括COntroller以及view,最后找到合适的视图来响应事件。

可以看出:当一个事件发生后,事件会从父控件传给子控件,也就是说由UIApplication->UIWindow->UIView->initial view ,以上就是时间的传递,也就是寻找最合适的view的过程。

涉及到的方法:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

当UIApplication发送时间到keyWindow时,会调用hitTest来找寻最合适的视图处理事件。判断逻辑如下:

  1. 首先判断自身是否能够响应触摸事件(userInteractionEnabledtrue,hiddentrue,alpha<=0.01不能响应触摸事件),若能响应则下一步,否则返回nil。
  2. 如果可以响应触摸事件,调用pointInside来判断是否在显示区域内,如果不在其中,pointInside返回false,同时hitTest返回nil。
  3. 如果 pointInside返回true,表示在当前的视图之中,然后倒序遍历该视图的子视图,重复上述步骤,直到某一视图可以响应,hitTest:返回该视图。
  4. 如果执行完上述步骤以后,没有符合条件的视图响应事件,则返回视图本身,表示只有当前视图符合条件,能够处理该事件。

Q:为什么倒序遍历?
A:因为在subViews数组中,最后添加的视图,在视图层级中处于最上方。

怎么判断谁来处理当前事件

当知道的上面时间传递机制后,我们就能理清我们的button处理时间逻辑了:

  1. 自定义Button继承自UIButton。
  2. 重写- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 方法,在其中判断是否需要自身处理。
  • 判断点是否在自身button.imageView的frame范围内
  • 得到点击点在button.imageView中该点的颜色值
  • 如果得到的色值中alpha小于阀值,则返回false
自定义按钮代码
//
//  LJButton.m
//  HexagonDemo
//
//  Created by JuneLee on 2019/4/24.
//  Copyright © 2019 JuneLee. All rights reserved.
//

#import "LJButton.h"

/** 不响应的alpha最小值 */
static CGFloat minNotResponseAlpha = 0.1;

@interface LJButton ()

/** 记录上一次点击的点 */
@property (nonatomic, assign) CGPoint previousTouchPoint;
/** 记录上一次点击的点的响应值 */
@property (nonatomic, assign) BOOL previousTouchHitTestResponse;
/** 按钮的图片 */
@property (nonatomic, strong) UIImage *contentImage;
/** 按钮的背景图片 */
@property (nonatomic, strong) UIImage *backgroundImage;

@end

@implementation LJButton

/** 初始化默认值 */
- (void)setDefualtParam {
    _previousTouchPoint = CGPointMake(-9999, -99);
    _contentImage = self.currentImage;
    _backgroundImage = self.currentBackgroundImage;
}

#pragma mark - 重写点击方法
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    BOOL superResult = [super pointInside:point withEvent:event];
    if (!superResult) {
        return superResult;
    }
    
    if (point.x == _previousTouchPoint.x && point.y == _previousTouchPoint.y) {
        return self.previousTouchHitTestResponse;
    }else {
        self.previousTouchPoint = point;
    }
    
    BOOL response = [self shouldResponseAtPoint:point image:_contentImage] || [self shouldResponseAtPoint:point image:_backgroundImage];
    self.previousTouchHitTestResponse = response;
    return response;
}

/** 该点是否可响应 */
- (BOOL)shouldResponseAtPoint:(CGPoint)point image:(UIImage *)image {
    
    UIImageView *contentView = self.imageView;
    if (!image || !contentView) {
        return NO;
    }
    
    CGPoint imagePoint = CGPointMake(point.x - contentView.frame.origin.x, point.y - contentView.frame.origin.y);
    
    UIColor *pixeColor = [self imageColorAtPoint:imagePoint image:image];
    CGFloat alpla = 0.0;
    if (pixeColor) {
        [pixeColor getRed:nil green:nil blue:nil alpha:&alpla];
    }
    return alpla >= minNotResponseAlpha;
}

#pragma mark - 获取该点的颜色值
- (UIColor *)imageColorAtPoint:(CGPoint)point image:(UIImage *)image {
    
    //获取在实际原图上该点的值
    size_t pixelsWide = CGImageGetWidth(image.CGImage);
    size_t pixelsHigh = CGImageGetHeight(image.CGImage);
    CGFloat rateWidth = self.bounds.size.width / pixelsWide;
    CGFloat rateHeight = self.bounds.size.height / pixelsHigh;
    CGPoint realPoint = CGPointMake(point.x / rateWidth, point.y / rateHeight);
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    int bitmapBytesPerRow = (int)(pixelsWide * 4);
    UInt32 bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big;
    
    CGContextRef context = CGBitmapContextCreate(nil,  pixelsWide, pixelsHigh,  8, bitmapBytesPerRow, colorSpace, bitmapInfo);
    unsigned char* data = CGBitmapContextGetData(context);
    
    if (!context || !data || !image.CGImage) {
        return nil;
    }
    
    CGRect rect = CGRectMake(0, 0, pixelsWide, pixelsHigh);
    CGContextDrawImage(context, rect, image.CGImage);
    
    int offset = 4 * ((pixelsWide * round(realPoint.y)) + round(realPoint.x));
    int alpha =  data[offset] / 255.0;
    int red = data[offset + 1] / 255.0;
    int green = data[offset + 2] / 255.0;
    int blue = data[offset + 3] / 255.0;
    return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
}
@end

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值