Flutter 学习 之 LogUtil 的 封装与实现 (一)

Flutter运行app时向logcat输出当前打开的界面路径且点击可跳转 阅读详情

一. 为什么要封装打印类

虽然 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<int> 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<ILogPrint> _printers = [];

  ///单例模式
  static LogManager? _instance;

  factory LogManager() => _instance ??= LogManager._();

  LogManager._();

  ///初始化 Manager方法
  LogManager.init({config, printers}) {
    _config = config;
    _printers.addAll(printers);
    _instance = this;
  }

  get printers => _printers;

  get config => _config;

  void addPrinter(ILogPrint print) {
    bool isHave = _printers.any((element) => 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);

源码地址 https://gitee.com/half_city/flutter_log_util.git

本文转自 https://www.jianshu.com/p/84f74b67add3,如有侵权,请联系删除。

有需要更多完整学习资料的,可以扫描下方二维码领取资料!

Flutter技术解析与实战,全家桶学习资料(含Flutter进阶学习笔记、入门与实战和完整开发实战详解)

Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。

在这里为了方便大家系统的学习Flutter,这里特意联合了阿里P7架构师和谷歌技术团队共同整理了一份Flutter全家桶学习资料。

内容概要:Flutter技术解析与实战、Flutter进阶学习笔记、Flutter入门与实战和Flutter完整开发实战详解。

内容特点:条理清晰,含图像化表示更加易懂。

由于文章内容比较多,篇幅有限,资料已经被整理成了PDF文档,有需要 Flutter技术解析与实战 完整文档的可扫描下方卡片免费获取!

# **《Flutter技术解析与实战》**

目录

img

第一章 混合工程

​ ● Flutter工程体系

​ ● 混合工程改造实战

​ ● 混合工程与持续集成

​ ● 快速完成混合工程搭建

​ ● 使用混合栈框架开发

img

第二章 能力增强

​ ● 基于原生能力的插件扩展

​ ● 基于外接纹理的同层渲染

​ ● 多媒体能力扩展实践

​ ● 富文本能力应用实践

img

第三章 业务架构设计

​ ● 应用框架设计实践

​ ● 轻量级动态化渲染引擎的设计

​ ● 面向切面编程的设计实践

​ ● 高性能的动态模板渲染实践

img

第四章 数据统计与性能

​ ● 数据统计框架的设计

​ ● 性能稳定性监控方案的设计

​ ● 高可用框架的设计与实践

​ ● 跨端方案性能对比实践

img

第五章 企业级应用实战

​ ● 基于Flutter的端结构演进与创新

​ ● Flutter与FaaS云端一体化架构

img

由于文章内容比较多,篇幅有限,资料已经被整理成了PDF文档,有需要 Flutter技术解析与实战 完整文档的可以加微信) 即可免费领取!

《Flutter进阶学习笔记》

目录

img

img

第一章 为什么 Flutter 是跨平台开发的终极之选

​ ● 这是为什么?

​ ● 跨平台开发

​ ● 什么是Flutter

​ ● Flutter特性

​ ● Flutter 构建应用的工具

​ ● 使用 Flutter 构建的热门应用

​ ● 构建 Flutter 应用的成本

img

第二章 在Windows上搭建Flutter开发环境

​ ● 使用镜像

​ ● 系统要求

​ ● 获取Flutter SDK

​ ● 编辑器设置

​ ● Android设置

​ ● 起步: 配置编辑器

​ ● 起步: 体验

​ ● 体验热重载

img

第三章 编写您的第一个 Flutter App

​ ● 创建 Flutter app

​ ● 使用外部包(package)

​ ● 添加一个 有状态的部件(Stateful widget)

​ ● 创建一个无限滚动ListView

​ ● 添加交互

​ ● 导航到新页面

​ ● 使用主题更改UI

img

第四章 Flutter开发环境搭建和调试

​ ● 开发环境的搭建

​ ● 模拟器的安装与调试

​ ● 开发环境的搭建

​ ● 模拟器的安装与调试

img

第五章 Dart语法篇之基础语法(一)

​ ● 简述

​ ● Hello Dart

​ ● 数据类型

​ ● 变量和常量

​ ● 集合(List、Set、Map)

​ ● 流程控制

​ ● 运算符

​ ● 异常

​ ● 函数

