Files
peko-ios/Banner手势优化实施总结.md

9.4 KiB
Raw Blame History

Banner手势优化实施总结

概述

本文档记录了在 RoomAnimationView.m 中对 banner 手势系统的优化实施过程。

最新优化内容2025年1月

需求描述

  1. bannerContainer 手势范围调整

    • 中央宽度 2/3 的位置:保留 swipe 手势
    • 左右两侧各 1/6 宽度:添加 tap 手势
  2. 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 作为手势容器,避免 XPRoomAnimationHitViewhitTest 冲突
  • 手势容器独立于 banner 内容,确保手势识别不受干扰

3. 智能的事件处理

  • 检查 banner 是否在 tap 位置可响应
  • 自动判断是否需要保存 tap 位置
  • 避免与 banner 原有交互逻辑冲突

4. 灵活的接口设计

  • 提供公共方法获取保存的 tap 位置
  • 支持清除保存的位置
  • 便于外部代码使用

5. 完善的日志记录

  • 详细记录手势处理过程
  • 便于调试和问题排查

使用示例

// 检查是否有保存的 tap 位置
if ([roomAnimationView hasSavedTapPointAvailable]) {
    CGPoint savedPoint = [roomAnimationView getSavedTapPoint];
    NSLog(@"保存的 tap 位置: %@", NSStringFromCGPoint(savedPoint));
    
    // 使用保存的位置进行后续处理
    // ...
    
    // 清除保存的位置
    [roomAnimationView clearSavedTapPoint];
}

注意事项

  1. 手势容器设计:使用普通 UIView 作为手势容器,避免 XPRoomAnimationHitViewhitTest 冲突
  2. 区域划分:通过独立的视图容器精确划分手势区域,确保手势识别不受干扰
  3. 交互检查:通过 hitTest 方法检查子视图的实际可交互性
  4. 内存管理:及时清除不需要的 tap 位置数据
  5. 调试支持:在 DEBUG 模式下为手势容器添加背景色,便于调试区域划分

测试建议

  1. 区域划分测试

    • 在中央区域测试 swipe 手势
    • 在左右两侧测试 tap 手势
    • 验证手势在错误区域不触发
  2. 交互逻辑测试

    • 在有可交互 banner 的区域 tap
    • 在无可交互 banner 的区域 tap
    • 验证 tap 位置的保存和清除
  3. 边界条件测试

    • 测试不同屏幕尺寸下的区域划分
    • 测试 RTL 语言环境下的手势方向
    • 测试多个 banner 同时显示的情况

总结

本次优化成功实现了:

  • bannerContainer 手势范围的精确划分
  • 智能的 tap 手势处理逻辑
  • 灵活的 tap 位置保存机制
  • 完善的公共接口设计
  • 与现有代码的良好兼容性
  • 解决了 XPRoomAnimationHitView 的手势冲突问题

关键改进

  1. 避免手势冲突:使用普通 UIView 作为手势容器,避免 XPRoomAnimationHitViewhitTest 方法干扰
  2. 精确区域控制:通过独立的视图容器实现精确的手势区域划分
  3. 调试友好:在 DEBUG 模式下为手势容器添加背景色,便于调试

该方案既满足了新的功能需求,又解决了潜在的手势冲突问题,保持了代码的可维护性和扩展性。