9.4 KiB
9.4 KiB
Banner手势优化实施总结
概述
本文档记录了在 RoomAnimationView.m
中对 banner 手势系统的优化实施过程。
最新优化内容(2025年1月)
需求描述
-
bannerContainer 手势范围调整:
- 中央宽度 2/3 的位置:保留 swipe 手势
- 左右两侧各 1/6 宽度:添加 tap 手势
-
tap 手势处理逻辑:
- 检查当前显示的 banner 是否在 tap 位置可以响应事件
- 如果可以响应:不处理,让 banner 继续原有逻辑
- 如果不能响应:保存 tap 位置点,供后续使用
实施方案
1. 手势识别器重新设计
- (void)addBnnerContainGesture {
// 创建独立的手势容器,避免与XPRoomAnimationHitView的hitTest冲突
[self insertSubview:self.bannerSwipeGestureContainer aboveSubview:self.bannerContainer];
[self insertSubview:self.bannerLeftTapGestureContainer aboveSubview:self.bannerContainer];
[self insertSubview:self.bannerRightTapGestureContainer aboveSubview:self.bannerContainer];
// 设置手势容器的布局约束
[self.bannerSwipeGestureContainer mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.bannerContainer);
make.top.bottom.mas_equalTo(self.bannerContainer);
make.width.mas_equalTo(self.bannerContainer.mas_width).multipliedBy(2.0/3.0);
}];
[self.bannerLeftTapGestureContainer mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.leading.bottom.mas_equalTo(self.bannerContainer);
make.trailing.mas_equalTo(self.bannerSwipeGestureContainer.mas_leading);
}];
[self.bannerRightTapGestureContainer mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.trailing.bottom.mas_equalTo(self.bannerContainer);
make.leading.mas_equalTo(self.bannerSwipeGestureContainer.mas_trailing);
}];
// 创建中央区域的 swipe 手势(2/3 宽度)
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self
action:@selector(handleSwipe)];
if (isMSRTL()) {
swipe.direction = UISwipeGestureRecognizerDirectionRight;
} else {
swipe.direction = UISwipeGestureRecognizerDirectionLeft;
}
swipe.delegate = self;
// 创建左侧区域的 tap 手势(1/6 宽度)
UITapGestureRecognizer *leftTap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(handleBannerTap:)];
leftTap.delegate = self;
// 创建右侧区域的 tap 手势(1/6 宽度)
UITapGestureRecognizer *rightTap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(handleBannerTap:)];
rightTap.delegate = self;
// 添加手势识别器到对应的手势容器
[self.bannerSwipeGestureContainer addGestureRecognizer:swipe];
[self.bannerLeftTapGestureContainer addGestureRecognizer:leftTap];
[self.bannerRightTapGestureContainer addGestureRecognizer:rightTap];
}
2. 区域划分逻辑
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
CGPoint touchPoint = [touch locationInView:self.bannerContainer];
CGFloat containerWidth = self.bannerContainer.bounds.size.width;
// 计算区域边界
CGFloat leftBoundary = containerWidth / 6.0; // 1/6 宽度
CGFloat rightBoundary = containerWidth * 5.0 / 6.0; // 5/6 宽度
if ([gestureRecognizer isKindOfClass:[UISwipeGestureRecognizer class]]) {
// Swipe 手势只在中央 2/3 区域生效
return touchPoint.x >= leftBoundary && touchPoint.x <= rightBoundary;
} else if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
// Tap 手势只在左右两侧 1/6 区域生效
return touchPoint.x < leftBoundary || touchPoint.x > rightBoundary;
}
return YES;
}
3. Tap 手势处理逻辑
- (void)handleBannerTap:(UITapGestureRecognizer *)tapGesture {
CGPoint tapPoint = [tapGesture locationInView:self.bannerContainer];
// 检查当前显示的 banner 是否在 tap 位置可以响应事件
if ([self isPointInBannerInteractiveArea:tapPoint]) {
// banner 可以响应,不处理,让 banner 继续原有逻辑
NSLog(@"🎯 Banner tap 位置在可交互区域,banner 将处理此事件");
return;
} else {
// banner 不能响应,保存 tap 位置
self.savedTapPoint = tapPoint;
self.hasSavedTapPoint = YES;
NSLog(@"💾 Banner tap 位置不在可交互区域,已保存位置: %@", NSStringFromCGPoint(tapPoint));
}
}
4. Banner 交互区域检查
- (BOOL)isPointInBannerInteractiveArea:(CGPoint)point {
// 检查当前显示的 banner 是否在指定位置可以响应事件
for (UIView *subview in self.bannerContainer.subviews) {
if (subview.hidden || subview.alpha <= 0.01) {
continue;
}
// 检查点是否在子视图范围内
if (CGRectContainsPoint(subview.bounds, point)) {
// 检查子视图是否支持用户交互
if (subview.userInteractionEnabled) {
// 进一步检查子视图是否有可点击的元素
CGPoint subviewPoint = [subview convertPoint:point fromView:self.bannerContainer];
UIView *hitView = [subview hitTest:subviewPoint withEvent:nil];
if (hitView && hitView.userInteractionEnabled) {
return YES;
}
}
}
}
return NO;
}
5. 公共接口方法
// 获取保存的 tap 位置
- (CGPoint)getSavedTapPoint;
// 检查是否有保存的 tap 位置
- (BOOL)hasSavedTapPointAvailable;
// 清除保存的 tap 位置
- (void)clearSavedTapPoint;
新增属性
// Banner 手势相关属性
@property(nonatomic, assign) CGPoint savedTapPoint;
@property(nonatomic, assign) BOOL hasSavedTapPoint;
// 手势容器(使用普通UIView避免XPRoomAnimationHitView的hitTest冲突)
@property(nonatomic, strong) UIView *bannerSwipeGestureContainer;
@property(nonatomic, strong) UIView *bannerLeftTapGestureContainer;
@property(nonatomic, strong) UIView *bannerRightTapGestureContainer;
协议支持
- 添加了
UIGestureRecognizerDelegate
协议支持 - 实现了手势识别器的 delegate 方法
技术特点
1. 精确的区域控制
- 使用独立的手势容器精确划分区域
- 中央 2/3 区域:swipe 手势容器
- 左右两侧各 1/6 区域:tap 手势容器
2. 避免手势冲突
- 使用普通
UIView
作为手势容器,避免XPRoomAnimationHitView
的hitTest
冲突 - 手势容器独立于 banner 内容,确保手势识别不受干扰
3. 智能的事件处理
- 检查 banner 是否在 tap 位置可响应
- 自动判断是否需要保存 tap 位置
- 避免与 banner 原有交互逻辑冲突
4. 灵活的接口设计
- 提供公共方法获取保存的 tap 位置
- 支持清除保存的位置
- 便于外部代码使用
5. 完善的日志记录
- 详细记录手势处理过程
- 便于调试和问题排查
使用示例
// 检查是否有保存的 tap 位置
if ([roomAnimationView hasSavedTapPointAvailable]) {
CGPoint savedPoint = [roomAnimationView getSavedTapPoint];
NSLog(@"保存的 tap 位置: %@", NSStringFromCGPoint(savedPoint));
// 使用保存的位置进行后续处理
// ...
// 清除保存的位置
[roomAnimationView clearSavedTapPoint];
}
注意事项
- 手势容器设计:使用普通
UIView
作为手势容器,避免XPRoomAnimationHitView
的hitTest
冲突 - 区域划分:通过独立的视图容器精确划分手势区域,确保手势识别不受干扰
- 交互检查:通过
hitTest
方法检查子视图的实际可交互性 - 内存管理:及时清除不需要的 tap 位置数据
- 调试支持:在 DEBUG 模式下为手势容器添加背景色,便于调试区域划分
测试建议
-
区域划分测试:
- 在中央区域测试 swipe 手势
- 在左右两侧测试 tap 手势
- 验证手势在错误区域不触发
-
交互逻辑测试:
- 在有可交互 banner 的区域 tap
- 在无可交互 banner 的区域 tap
- 验证 tap 位置的保存和清除
-
边界条件测试:
- 测试不同屏幕尺寸下的区域划分
- 测试 RTL 语言环境下的手势方向
- 测试多个 banner 同时显示的情况
总结
本次优化成功实现了:
- ✅ bannerContainer 手势范围的精确划分
- ✅ 智能的 tap 手势处理逻辑
- ✅ 灵活的 tap 位置保存机制
- ✅ 完善的公共接口设计
- ✅ 与现有代码的良好兼容性
- ✅ 解决了 XPRoomAnimationHitView 的手势冲突问题
关键改进
- 避免手势冲突:使用普通
UIView
作为手势容器,避免XPRoomAnimationHitView
的hitTest
方法干扰 - 精确区域控制:通过独立的视图容器实现精确的手势区域划分
- 调试友好:在 DEBUG 模式下为手势容器添加背景色,便于调试
该方案既满足了新的功能需求,又解决了潜在的手势冲突问题,保持了代码的可维护性和扩展性。