返回顶部
首页 > 资讯 > 移动开发 >Flutter学习LogUtil封装与实现实例详解
  • 239
分享到

Flutter学习LogUtil封装与实现实例详解

2024-04-02 19:04:59 239人浏览 八月长安
摘要

目录一. 为什么要封装打印类二. 需要哪些类三. 打印输出的抽象类四. 格式化日志内容格式化堆栈堆栈裁切工具类格式化堆栈信息格式化JSON五. 需要用到的常量六. 为了控制多个打印器

一. 为什么要封装打印类

虽然 flutter/原生给我们提供了日志打印的功能,但是超出一定长度以后会被截断

json打印挤在一起看不清楚

堆栈打印深度过深多打印一些不需要的东西

实现 log 的多种展示方式

二. 需要哪些类

为了可以实现对日志的多种内容格式化和各种显示输出所以抽出来以下几个类

  • 一些常量的字符串表示
  • 对日志内容的打印输出抽象类
  • 对日志内容格式化的抽象类
  • 日志工具的config类
  • 日志工具的管理类
  • 日志工具的Util类

三. 打印输出的抽象类

打印类核心的功能就是打印日志 所以它有一个方法就是打印的方法
而我们要打印输出的内容有 当前 log等级 log的tag 需要打印的数据 当前堆栈信息 亦或是获取的Json数据

/// 日志打印输出的接口类
abstract class IHCLogPrint {
  void logPrint({
    required LogType type,
    required String tag,
    required String message,
    StackTrace? stackTrace,
    Map<String, dynamic>? json,
  });
}

四. 格式化日志内容

这里定义一个IHCLogFORMatter抽象类

///格式化的接口类
abstract class IHCLogFormatter<T> {
  String format(T data);
}

格式化堆栈

堆栈的格式例如这样

#0 LogUtil._logPrint (package:com.halfcity.full_flutter_app/utils/log/log_util.dart:104:42)
#1 LogUtil._logPrint (package:com.halfcity.full_flutter_app/utils/log/log_util.dart:104:42)
#2 LogUtil._logPrint (package:com.halfcity.full_flutter_app/utils/log/log_util.dart:104:42)
....

会返回来很多无用的数据 而我们实际用到的也不过前五层就可以了
所以需要一个工具来剔除无用的数据和当前自己的包名

堆栈裁切工具类

class StackTraceUtil {
  ///正则表达式 表示#+数字+空格的格式
  static final RegExp _startStr = RegExp(r'#\d+[\s]+');
  ///正则表达式表示 多个非换行符+ (非空) 正则表达式中()代表子项 如果需要正则()需要转义\( \)
  ///了解更多 https://www.runoob.com/regexp/regexp-syntax.html
  static final RegExp _stackReg = RegExp(r'.+ \(([^\s]+)\)');
  /// 把StackTrace 转成list 并去除无用信息
  /// [stackTrace] 堆栈信息
  ///#0      LogUtil._logPrint (package:com.halfcity.full_flutter_app/utils/log/log_util.dart:104:42)
  static List<String> _fixStack(StackTrace stackTrace) {
    List tempList = stackTrace.toString().split("\n");
    List<String> stackList = [];
    for (String str in tempList) {
      if (str.startsWith(_startStr)) {
        //又是#号又是空格比较占地方 这里省去了 如果你不想省去直接传入str即可
        stackList.add(str.replaceFirst(_startStr, ' '));
      }
    }
    return stackList;
  }
  ///获取剔除忽略包名及其其他无效信息的堆栈
  /// [stackTrace] 堆栈
  /// [ignorePackage] 需要忽略的包名
  static List<String> _getRealStackTrack(
      StackTrace stackTrace, String ignorePackage) {
    ///由于Flutter 上的StackTrack上的不太一样,Android返回的是list flutter返回的是StackTrack 所以需要手动切割 再处理
    List<String> stackList = _fixStack(stackTrace);
    int ignoreDepth = 0;
    int allDepth = stackList.length;
    //倒着查询 查到倒数第一包名和需要屏蔽的包名一致时,数据往上的数据全部舍弃掉
    for (int i = allDepth - 1; i > -1; i--) {
      Match? match = _stackReg.matchAsPrefix(stackList[i]);
      //如果匹配且第一个子项也符合  group 0 表示全部 剩下的数字看子项的多少返回
      if (match != null &&
          (match.group(1)!.startsWith("package:$ignorePackage"))) {
        ignoreDepth = i + 1;
        break;
      }
    }
    stackList = stackList.sublist(ignoreDepth);
    return stackList;
  }
  /// 裁切堆栈
  /// [stackTrace] 堆栈
  /// [maxDepth] 深度
  static List<String> _cropStackTrace(List<String> stackTrace, int? maxDepth) {
    int realDeep = stackTrace.length;
    realDeep =
        maxDepth != null && maxDepth > 0 ? min(maxDepth, realDeep) : realDeep;
    return stackTrace.sublist(0, realDeep);
  }
  ///裁切获取到最终的stack 并获取最大深度的栈信息
  static getCroppedRealStackTrace(
      {required StackTrace stackTrace, ignorePackage, maxDepth}) {
    return _cropStackTrace(
        _getRealStackTrack(stackTrace, ignorePackage), maxDepth);
  }
}

