Flutter 自定义插件开发:实现原生 Toast 提示功能

1. 插件创建与配置

使用命令创建插件项目:

flutter create --template=plugin --org com.example flutter_toast_plugin

项目结构:

flutter_toast_plugin/
├── android/      # Android 原生实现
├── ios/          # iOS 原生实现
└── lib/          # Dart 接口

2. Android 原生实现

android/src/main/kotlin/com/example/flutter_toast_plugin/ToastPlugin.kt

package com.example.flutter_toast_plugin

import android.content.Context
import android.widget.Toast
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel

class ToastPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
    private lateinit var channel: MethodChannel
    private lateinit var context: Context

    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        channel = MethodChannel(binding.binaryMessenger, "toast_channel")
        channel.setMethodCallHandler(this)
        context = binding.applicationContext
    }

    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        when (call.method) {
            "showToast" -> {
                val message = call.argument<String>("msg") ?: ""
                val duration = call.argument<Int>("duration") ?: Toast.LENGTH_SHORT
                Toast.makeText(context, message, duration).show()
                result.success(null)
            }
            else -> result.notImplemented()
        }
    }

    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
    }
}

3. iOS 原生实现

ios/Classes/ToastPlugin.swift

import Flutter
import UIKit

public class ToastPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "toast_channel", binaryMessenger: registrar.messenger())
    let instance = ToastPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    switch call.method {
    case "showToast":
        guard let args = call.arguments as? [String: Any],
              let message = args["msg"] as? String else {
            result(FlutterError(code: "INVALID_ARGS", message: "Invalid arguments", details: nil))
            return
        }
        showToast(message: message)
        result(nil)
    default:
        result(FlutterMethodNotImplemented)
    }
  }

  private func showToast(message: String) {
    DispatchQueue.main.async {
        guard let window = UIApplication.shared.keyWindow else { return }
        let toastView = UILabel()
        toastView.text = message
        toastView.textAlignment = .center
        toastView.backgroundColor = UIColor.black.withAlphaComponent(0.7)
        toastView.textColor = UIColor.white
        toastView.layer.cornerRadius = 10
        toastView.clipsToBounds = true
        toastView.numberOfLines = 0

        let maxSize = CGSize(width: window.bounds.width - 40, height: window.bounds.height)
        let textSize = toastView.sizeThatFits(maxSize)
        toastView.frame = CGRect(
            x: window.center.x - textSize.width/2 - 10,
            y: window.bounds.height - 100,
            width: textSize.width + 20,
            height: textSize.height + 20
        )

        window.addSubview(toastView)
        UIView.animate(withDuration: 2.0, delay: 1.5, options: .curveEaseOut, animations: {
            toastView.alpha = 0.0
        }, completion: { _ in
            toastView.removeFromSuperview()
        })
    }
  }
}

4. Dart 接口封装

lib/flutter_toast_plugin.dart

import 'package:flutter/services.dart';

class FlutterToastPlugin {
  static const MethodChannel _channel = 
      const MethodChannel('toast_channel');

  /// 显示原生 Toast
  /// [msg] 提示内容
  /// [duration] 显示时长(仅Android有效)
  static Future<void> showToast({
    required String msg,
    int duration = 0, // 0=短时, 1=长时
  }) async {
    try {
      await _channel.invokeMethod('showToast', {
        'msg': msg,
        'duration': duration,
      });
    } on PlatformException catch (e) {
      print("Toast显示失败: ${e.message}");
    }
  }
}

5. 使用示例
import 'package:flutter/material.dart';
import 'package:flutter_toast_plugin/flutter_toast_plugin.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Toast 示例')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                child: Text('显示短Toast'),
                onPressed: () => FlutterToastPlugin.showToast(msg: '你好 Flutter!'),
              ),
              ElevatedButton(
                child: Text('显示长Toast'),
                onPressed: () => FlutterToastPlugin.showToast(
                  msg: '这是一个长时间提示',
                  duration: 1
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

关键技术点
  1. 平台通道:通过 MethodChannel 实现 Dart 与原生通信
  2. 平台差异处理
    • Android 使用原生 Toast 组件
    • iOS 使用自定义 UILabel 模拟 Toast
  3. 线程安全
    • iOS 通过 DispatchQueue.main.async 确保 UI 操作在主线程
  4. 参数传递:使用 Map 传递多参数
常见问题解决
  1. iOS 无法显示:检查是否在 AppDelegate.swift 中注册插件:
import flutter_toast_plugin

@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

  1. Android 空指针:确保在 onAttachedToEngine 中初始化 Context

此插件已实现基础 Toast 功能,可通过扩展参数支持位置调整、样式自定义等高级特性。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