​ ● 总结

img

第六章 Dart语法篇之集合的使用与源码解析(二)

​ ● List

​ ● Set

​ ● Map

​ ● Queue

​ ● LinkedList

​ ● HashMap

​ ● Map、HashMap、LinkedHashMap、SplayTreeMap区别

​ ● 命名构造函数from和of的区别以及使用建议

img

第七章 Dart语法篇之集合操作符函数与源码分析(三)

​ ● 简述

​ ● Iterable

​ ● forEach

​ ● map

​ ● any

​ ● every

​ ● where

​ ● firstWhere和singleWhere和lastWhere

​ ● join

​ ● take

​ ● takeWhile

​ ● skip

​ ● skipWhile

​ ● follwedBy

​ ● expand

​ ● reduce

​ ● elementAt

img

第八章 Dart语法篇之函数的使用(四)

​ ● 简述

​ ● 函数参数

​ ● 匿名函数(闭包,lambda)

​ ● 箭头函数

​ ● 局部函数

​ ● 顶层函数和静态函数

​ ● main函数

​ ● Function函数对象

第九章 Dart语法篇之面向对象基础(五)

​ ● 简述

​ ● 属性访问器(accessor)函数setter和getter

​ ● 面向对象中的变量

​ ● 构造函数

​ ● 抽象方法、抽象类和接口

​ ● 类函数

​ ● 总结

img

第十章 Dart语法篇之面向对象继承和Mixins(六)

​ ● 简述

​ ● 类的单继承

​ ● 基于Mixins的多继承

​ ● 总结

img

第十一章 Dart语法篇之类型系统与泛型(七)

​ ● 简述

​ ● 可选类型

​ ● 接口类型

​ ● 泛型

​ ● 类型具体化

​ ● 总结

img

第十二章 Flutter中的widget

​ ● Flutter页面-基础Widget

​ ● Widget

​ ● StatelessWidget

​ ● State生命周期

​ ● 基础widget

​ ● DefaultTextStyle

​ ● FlutterLogo

​ ● Icon

​ ● Iamge.asset

​ ● CircleAvatar

​ ● FadeInImage

​ ● 按钮

​ ● FlatButton

​ ● OutlineButton

​ ● TextFormField

img

由于文章内容比较多,篇幅有限,资料已经被整理成了PDF文档,有需要 Flutter技术解析与实战 完整文档的可以加微信 即可免费领取!

《Flutter入门与实战》

目录

img

img

img

img

第一章、Flutter基本功能

​ ● 我的第一个 Flutter 应用之旅

​ ● 容器的盒子模型

​ ● 构建一个常用的页面框架

​ ● 设置 App 的主色调与字体

​ ● 来一个图文并茂的列表

​ ● 给列表增加下拉刷新和上滑加载更多功能

​ ● 使用cached_network_image 优化图片加载体验

​ ● 仿一个微信价值几个亿的页面

​ ● 开发一个常用的登录页面

​ ● 封装一个通用的文本输入框

​ ● 底部弹窗ModelBottomSheet详解

​ ● 利用CustomScrollView实现更有趣的滑动效果

​ ● 底部弹窗如何实现多项选择?

img

第二章、Flutter路由管理

​ ● App页面路由及路由拦截实现

​ ● 路由参数处理

​ ● 初识 fluro 路由管理

​ ● 使用 fluro 的转场动画提高页面切换体验

​ ● 使用自定义转场动画实现个性化页面切换

​ ● 此路是我开,此树是我栽。若是没权限,403到来

​ ● Flutter 2.0的路由把我搞蒙了

​ ● 山路十八弯的2.0路由

img

第三章、Flutter网络请求插件Dio

​ ● 初次见面,网络请求王者之dio

​ ● 利用 Dio请求删除数据

​ ● 使用 Dio的 Patch请求完成详情编辑

​ ● 使用 Post 请求增加动态

​ ● 一文搞定图片选择及图片上传

​ ● 使用 GetIt 同步不同页面间数据

​ ● Dio 封装之金屋藏娇

​ ● Dio 之拦截器

​ ● Dio之戛然而止

​ ● 从源码深入了解Dio 的

​ ● 小伙子,你买票了吗?

​ ● 手写一个持久化的

​ ● Dio之文件下载