格式化堆栈信息


class StackFormatter implements ILogFormatter<List<String>> {
  @override
  String format(List<String> stackList) {
    ///每一行都设置成单独的 字符串
    StringBuffer sb = StringBuffer();
    ///堆栈是空的直接返回
    if (stackList.isEmpty) {
      return "";
      ///堆栈只有一行那么就返回 - 堆栈
    } else if (stackList.length == 1) {
      return "\n\t-${stackList[0].toString()}\n";
      ///多行堆栈格式化
    } else {
      for (int i = 0; i < stackList.length; i++) {
        if (i == 0) {
          sb.writeln("\n\t┌StackTrace:");
        }
        if (i != stackList.length - 1) {
          sb.writeln("\t├${stackList[i].toString()}");
        } else {
          sb.write("\t└${stackList[i].toString()}");
        }
      }
    }
    return sb.toString();
  }
}

格式化JSON

class JsonFormatter extends ILogFormatter<Map<String, dynamic>> {
  @override
  String format(Map<String, dynamic> data) {
    ///递归调用循环遍历data 在StringBuffer中添加StringBuffer
    String finalString = _forEachJson(data, 0);
    finalString = "\ndata:$finalString";
    return finalString;
  }
  /// [data]  传入需要格式化的数据
  /// [spaceCount]  需要添加空格的长度 一个数字是两个空格
  /// [needSpace] 需不需要添加空格
  /// [needEnter] 需不需要回车
  String _forEachJson(dynamic data, int spaceCount,
      {bool needSpace = true, needEnter = true}) {
    StringBuffer sb = StringBuffer();
    int newSpace = spaceCount + 1;
    if (data is Map) {
      ///如果它是Map走这里
      ///是否需要空格
      sb.write(buildSpace(needSpace ? spaceCount : 0));
      sb.write(needEnter ? "{\n" : "{");
      data.forEach((key, value) {
        ///打印输出 key
        sb.write("${buildSpace(needEnter ? newSpace : 0)}$key: ");
        ///递归调用看value是什么类型 如果字符长度少于30就不回车显示
        sb.write(_forEachJson(value, newSpace,
            needSpace: false,
            needEnter: !(value is Map ? false : value.toString().length < 50)));
        ///不是最后一个就加,
        if (data.keys.last != key) {
          sb.write(needEnter ? ",\n" : ",");
        }
      });
      if (needEnter) {
        sb.writeln();
      }
      sb.write("${buildSpace(needEnter ? spaceCount : 0)}}");
    } else if (data is List) {
      ///如果他是列表 走这里
      sb.write(buildSpace(needSpace ? spaceCount : 0));
      sb.write("[${needEnter ? "\n" : ""}");
      for (var item in data) {
        sb.write(_forEachJson(item, newSpace,
            needEnter: !(item.toString().length < 30)));
        ///不是最后一个就加的,
        if (data.last != item) {
          sb.write(needEnter ? ",\n" : ",");
        }
      }
      sb.write(needEnter ? "\n" : "");
      sb.write("${buildSpace(needSpace?spaceCount:0)}]");
    } else if (data is num || data is bool) {
      ///bool 或者数组不加双引号
      sb.write(data);
    } else if (data is String) {
      ///string 或者其他的打印加双引号 如果他是回车就改变他 按回车分行会错乱
      sb.write("\"${data.replaceAll("\n", r"\n")}\"");
    } else {
      sb.write("$data");
    }
    return sb.toString();
  }
  ///构造空格
  String buildSpace(int deep) {
    String temp = "";
    for (int i = 0; i < deep; i++) {
      temp += "  ";
    }
    return temp;
  }
}

