Flutter Toastx 开发思路

1. 思路

1). 原生开发
  • Android 使用原生 Toast 对象
  • iOS 使用原生创建一个悬浮层添加到窗口上
2). Flutter 开发
  • 使用 Overlay 对象添加一个 OverlayEntry 对象实现, 并将其按顺序显示出来

2. 原生开发

1). Android
  • 原生 kotlin

先在 onCreate 方法中初始化 Channel, 这里显示吐司是由 Flutter 代码主动调用,因此我们使用 MethodChannel 对象,根据 Flutter 中传递过来的方法名称和参数去显示 Toast , 在 Android 中相对比较容易。

/**
 * Flutter 向 Native 主动调用 MethodChannel
 * Native 向 Flutter 发送事件 EventChannel
 */
class MainActivity: FlutterActivity() {
  companion object {
    /** Toast标识 */
    private const val TOAST = "com.mazaiting/toast"
  }
  
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    GeneratedPluginRegistrant.registerWith(this)
    initChannel()
  }
  
  /**
   * 初始化 Channel
   */
  private fun initChannel() {
    initToastChannel()
  }
  
  /**
   * Toast工具类
   */
  private fun initToastChannel() {
    // Toast 显示
    MethodChannel(flutterView, TOAST).setMethodCallHandler { methodCall, result ->
      println(methodCall.arguments)
      when(methodCall.method) {
        "showToast" -> {
          Toast.makeText(this, methodCall.argument<String>("msg"), Toast.LENGTH_SHORT).show()
          result.success("success")
        }
        else -> {
          // 未实现
          result.notImplemented()
        }
      }
    }
  }
2). iOS
  • 先创建一个 UILabel, 设置它的字体大小,颜色,内容,居中,再创建一个 UIView , 设置它的背景,圆角,并将 UILabel 添加到其中,最后创建 UIWindow对象,将 UIView 添加到 UIWindow 中,最后将 UIWindow 对象添加在UIApplication.shared.windows, 源码如下。
//
//  Toast.swift
//  Runner
//
//  Created by 麻再挺 on 2019/3/25.
//  Copyright © 2019 The Chromium Authors. All rights reserved.
//

import UIKit

// 弹窗
class ToastView: NSObject {
    
    // 单例
    static let instance: ToastView = ToastView()
    // 获取窗口对象
    var windows = UIApplication.shared.windows
    // 键盘视图
    let rv = UIApplication.shared.keyWindow?.subviews.first as UIView!
    // 获取屏幕宽高
    let windowFrame = UIScreen.main.bounds
    // 显示弹窗
    // @param content 显示内容
    // @param duration 显示时长, 默认为1.5s
    func showToast(content: String, duration: CFTimeInterval = 1.5) {
        // 清除所有弹窗
        clear()
        // 创建 Toast 视图
        let contentView = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 50))
        // 设置字体大小
        contentView.font = UIFont.systemFont(ofSize: 13)
        // 设置字体颜色
        contentView.textColor = UIColor.white
        // 设置内容
        contentView.text = content
        // 设置对齐方式
        contentView.textAlignment = NSTextAlignment.center
        
        // 创建 Toast 父布局
        let containerView = UIView()
        // 设置圆角
        containerView.layer.cornerRadius = 10
        // 设置背景颜色
        containerView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.7)
        
        // 将 Toast 视图 添加到 Toast 父布局中
        containerView.addSubview(contentView)
        
        let frame = CGRect(x: 0, y: 0, width: 300, height: 50)
        let window = UIWindow()
        window.backgroundColor = UIColor.clear
        window.frame = frame
        containerView.frame = frame
        window.windowLevel = UIWindowLevelAlert
        window.center = CGPoint(x: windowFrame.maxX * 0.5, y: windowFrame.maxY * 0.8)
//        window.center = CGPoint(x: rv?.center.x, y: rv?.center.y * 16 / 10)
        window.isHidden = false
        window.addSubview(containerView)
        
        windows.append(window)
        
        containerView.layer.add(AnimationUtil.getToastAnimation(duration: duration), forKey: "animation")
        
        
