FSA全栈行动 FSA全栈行动
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr

公众号:FSA全栈行动

记录学习过程中的知识
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr
  • AndroidUI

  • Android第三方SDK

  • Android混淆

  • Android仓库

  • Android新闻

  • Android系统开发

  • Android源码

  • Android注解AOP

  • Android脚本

  • AndroidTv开发

  • AndroidNDK

  • Android音视频

  • Android热修复

  • Android性能优化

  • Android云游戏

  • Android插件化

  • iOSUI

  • iOS工具

  • iOS底层原理与应用

  • iOS组件化

  • iOS音视频

  • iOS疑难杂症

  • iOS之Swift

  • iOS之RxSwift

  • iOS开源项目

  • iOS逆向

  • Flutter开发

    • Dart - 抽象类的实例化
    • Flutter - 打印好用的Debug日志
    • Flutter - 混合开发
    • Flutter - 解决混合开发iOS脚本打包遇到的问题
    • Flutter - 低版本在iOS14上遇到的问题与解决方案
    • Flutter - 解决原生弹窗的触摸事件被Flutter响应的问题
      • 一、问题
      • 二、源码
        • 1、定位匹配的源码
        • 2、定位造成问题的源码
      • 三、解决问题
      • 四、使用步骤
    • Flutter - 实现列表上下拉切换header
    • Flutter - 获取ListView当前正在显示的Widget信息
    • Flutter - 列表滚动定位超强辅助库,墙裂推荐!🔥
    • Flutter - 快速实现聊天会话列表的效果,完美💯
    • Flutter - 聊天输入框更新文本时的必备优化点🔖
    • Flutter - 我给官方提PR,解决run命令卡住问题 😃
    • Flutter - 探索run命令到底做了什么 🤔
    • Flutter - 引擎调试(iOS篇)🛠
    • Flutter - 引擎调试bug到提交PR实战 🐞
    • Flutter - 船新升级😱支持观察第三方构建的滚动视图💪
    • Flutter - 瀑布流交替播放视频 🎞
    • Flutter - IM保持消息位置大升级(支持ChatGPT生成式消息) 🤖
    • Flutter - 滚动视图中的表单防遮挡 🗒
    • Flutter - 秒杀1/2曝光统计 📊
    • 一天内加入 Flutter 和 FlutterCandies 两大组织是什么体验 🧐
    • Flutter - 如何快速搓一个微信通讯录列表(azlist) 📓
    • Flutter - 混编项目集成Shorebird热更新🐦(安卓篇)
    • Flutter - 混编项目集成Shorebird热更新🐦(iOS篇)
    • Flutter - 解决返回原生页面时dispose方法未被触发的问题 🐞
    • Flutter - 升级3.19之后页面多次rebuild?🤨
    • Flutter - 热更新 Shorebird 1.0 正式版来了 🐦
    • Flutter - 使用Pigeon实现视频缓存插件 🐌
    • Flutter - 轻松搞定屏幕旋转功能 😎
    • Flutter - 解决Connection closed before full header was received
    • Flutter - 实现聊天键盘与功能面板的丝滑切换 🍻
    • Flutter - 支持观察NestedScrollView,兼容性更强 😈
    • Flutter - 聊天键盘与面板丝滑切换的强势升级 🍻
    • Flutter - 升级到3.24后页面还会多次rebuild吗?🧐
    • Flutter - 轻松实现PageView卡片偏移效果
    • Flutter - 轻松搞定炫酷视差(Parallax)效果
    • Flutter - 危!3.24版本苹果审核被拒!
    • Flutter - 子部件任意位置观察滚动数据
    • Flutter - iOS编译加速
    • Flutter - Xcode16 还原编译速度
  • 移动端
  • Flutter开发
LinXunFeng
2022-04-03
目录

Flutter - 解决原生弹窗的触摸事件被Flutter响应的问题

欢迎关注微信公众号:[FSA全栈行动 👋]

# 一、问题

我先跳转至 Flutter 页面,1秒后在 Flutter 页面上添加一个原生的弹窗视图,代码如下:

let flutterVc = FlutterViewController(engine: fetchFlutterEngine(), nibName: nil, bundle: nil)
self.navigationController?.pushViewController(flutterVc, animated: true)

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(1)) { [self] in
    
    let popView = LXFPopView(frame: CGRect(x: 0, y: 0, width: screenW, height: screenH))
    flutterVc.view.addSubview(popView)
    popView.checkInfoBlock = { [weak self] in
        guard let self = self else { return }
        self.navigationController?.pushViewController(InfoViewController(), animated: true)
    }
    
}

可以看到,我在原生弹窗视图上滑动和点击,会被底下的 Flutter 内容所响应~

有人会说,直接添加到 navigationController 的 view 上不就行了吗?

// flutterVc.view.addSubview(popView)
self.navigationController?.view.addSubview(popView)

不行,因为跳转到其它页面后会遮挡其它页面内容,看效果图便一目了然

接下来我们一起来看看 FlutterViewController 源码,便可知道原因了

# 二、源码

# 1、定位匹配的源码

首先找到与当前 Flutter 环境相匹配的源码内容