五. 需要用到的常量

///常量
//log的type
enum LogType {
  V, //VERBOSE
  E, //ERROR
  A, //ASSERT
  W, //WARN
  I, //INFO
  D, //DEBUG
}
int logMaxLength=1024;
///log的type 字符串说明
List logTypeStr = ["VERBOSE", "ERROR", "ASSERT", "WARN", "INFO", "DEBUG"];
///log的type 数字说明(匹配的Android原生,iOS暂不清楚)
List&lt;int&gt; logTypeNum = [2, 6, 7, 5, 4, 3];

六. 为了控制多个打印器的设置做了一个配置类

class LoGConfig {
  ///是否开启日志
  bool _enable = false;
  ///默认的Tag
  String _globalTag = "LogTag";
  ///堆栈显示的深度
  int _stackTraceDepth = 0;
  ///打印的方式
  List<ILogPrint>? _printers;
  LogConfig({enable, globalTag, stackTraceDepth, printers}) {
    _enable = enable;
    _globalTag = globalTag;
    _stackTraceDepth = stackTraceDepth;
    _printers?.addAll(printers);
  }
  @override
  String toString() {
    return 'LogConfig{_enable: $_enable, _globalTag: $_globalTag, _stackTraceDepth: $_stackTraceDepth, _printers: $_printers}';
  }
  get enable => _enable;
  get globalTag => _globalTag;
  get stackTraceDepth => _stackTraceDepth;
  get printers => _printers;
}

七. Log的管理类

class LogManager {
  ///config
  late LogConfig _config;
  ///打印器列表
  List&lt;ILogPrint&gt; _printers = [];
  ///单例模式
  static LogManager? _instance;
  factory LogManager() =&gt; _instance ??= LogManager._();
  LogManager._();
  ///初始化 Manager方法
  LogManager.init({config, printers}) {
    _config = config;
    _printers.addAll(printers);
    _instance = this;
  }
  get printers =&gt; _printers;
  get config =&gt; _config;
  void addPrinter(ILogPrint print) {
    bool isHave = _printers.any((element) =&gt; element == print);
    if (!isHave) {
      _printers.add(print);
    }
  }
  void removePrinter(ILogPrint print) {
    _printers.remove(print);
  }
}

九. 调用LogUtil

class LogUtil {
  static const String _ignorePackageName = "log_demo/utils/log";
  static void V(
      {String? tag,
      dynamic? message,
      LogConfig? logConfig,
      StackTrace? stackTrace,
      Map<String, dynamic>? json}) {
    _logPrint(
        type: LogType.V,
        tag: tag ??= "",
        logConfig: logConfig,
        message: message,
        json: json,
        stackTrace: stackTrace);
  }
  static void E(
      {String? tag,
      dynamic? message,
      LogConfig? logConfig,
      StackTrace? stackTrace,
      Map<String, dynamic>? json}) {
    _logPrint(
        type: LogType.E,
        tag: tag ??= "",
        message: message,
        logConfig: logConfig,
        json: json,
        stackTrace: stackTrace);
  }
  static void I(
      {String? tag,
      dynamic? message,
      LogConfig? logConfig,
      StackTrace? stackTrace,
      Map<String, dynamic>? json}) {
    _logPrint(
        type: LogType.I,
        tag: tag ??= "",
        message: message,
        json: json,
        stackTrace: stackTrace);
  }
  static void D(
      {String? tag,
      dynamic? message,
      LogConfig? logConfig,
      StackTrace? stackTrace,
      Map<String, dynamic>? json}) {
    _logPrint(
        type: LogType.D,
        tag: tag ??= "",
        logConfig: logConfig,
        message: message,
        json: json,
        stackTrace: stackTrace);
  }
  static void A(
      {String? tag,
      LogConfig? logConfig,
      dynamic? message,
      StackTrace? stackTrace,
      Map<String, dynamic>? json}) {
    _logPrint(
        type: LogType.A,
        tag: tag ??= "",
        message: message,
        logConfig: logConfig,
        json: json,
        stackTrace: stackTrace);
  }
  static void W(
      {String? tag,
      dynamic? message,
      LogConfig? logConfig,
      StackTrace? stackTrace,
      Map<String, dynamic>? json}) {
    _logPrint(
        type: LogType.W,
        tag: tag ??= "",
        message: message,
        logConfig: logConfig,
        json: json,
        stackTrace: stackTrace);
  }
  static Future<void> _logPrint({
    required LogType type,
    required String tag,
    LogConfig? logConfig,
    dynamic message,
    StackTrace? stackTrace,
    Map<String, dynamic>? json,
  }) async {
    ///如果logConfig为空那么就用默认的
    logConfig ??= LogManager().config;
    if (!logConfig?.enable) {
      return;
    }
    StringBuffer sb = StringBuffer();
    ///打印当前页面
    if (message.toString().isNotEmpty) {
      sb.write(message);
    }
    ///如果传入了栈且 要展示的深度大于0
    if (stackTrace != null && logConfig?.stackTraceDepth > 0) {
      sb.writeln();
      String stackTraceStr = StackFormatter().format(
          StackTraceUtil.getCroppedRealStackTrace(
              stackTrace: stackTrace,
              ignorePackage: _ignorePackageName,
              maxDepth: logConfig?.stackTraceDepth));
      sb.write(stackTraceStr);
    }
    if (json != null) {
      sb.writeln();
      String body = JsonFormatter().format(json);
      sb.write(body);
    }
    ///获取有几个打印器
    List<ILogPrint> prints = logConfig?.printers ?? LogManager().printers;
    if (prints.isEmpty) {
      return;
    }
    ///遍历打印器 分别打印数据
    for (ILogPrint print in prints) {
      print.logPrint(type: type, tag: tag, message: sb.toString());
    }
  }
}

