0%

Flutter的onTap没有反应

很简单的场景下想添加一个点击事件,但是没有反应,onTap 没有执行,但是如果把 onTap 换成 doubleTap 就可以了,这是为什么呢?

gpt 给出的分析

如果你使用 GestureDetector 的 onTap 事件没有反应,这可能是由于以下原因:

  1. 没有子组件:GestureDetector 必须有一个非空子组件。请确保你为 GestureDetector 提供了一个子组件,例如 Text、Container 等。

    GestureDetector(
    onTap: () {
    print('GestureDetector tapped');
    },
    child: Container(
    width: 100,
    height: 100,
    color: Colors.blue,
    child: Center(child: Text('Tap Me')),
    ),
    )
  2. 子组件尺寸太小:请确保 GestureDetector 的子组件尺寸足够大,以便用户可以轻松地触摸。如果子组件(如 Text)太小,可以使用 Container 或 SizedBox 来增加其尺寸。

  3. 嵌套在其他组件中:请检查 GestureDetector 是否被其他手势识别组件(如 ListView、PageView、InkWell 等)覆盖。在这种情况下,上层组件可能会拦截手势事件,导致 GestureDetector 无法响应。如果需要,可以使用 HitTestBehavior 属性来调整手势识别的行为。

  4. 使用 InkWell 替换 GestureDetector:在某些情况下,你可能会希望使用 InkWell 来替换 GestureDetector。InkWell 是一个具有涟漪效果的 Material Design 风格的组件,可以在用户触摸时提供视觉反馈。

    InkWell(
    onTap: () {
    print('InkWell tapped');
    },
    child: Container(
    width: 100,
    height: 100,
    color: Colors.blue,
    child: Center(child: Text('Tap Me')),
    ),
    )

如果你在实际项目中遇到问题,请提供更多关于你的项目和实现的详细信息,以便我们能够为你提供更具体的帮助。

自分析

  1. 试下 hitTestBehavior 属性,设置为 opaque,说是可以解决问题,但是没有。
  2. 发现是 UiKitView 和 PlatformViewLink 的问题,需要原生那边提供点击手势传过来才行。

gpt 继续分析

分两个 prompt 来完成。

在Flutter中,PlatformViewLink 和 UiKitView 可以在应用程序中嵌入原生视图。为了响应原生视图上的手势,您需要在原生视图(例如AndroidView或UiKitView)中配置手势识别。以下是如何为UiKitView添加手势识别的示例。

首先,为iOS平台创建一个原生视图,例如CustomView。
在 ios/Runner/CustomView.swift 文件中:

import UIKit

class CustomView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.red
self.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
self.addGestureRecognizer(tapGesture)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

@objc func handleTap(_ sender: UITapGestureRecognizer) {
print("CustomView tapped")
}
}

在这个示例中,我们创建了一个名为 CustomView 的原生视图,并为其添加了一个点击手势识别器。当点击视图时,控制台将打印 “CustomView tapped”。

注册原生视图工厂。
在 ios/Runner/AppDelegate.swift 文件中:

import Flutter
import UIKit

class CustomViewFactory: NSObject, FlutterPlatformViewFactory {
func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
return CustomViewPlatformView(frame: frame)
}
}

class CustomViewPlatformView: NSObject, FlutterPlatformView {
private let customView: CustomView

init(frame: CGRect) {
self.customView = CustomView(frame: frame)
}

func view() -> UIView {
return customView
}
}

func registerCustomView(_ registrar: FlutterPluginRegistrar) {
let factory = CustomViewFactory()
registrar.register(factory, withId: "custom_view")
}

class AppDelegate: FlutterAppDelegate {
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
let registry = controller as! FlutterPluginRegistry
registerCustomView(registry)

GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

在这里,我们创建了一个名为 CustomViewFactory 的原生视图工厂,并在 AppDelegate 类中注册它。

在 Flutter 代码中使用 UiKitView。

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('UiKitView Gesture Example'),
),
body: Center(
child: Container(
width: 200,
height: 200,
child: UiKitView(
viewType: 'custom_view',
),
),
),
),
);
}
}

在这个示例中,我们使用 UiKitView 来显示原生视图 CustomView。我们通过在 UiKitView 中设置 viewType 参数为 custom_view 来告诉 Flutter 使用我们之前注册的原生视图工厂。这将在屏幕上显示一个红色的矩形区域,当您点击这个区域时,iOS 原生代码中的 handleTap 函数将被调用,并在控制台中打印 “CustomView tapped”。

