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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值