​ ● Dio 篇章总结

img

第四章、Flutter状态管理

​ ● 基础原理篇

​ ● Provider篇

​ ● Redux篇

​ ● Mobx篇

​ ● Getx篇

​ ● BLOC篇

​ ● 状态管理系列大汇总

img

第五章、Flutter 动画

​ ● 使用 Animation 构建爱心三连动画

​ ● 让你的组件拥有三维动效

​ ● 小姐姐渐现效果 ——AnimatedOpacity 使用

​ ● 使用 AnimatedBuilder分离组件和动画,实现动效复用

Flogs:在Flutter中开发的高级日志记录框架,可提供快速简单的日志记录解决方案 FLogs高级日志记录框架 FLog是在flutter中开发的高级日志记录框架,可提供快速简单的日志记录解决方案。 所有日志均保存到数据库中,然后可以将其导出为zip文件。 概述 FLogs是用Dart写的。 它基本上具有两种类型的记录器(FLog和DataLog),并具有记录所需的许多高级功能。 日志保存在数据库中,然后可以将其导出到Android | iOS设备的文档目录中。 当开发人员想要分析应用程序内的用户活动时,日志非常有用。 这些日志可以轻松过滤和排序。 可以根据过滤器类型轻松地将日志导出为zip文件,然后可以将该zip文件上传到服务器或在本地使用。 很多时候,我们希望记录数据集以分析某些活动,例如位置(GPS坐标),设备信息,网络请求等。这有助于我们快速识别和修复在应用程序投入生产时难以调试的问题。 FLog提供了将数据集记录到数据库中的功能。 然后可以通过应用不同的便捷过 立即下载

相关推荐

Java 日志工具 LogUtil 源码 不依赖第三方jar包

Java 日志工具 LogUtil 源码 不依赖第三方jar包

Flutter (八)Log 工具类

Flutter log 工具类

qq_42795723的博客 1579

数字设计实验:自动售货机

本文旨在介绍数字设计实验:自动售货机的设计思路和实现代码

2401_83740084的博客 2228

Flutter 自定义调试Log

使用自定义Log打印调试信息,在debug时开启,release时关闭。

征战无悔人生 867

本地AI代码助手:智能解析大型代码库,实现自动化重构与理解

在软件开发中,大型代码库的理解与维护是常见挑战。传统方法依赖人工阅读和有限上下文工具,效率低下。通过结合检索增强生成(RAG)和多智能体(Multi-Agent)架构,现代AI工具能够智能解析代码结构,理解模块关系,并精准定位相关代码片段。这种技术不仅能提升代码探索效率,还能自动化执行重构、文档生成等任务,在遗留系统维护、快速上手新项目等场景中发挥关键作用。本文以blackboxaicode/cli为例,展示了如何利用其代码感知和Workflow Automation能力,高效处理复杂代码库。

weixin_30240349的博客 366

让 ABAP 复杂内表优雅“喂饱”:Complex Internal Table Filling 全面实战指南

本文为ABAP开发者系统梳理了复杂内表的填充与复制方法。重点介绍了work area的两种声明方式、APPEND操作的多形态用法及其初始值语义,以及VALUE #( )构造器的逐行与整表写法。同时讲解了CORRESPONDING的同名字段映射功能,帮助开发者在"航司与航线"等复杂业务场景中实现最佳实践。文章还涵盖内存优化、可读性建议和常见误区,提供可运行样例,适用于ABAP On-Premise和SAP BTP环境。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 169

flutter 日志输出,Flutter打印日志,flutter log,flutter 真机日志

flutter 提供了 print(Object object) 来向开发工具的控制 台输出日志信息 print("test"); common_utils 工具类已经将pring 封装为工具类 common_utils: ^1.1.1 使用common_utils工具类中的LogUtil //初始化设置 LogUtil LogUtil.init(true); //输出日志 LogUtil....

代码小疯子 5万+

FLUENT Meshing Watertight Geometry工作流入门 - 4 局部加密区域

总结一下本课程中学到的内容:我们了解了如何使用模型中特定兴趣区域的BOI和FOI局部尺寸控制来加密网格。然后,了解了如何使用Box类型的局部细化区域。最后,学习了Ansys Fluent Meshing Watertight Geometry Workflow的Offset Surface类型创建局部细化区域任务。