对于 Android 平台和 PlatformViewLink,实现步骤类似。您需要在 Android 的原生代码中创建一个原生视图,为其添加手势识别器,然后在 Flutter 代码中使用 PlatformViewLink 和 AndroidView 来显示原生视图。

当使用原生视图时,要注意性能影响。原生视图会创建一个叠加层,这可能会影响应用程序的性能。请确保仅在需要使用原生组件的情况下使用 PlatformViewLink、UiKitView 或 AndroidView。在其他情况下,请尽量使用 Flutter 的组件。

2 . iOS handletap 如何传到 flutter,也就是如何获取原生那部分的点击

要将 iOS 原生视图中的点击事件传递到 Flutter,您需要使用 MethodChannel。MethodChannel 是 Flutter 与原生代码之间进行通信的一种机制。以下是一个示例,展示了如何将 CustomView 中的点击事件传递到 Flutter 代码。

  1. 在 ios/Runner/CustomView.swift 文件中,添加 MethodChannel 以触发点击事件:
import UIKit
import Flutter

class CustomView: UIView {
private var channel: FlutterMethodChannel?

override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.red
self.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
self.addGestureRecognizer(tapGesture)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func setMethodChannel(_ channel: FlutterMethodChannel) {
self.channel = channel
}

@objc func handleTap(_ sender: UITapGestureRecognizer) {
print("CustomView tapped")
channel?.invokeMethod("customViewTapped", arguments: nil)
}
}

在这里,我们为 CustomView 添加了一个 MethodChannel 属性,并在点击事件处理器 handleTap 中调用 invokeMethod 方法。

  1. 在 ios/Runner/AppDelegate.swift 文件中,将 MethodChannel 传递给原生视图:
class CustomViewPlatformView: NSObject, FlutterPlatformView {
private let customView: CustomView

init(frame: CGRect, viewId: Int64, registrar: FlutterPluginRegistrar) {
self.customView = CustomView(frame: frame)

let channel = FlutterMethodChannel(name: "custom_view_\(viewId)", binaryMessenger: registrar.messenger())
customView.setMethodChannel(channel)
}

func view() -> UIView {
return customView
}
}

func registerCustomView(_ registrar: FlutterPluginRegistrar) {
let factory = CustomViewFactory(registrar: registrar)
registrar.register(factory, withId: "custom_view")
}

我们更新了 CustomViewPlatformView 的构造函数,将 MethodChannel 传递给原生视图。

  1. 在 Flutter 代码中设置 MethodChannel 并监听原生点击事件:
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
int _viewId = -1;

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('UiKitView Gesture Example'),
),
body: Center(
child: Container(
width: 200,
height: 200,
child: UiKitView(
viewType: 'custom_view',
onPlatformViewCreated: (int viewId) {
_viewId = viewId;
_setUpMethodChannel(viewId);
},
),
),
),
),
);
}

void _setUpMethodChannel(int viewId) {
final methodChannel = MethodChannel('custom_view_$viewId');
methodChannel.setMethodCallHandler(_handleMethodCall);
}

Future<void> _handleMethodCall(MethodCall call) async {
switch (call.method) {
case 'customViewTapped':
_onCustomViewTapped();
break;
default:
throw PlatformException(
code: 'Unimplemented',
details: "The method '${call.method}' is not implemented.",
);
}
}

void _onCustomViewTapped() {
print('CustomView tapped in Flutter');
// You can do whatever you want here, like showing a SnackBar or updating the UI.
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('CustomView tapped')),);
}
}

在这个示例中,我们更新了 MyApp 以使其成为一个有状态的组件,并在 UiKitViewonPlatformViewCreated 回调中设置了 MethodChannel_handleMethodCall 函数用于处理从原生代码发送的方法调用。当接收到 customViewTapped 方法时,我们调用 _onCustomViewTapped 函数,并显示一个 SnackBar

现在,在点击原生 CustomView 时,点击事件将传递到 Flutter 代码,显示一个 SnackBar 并在控制台中打印 “CustomView tapped in Flutter”。您可以根据需要在 _onCustomViewTapped 函数中添加任何其他操作。

总结