//        perform(#selector(removeToast(_:)), with: window, afterDelay: duration)
        
        DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute:{
            self.removeToast(sender: window)
        })
    }
    
    // 移除当前所有弹窗
    func removeToast(sender: AnyObject) {
        if let window = sender as? UIWindow {
            if let index = windows.firstIndex(of: window) {
                // 移除对应 window
                windows.remove(at: index)
            }
        } else {
            print("can not find the window")
        }
    }
    
    // 清除所有弹窗
    func clear() {
        // 取消之前的所有请求
        NSObject.cancelPreviousPerformRequests(withTarget: self)
        // 移除全部
        windows.removeAll(keepingCapacity: false)
    }
}

其中 AnimationUtil 工具类代码如下:

//
//  AnimationUtil.swift
//  Runner
//
//  Created by 麻再挺 on 2019/3/26.
//  Copyright © 2019 The Chromium Authors. All rights reserved.
//

import Foundation

// 动画工具类
class AnimationUtil {
    // 弹窗动画
    // @param duration 显示时长, 默认值为1.5s
    // @return 动画
    static func getToastAnimation(duration: CFTimeInterval = 1.5) -> CAAnimation {
        // 大小变化动画
        let scaleAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
        // 设置时间
        scaleAnimation.keyTimes = [0, 0.1, 0.9, 1]
        // 设置值
        scaleAnimation.values = [0.5, 1, 1, 0.5]
        // 设置显示时间
        scaleAnimation.duration = duration
        
        // 透明度变化动画
        let opacityAnimation = CAKeyframeAnimation(keyPath: "opacity")
        // 设置时间
        opacityAnimation.keyTimes = [0, 0.8, 1]
        // 设置值
        opacityAnimation.values = [0.5, 1, 0]
        // 设置显示时长
        opacityAnimation.duration = duration
        
        // 组动画
        let animation = CAAnimationGroup()
        // 设置动画组
        animation.animations = [scaleAnimation, opacityAnimation]
        // 动画过渡效果
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
        // 设置时间
        animation.duration = duration
        // 设置重复次数
        animation.repeatCount = 0
        // 设置完成后移除
        animation.isRemovedOnCompletion = false
        return animation
    }
}
3). Flutter 端代码
import 'package:flutter/services.dart';

class Toast {
  // 获取原生Toast
  static const TOAST = const MethodChannel("com.mazaiting/toast");

  /// 显示 Toast
  /// @param msg 显示信息
  static Future<void> show(msg) async {
    try {
      final String result = await TOAST.invokeMethod('showToast', {'msg': msg});
      print("Android_Toast: " + result);
    } on PlatformException catch (e) {
      print("Android_Toast: " + e.message);
    }
  }
}

3. Overlay 添加 OverlayEntry 方法

1). 创建一个 OverlayEntry 实体对象
//创建一个OverlayEntry对象
    OverlayEntry overlayEntry = OverlayEntry(builder: (context) {
      //外层使用Positioned进行定位,控制在Overlay中的位置
      return new Positioned(
          top: MediaQuery.of(context).size.height * 0.8, // 设置距离顶部80%
          child: new Material(
            type: MaterialType.transparency, // 设置透明
            child: new Container(
              width: MediaQuery.of(context).size.width, // 设置宽度
              alignment: Alignment.center, // 设置居中
              child: new Center(
                child: Container(
                  constraints: BoxConstraints(
                      maxWidth: MediaQuery.of(context).size.width *
                          0.8), // 设置约束,最大宽度为屏幕宽的80%
                  child: new Card(
                    child: new Padding(
                      padding: EdgeInsets.all(10),
                      child: new Text(message),
                    ),
                    color: Colors.grey,
                    shape: RoundedRectangleBorder(
                        borderRadius:
                            BorderRadius.all(Radius.circular(10))), // 设置圆角
                  ),
                ),
              ),
            ),
          ));
    });
2). 添加到 Overlay 中
    //往Overlay中插入插入OverlayEntry
    Overlay.of(context).insert(overlayEntry);
3). 延时后移除
    //两秒后,移除Toast
    new Future.delayed(Duration(seconds: 2)).then((value) {
      // 移除
      overlayEntry.remove();
    });
4). 定义一个静态列表,将每次创建好的 OverlayEntry 对象添加到静态表中,使用递归方法,每次移除一个 OverlayEntry 后,插入静态列表的第一个元素,并显示延时关闭。

Toastx_Gitee

Toastx_Github

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336