十. 定义一个Flutter 控制台打印输出的方法

class ConsolePrint extends ILogPrint {
  @override
  void logPrint(
      {required LogType type,
      required String tag,
      required String message,
      StackTrace? stackTrace,
      Map<String, dynamic>? json}) {
    ///如果要开启颜色显示 那么就是1000
    ///如果不开启颜色显示 那么就是1023
    int _maxCharLength = 1000;
    //匹配中文字符以及这些中文标点符号 。 ? ! , 、 ; : “ ” ‘ ' ( ) 《 》 〈 〉 【 】 『 』 「 」 ﹃ ﹄ 〔 〕 … — ~ ﹏ ¥
    RegExp _chineseRegex = RegExp(r"[\u4e00-\u9fa5|\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5]");
    ///用回车做分割
    List<String> strList = message.split("\n");
    ///判断每句的长度 如果长度过长做切割
    for (String str in strList) {
      ///获取总长度
      int len = 0;
      ///获取当前长度
      int current = 0;
      ///获取截断点数据
      List<int> entry = [0];
      ///遍历文字 查看真实长度
      for (int i = 0; i < str.length; i++) {
        //// 一个汉字再打印区占三个长度,其他的占一个长度
        len += str[i].contains(_chineseRegex) ? 3 : 1;
        ///寻找当前字符的下一个字符长度
        int next = (i + 1) < str.length
            ? str[i + 1].contains(_chineseRegex)
                ? 3
                : 1
            : 0;
        ///当前字符累计长度 如果达到了需求就清空
        current += str[i].contains(_chineseRegex) ? 3 : 1;
        if (current < _maxCharLength && (current + next) >= _maxCharLength) {
          entry.add(i);
          current = 0;
        }
      }
      ///如果最后一个阶段点不是最后一个字符就添加上
      if (entry.last != str.length - 1) {
        entry.add(str.length);
      }
      ///如果所有的长度小于1023 那么打印没有问题
      if (len < _maxCharLength) {
        _logPrint(type, tag, str);
      } else {
        ///按照获取的截断点来打印
        for (int i = 0; i < entry.length - 1; i++) {
          _logPrint(type, tag, str.substring(entry[i], entry[i + 1]));
        }
      }
    }
  }
  _logPrint(LogType type, String tag, String message) {
    ///前面的\u001b[31m用于设定SGR颜色,后面的\u001b[0m相当于一个封闭标签作为前面SGR颜色的作用范围的结束点标记。
    /// \u001b[3 文字颜色范围 0-7 标准颜色 0是黑色 1是红色 2是绿色 3是黄色 4是蓝色 5是紫色 6蓝绿色 是 7是灰色 范围之外都是黑色
    /// \u001b[9 文字颜色范围 0-7 高强度颜色 0是黑色 1是红色 2是绿色 3是黄色 4是蓝色 5是紫色 6蓝绿色 是 7是灰色 范围之外都是黑色
    /// 自定义颜色 \u001b[38;2;255;0;0m 表示文字颜色 2是24位 255 0 0 是颜色的RGB 可以自定义颜色
    /// \u001b[4 数字 m 是背景色
    /// \u001b[1m 加粗
    /// \u001b[3m 斜体
    /// \u001b[4m 下划线
    /// \u001b[7m 黑底白字
    ///\u001b[9m 删除线
    ///\u001b[0m 结束符
    //////详情看 Https://www.cnblogs.com/zt123123/p/16110475.html
    String colorHead = "";
    String colorEnd = "\u001b[0m";
    switch (type) {
      case LogType.V:
        // const Color(0xff181818);
        colorHead = "\u001b[38;2;187;187;187m";
        break;
      case LogType.E:
        colorHead = "\u001b[38;2;255;0;6m";
        break;
      case LogType.A:
        colorHead = "\u001b[38;2;143;0;5m";
        break;
      case LogType.W:
        colorHead = "\u001b[38;2;187;187;35m";
        break;
      case LogType.I:
        colorHead = "\u001b[38;2;72;187;49m";
        break;
      case LogType.D:
        colorHead = "\u001b[38;2;0;112;187m";
        break;
    }
    /// 这里是纯Flutter项目所以在控制台打印这样子是可以有颜色的 如果是flutter混编 安卓原生侧打印\u001b 可能是一个乱码也没有变色效果
    /// 如果你不想只在调试模式打印 你可以把debugPrint换成print
    debugPrint("$colorHead$message$colorEnd");
    /// 如果原生侧有封装log工具直接 写一个methodChannel 传参数就好 ,如果没有,可以调用原生的log打印 传入 level tag 和message
    /// kDebugMode 用这个可以判断是否在debug模式下
    /// if(kDebugMode){
    /// 在debug模式下打印日志
    //  bool? result=await CustomChannelUtil.printLog(level:logTypeNum[type.index],tag:tag,message:message);
    /// }
  }
}