B_etter_Man的博客 6647

Flutter 常用工具类库common_utils

地址:https://pub.flutter-io.cn/packages/common_utils#-readme-tab- Dart常用工具类库common_utils 1、TimelineUtil : 时间轴.(新)2、TimerUtil : 倒计时,定时任务.(新)3、MoneyUtil : 精确转换,元转分,分转元,支持格式输出.(新)4、LogUtil : 简单封装打印...

aau88497的博客 4397

RK3588 开启多窗口

这样就支持多窗口了。

tzj1058756809的博客 268

【WorkBuddy从入门到精通实战教程】使用手册 第 1 章 初识 WorkBuddy

腾讯推出全场景AI办公助手WorkBuddy,突破传统聊天机器人局限,实现从需求理解到结果交付的全流程自动化。该工具通过自然语言交互,可自主规划执行复杂任务,包括文件处理、数据分析、PPT制作等,支持多模型切换和技能扩展。内置权限管控机制确保本地文件操作安全,显著降低人工切换工具的成本,提升职场效率。

企鹅码农的专属播客,分享10多年的实战案例和经验解读,包含YOLO、RT-DETR、AI等各个工作领域。 1344

探索Dart与Flutter的强大工具库:common_utils 与 flustars

在现代软件开发中,高效和可靠的工具库是提升开发效率和代码质量的关键。今天,我们将深入介绍一个在Dart和Flutter社区中广受欢迎的开源项目——`common_utils`和`flustars`。这两个工具库提供了丰富的功能,旨在简化开发流程,提升代码的可维护性和性能。 ## 项目介绍 `common_utils`是一个Dart语言的常用工具类库,它包含了日期处理、正则表达式、倒计时、时间轴...

gitblog_00375的博客 577

第 9 篇:TMC2240 速度调节实战|STEP 信号生成 + 平滑调速算法(无抖动)

摘要:本文详细介绍了TMC2240电机调速方案,重点解决调速时电机抖动、丢步等问题。核心原理是通过STEP信号频率控制转速,结合平滑加减速算法避免电流冲击。文章从速度与STEP频率的数学关系入手,提供了定时器生成固定频率STEP信号的实现代码,并给出不同转速下的频率参数表。针对42步进电机+256微步细分场景,详细讲解了定时器初始化、中断处理及固定转速设置方法,帮助开发者实现稳定调速。全文包含可直接复用的代码片段,适合嵌入式开发者快速实现电机平滑调速功能。

BackCatK Chen的博客 1035

Flutter项目常用公共库

OpenGit_Flutter项目常用公共库 OpenGit公共库目前包含三部分 基本UI库(flutter_base_ui); 常用工具类库(flutter_common_util); 安装APK插件; 基本UI库 BloC和MVP基本封装 详情可以参考文章MVC、MVP、BloC、Redux四种架构在Flutter上的尝试 基本Style的提供 主要颜色,如下面代码所示 class YZC...

Yuzopro的博客 985

3D 韩系风格漫剧人设 | GPT-Image2 人设生成【附完整提示词】

宁姚同款清冷剑系美人,极致小巧鹅蛋脸,下颌线紧致纤细流畅,下巴小巧精致收窄,小脸衬得五官格外舒展,韩式水光肌,面部精细水光高光,鼻梁高光、鼻尖高光、面中苹果肌高光、唇珠高光,红棕色长卷发偏分大波浪,垂在一侧肩前,发量蓬松。发量丰厚蓬松有体积感,不贴头皮,几缕碎发自然垂落脸侧修饰脸型,长度过胸及背,发丝柔顺丝滑卷度自然,发尾自然垂落,身穿香槟色缎面挂脖长裙,挂脖设计无袖露肩露背,上半身围裹式荡领胸口垂褶,腰部堆褶收腰,修身直筒包臀裙长度到脚踝,缎面光泽感强垂坠感好,极简剪裁。纯白色背景,3D漫剧写实厚涂风。

欢迎来到鸾姝淡月博客,专为程序员和编程爱好者打造的技术博客。我们分享编程语言趋势、开发工具、编程哲学、职业发展等,助你掌握新技术,优化技能,激发编程热情。 146

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

日志框架:支持分级、格式化输出的日志库(232)