➜  flutter doctor -v
[✓] Flutter (Channel stable, 2.10.4, on macOS 12.2.1 21D62 darwin-x64, locale
    zh-Hans-CN)
    • Flutter version 2.10.4 at /Users/lxf/developer/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision c860cba910 (9 days ago), 2022-03-25 00:23:12 -0500
    • Engine revision 57d3bac3dd
    • Dart version 2.16.2
    • DevTools version 2.9.2
    • Pub download mirror https://pub.flutter-io.cn
    • Flutter download mirror https://storage.flutter-io.cn
...

从此处可以拿到 Engine 的 commit id

Engine revision 57d3bac3dd

将下方链接中的 【commit id】 进行替换即可得到相匹配的源码链接了

https://github.com/flutter/engine/blob/【commit id】/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm

链接: engine/FlutterViewController.mm at 57d3bac3dd (opens new window)

# 2、定位造成问题的源码

经过源码的查看,可以很快定位到如下部分的内容:

... 

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
  [self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
}

- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
  [self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
}

- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
  [self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
}

- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
  [self dispatchTouches:touches pointerDataChangeOverride:nullptr event:event];
}

- (void)forceTouchesCancelled:(NSSet*)touches {
  flutter::PointerData::Change cancel = flutter::PointerData::Change::kCancel;
  [self dispatchTouches:touches pointerDataChangeOverride:&cancel event:nullptr];
}

...

// Dispatches the UITouches to the engine. Usually, the type of change of the touch is determined
// from the UITouch's phase. However, FlutterAppDelegate fakes touches to ensure that touch events
// in the status bar area are available to framework code. The change type (optional) of the faked
// touch is specified in the second argument.
- (void)dispatchTouches:(NSSet*)touches
    pointerDataChangeOverride:(flutter::PointerData::Change*)overridden_change
                        event:(UIEvent*)event {
...
}

可以看到,在 FlutterViewController 内部,重写了 touchesXXX 系列的方法,然后统一调用 - dispatchTouches:pointerDataChangeOverride:event: 方法,将 UITouches 分发至 Flutter 引擎,从而与 Flutter 内容进行交互

这便是我们在原生弹窗上的点击、拖拽操作会被 Flutter 内容所响应的原因。

# 三、解决问题

既然我们已经知道原因所在,现在就好想办法去解决这个问题了

这里我直接给出最终实现代码:

// LXFFlutterViewController.swift

import Foundation
import Flutter

protocol LXFFlutterForbidResponseProtocol { }

class LXFFlutterViewController: FlutterViewController {
    
    var isForbidResponseForFlutter = false
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        if self.isForbidResponse() {
            self.isForbidResponseForFlutter = true
            return
        }
        print("touches began")
        super.touchesBegan(touches, with: event)
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        if isForbidResponseForFlutter { return }
        print("touches move")
        super.touchesMoved(touches, with: event)
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        if isForbidResponseForFlutter {
            self.isForbidResponseForFlutter = false
            return
        }
        print("touches ended")
        super.touchesEnded(touches, with: event)
    }
    
    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        if self.isForbidResponse() {
            self.isForbidResponseForFlutter = false
            return
        }
        print("touches cacelled")
        super.touchesCancelled(touches, with: event)
    }
}

extension LXFFlutterViewController {
    func isForbidResponse() -> Bool {
        var subViews = self.view?.subviews ?? []
        subViews = subViews.reversed()
        
        for i in 0..<subViews.count {
            let subView = subViews[i]
            if self.isHadForbidResponseView(view: subView) {
                return true
            }
        }
        return false
    }
    
    fileprivate func isHadForbidResponseView(view: UIView) -> Bool {
        if view is LXFFlutterForbidResponseProtocol {
            return true
        }
        let subViews = view.subviews
        for i in 0..<subViews.count {
            let subView = subViews[i]
            if self.isHadForbidResponseView(view: subView) {
                return true
            }
        }
        return false
    }
}

上图可以看到,在 FlutterView 中,LXFPopView 这一类的弹窗视图一般都会在使用时才会插入到视图中,所以在 isForbidResponse 方法里进行反转遍历子视图,以减少遍历次数。

touchesMoved 调用次数较多,所以为了避免在 touchesMoved 中去高频率的遍历 subViews,这里使用了 isForbidResponseForFlutter 变量,在 touchesBegan 时判断并记录是否需要禁用 Flutter 内容响应触摸事件,在 touchesEnded 和 touchesCancelled 中对 isForbidResponseForFlutter 重置为 false

# 四、使用步骤

步骤一:

使用 LXFFlutterViewController

let flutterVc = LXFFlutterViewController(engine: fetchFlutterEngine(), nibName: nil, bundle: nil)

步骤二:

令弹窗视图所在类遵守协议:LXFFlutterForbidResponseProtocol

extension LXFPopView: LXFFlutterForbidResponseProtocol { }

看看效果如何:

完美 😃

最后附上 Demo 链接:LinXunFeng/flutter_hybrid_touch_response_demo (github.com) (opens new window)

#Dart#Flutter
Flutter - 低版本在iOS14上遇到的问题与解决方案
Flutter - 实现列表上下拉切换header

← Flutter - 低版本在iOS14上遇到的问题与解决方案 Flutter - 实现列表上下拉切换header→

最近更新
01
Flutter - Xcode16 还原编译速度
04-05
02
AI - 免费的 Cursor 平替方案
03-30
03
Android - 2025年安卓真的闭源了吗
03-28
更多文章>
Theme by Vdoing | Copyright © 2020-2025 FSA全栈行动
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×