十一. 现在使用前初始化log打印器一次

 Widget build(BuildContext context) {
    LogManager.init(
        config: LogConfig(enable: true, globalTag: "TAG", stackTraceDepth: 5),
        printers: [ConsolePrint()]);

使用

///打印堆栈
    LogUtil.I(tag: "test", stackTrace: StackTrace.current);
///打印json
    LogUtil.E(tag: "JSON", json: json);
///打印信息 
    LogUtil.V(tag: "LogText", message: message);

以上就是Flutter学习LogUtil封装与实现实例详解的详细内容,更多关于Flutter LogUtil 封装实现的资料请关注编程网其它相关文章!

--结束END--

本文标题: Flutter学习LogUtil封装与实现实例详解

本文链接: https://lsjlt.com/news/167187.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Flutter学习LogUtil封装与实现实例详解
    目录一. 为什么要封装打印类二. 需要哪些类三. 打印输出的抽象类四. 格式化日志内容格式化堆栈堆栈裁切工具类格式化堆栈信息格式化JSON五. 需要用到的常量六. 为了控制多个打印器...
    99+
    2024-04-02
  • Flutter学习之实现自定义themes详解
    目录简介MaterialApp中的themes自定义themes的使用总结简介 一般情况下我们在flutter中搭建的app基本上都是用的是MaterialApp这种设计模式,Mat...
    99+
    2023-03-06
    Flutter自定义themes Flutter themes
  • vue3学习笔记简单封装axios示例实现
    目录简介openapi基本配置拦截器api请求数据渲染简介 axios是一个基于promise的网络请求库,管理后台使用的场景通常 获取后端api数据,然后交给页面渲染 还是在前面的...
    99+
    2024-04-02
  • Flutter实现Text完美封装
    使用惯了android的布局,对Flutter的组件和布局简直深恶痛绝啊,于是下定决心,一点一点封装Flutter的基础组件,今天封装的是Text组件,自认为封装的非常完美,完全可以...
    99+
    2024-04-02
  • vue实现全选组件封装实例详解
    效果  封装的组件 <template> <el-form-item :label="label"> <el-checkbox ...
    99+
    2024-04-02
  • React Native Modal 的封装与使用实例详解
    目录背景Android FullScreenModal 的封装使用Android 原生实现全屏 Dialog封装给 RN 进行相关的调用Android 原生部分实现JS 部分实现使用...
    99+
    2024-04-02
  • Flutter之Dio封装+实例(自己梳理)
    参考链接 https://github.com/cfug/dio/blob/main/dio/README-ZH.md 添加依赖  手动添加到pubspec.yaml: dependencies: dio: ^替换为最新版本 在终端使用以...
    99+
    2023-10-26
    flutter
  • Flutter如何实现Text完美封装
    这篇文章主要介绍“Flutter如何实现Text完美封装”,在日常操作中,相信很多人在Flutter如何实现Text完美封装问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Flutter如何实现Text完美封装...
    99+
    2023-06-21
  • Flutter StreamBuilder实现局部刷新实例详解
    目录前言StreamBuilder 简介StreamBuilder的实际应用总结前言 在flutter项目中,页面内直接调用setState方法会使得页面重新执行build方法,导致...
    99+
    2024-04-02
  • JavaScript封装axios的实现详解
    目录摘要1.post方法2.create方法3.拦截器4.代码摘要 在vue中,我们调用接口使用的都是axios,使用之前我们也会进行一定的封装,然后再进行使用。 在这里,我们主要说...
    99+
    2024-04-02
  • 图文详解Flutter单例的实现
    目录前言创建单例测试单例效果测试一测试二总结前言 作为最简单的一种设计模式之一,对于单例本身的概念,大家一看就能明白,但在某些情况下也很容易使用不恰当。相比其他语言,Dart 和 F...
    99+
    2024-04-02
  • Flutter实现矩形取色器的封装
    前言 最近看插件库上少有的取色器大都是圆形的或者奇奇怪的的亚子,所以今天做两个矩形的颜色取色器 提示:以下是本篇文章正文内容,下面案例可供参考 一、BarTypeColorPicke...
    99+
    2024-04-02
  • Android 逆向学习详解及实例
             断断续续的总算的把android开发和逆向的这两本书看完了,虽然没有java,...
    99+
    2022-06-06
    学习 Android
  • Android多线程学习实例详解
    本文实例分析了Android多线程。分享给大家供大家参考,具体如下: 在Android下面也有多线程的概念,在C/C++中,子线程可以是一个函数,一般都是一个带有循环的函数,来...
    99+
    2022-06-06
    学习 android多线程 线程 Android
  • JavaScript axios安装与封装案例详解
    1.下载axios插件 cnpm install axios -S 2.在main.js引入axios import axios from 'axios' Vue.prot...
    99+
    2024-04-02
  • Go日志封装实战示例详解
    目录背景Logger 封装的理解简单包封装多日志库切换使用中间件记录公共信息背景 日志是框架中至关重要的一部分。在 Golang 只也提供了基本的日志组件,但其没有提供给比较多的功...
    99+
    2023-05-16
    Go 日志封装 Go 日志
  • flutter实现磨砂玻璃效果实例详解
    目录flutter 中实现磨砂玻璃效果编写代码输出效果flutter 中实现磨砂玻璃效果 磨砂玻璃效果是一个很酷的用户界面概念,使我们的用户界面看起来更有吸引力。它基本上是一个模糊的...
    99+
    2022-11-13
    flutter 磨砂玻璃 flutter 磨砂
  • nodeJS express路由学习req.body与req.query方法实例详解
    目录引言前端路由后端路由Express路由教学GET/POST路由演示详解req处理数据的方法引言 所谓 路由 就是根据不同的 url 地址展示不同的内容或页面 形象点 举个栗子: ...
    99+
    2024-04-02
  • Hooks封装与使用示例详解
    目录Hooks是什么?Hooks解决了什么?HOC与HOOK对比分别使用React与Vue3两种框架封装useThrottle钩子函数总结Hooks是什么? 本篇文章主要介绍Hook...
    99+
    2023-01-04
    Hooks封装使用 Hooks封装
  • Flutter 绘制风车实现示例详解
    目录前言展示1. 风车 1 的绘制2. 风车 2 的绘制3. 旋转动画的处理4. 旋转动画的圈数前言展示 最近源码看得比较多,本文来画点东西调节下心情,本绘制已收录于 Flutter...
    99+
    2022-11-13
    Flutter 绘制风车 Flutter 绘制
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作