在鸿蒙(HarmonyOS)原生应用开发中,日志是监控运行状态、追踪业务流程和排查线上问题的核心工具。直接使用原生的console打印存在诸多痛点,如高频打印阻塞主线程、敏感信息泄露、日志顺序混乱以及崩溃后无法本地留存等。为此,开发者通常会基于鸿蒙官方的HiLog或console进行二次封装,打造支持分级、格式化、隐私脱敏及异步写入的日志框架。

Davina_yu的博客 388

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Dart 常用工具类库common_utils(新)

Dart常用工具类库 common_utils. 如果你有好的工具类欢迎PR. Dart常用工具类库 common_utils 1、TimelineUtil : 时间轴.(新) 2、TimerUtil : 倒计时,定时任务.(新) 3、MoneyUtil : 精确转换,防止精度丢失,分元互转,支持格式输出.(新) 4、LogUtil : 简单封装打印日志.(新) 5、DateUtil : 日期...

油焖茄子 的博客 3768

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

Android 性能优化【篇六:滑动卡帧分析流程】

本篇主要围绕Android性能优化,滑动卡帧这类问题为主题,先结网上已有的一些资料阐述滑动出帧流程,然后结合MTK的分析思路,其他厂商的思路,进行说明。首先需要注意一下,我对这块了解的其实不多,本篇属于单纯的摘抄、归纳、总结。PS:本人对这块不是很熟,所以本篇内容基本上都是摘抄各位大佬的资料在学习滑动卡帧类问题分析思路之前,需要先对Android Display显示模块的功能原理进行解读,我对这块不是很熟,这里主要引用整理网上的一些大神的资料。

qq_27672101的博客 339

Flutter _ 日志还能这么打印,太秀了!

logger是一个纯 dart 语言编写的日志打印库,不依赖于特定平台。它非常轻巧且可扩展非常强,打印出来的日志特别的漂亮,它完美的实现了堆栈信息的自定义打印,多样的打印器、过滤器。你可以将日志打印到控制台,输出到文件,临时保存到内存中等。我使用这个日志打印组件已经有一段时间了,整体感觉非常的稳定,我特别喜欢它可以打印出方法的堆栈信息,其次作为一个有些许颜控的人,它打印出的日志格式和颜色我都非常喜欢。这个组件的作者是居住在德国慕尼黑的一个小伙,他说这个组件的灵感来源于 Android 平台的日志组件。

2401_89284288的博客 1582

工具篇——LogUtil(控制日志打印)

写代码的四点: 1.明确需求。要做什么? 2.分析思路。要怎么做?(1,2,3……) 3.确定步骤。每一个思路要用到哪些语句、方法和对象。 4.代码实现。用具体的语言代码将思路实现出来。学习新技术的四点: 1.该技术是什么? 2.该技术有什么特点?(使用需注意的方面) 3.该技术怎么使用?(写Demo) 4.该技术什么时候用?(在Pr...

一个灵活的胖子MrWang 8145

flutter 日志功能(十一)

如要使用flutter官方的日志类,需要添加对该库的依赖: dependencies: flutter: sdk: flutter logger: ^1.0.0 在使用的类中,添加对logger包的引用: import 'package:logger/logger.dart'; 对logger类的简单使用: var logger = Logger(); logger.d("debug messages!"); 不同级别日志的输出: ///详细日志 logger.v("V

genispan的专栏 6007

Flutter logger 日志

其实读者只要指定打印的方法栈个数足够大,就可以看到了。我使用这个日志打印组件已经有一段时间了,整体感觉非常的稳定,我特别喜欢它可以打印出方法的堆栈信息,其次作为一个有些许颜控的人,它打印出的日志格式和颜色我都非常喜欢。打印日志在项目中是全局的,为了能在项目中任意地方使用打印功能,最好封装一下,如下是一个简单的封装,Logger 只需要实例化一次,之后在项目中任何地方都可以调用各个级别的打印方法。它非常轻巧且可扩展非常强,打印出来的日志特别的漂亮,它完美的实现了堆栈信息的自定义打印,多样的打印器、过滤器。

shelutai的博客 1663
上一篇: 2023最新Android大厂高频面试题(包含答案解析免费分享)
下一篇: 一分钟带你了解Flutter
不秃头de程序猿
博客等级 码龄5年 4089粉丝 1478原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值