Flutter 百题斩#16 | 收集 SDK 所有 Widget 组件基本信息


最近在着手开发我的 《匠心星问》 ,它定位是一款 题库 应用,将集题目浏览、发布、解答、做题为一体。打算第一步先以 Flutter 为核心,准备题库资源。于是诞生《每日一题》 系列,准备精心设计一些 Flutter 的问题与解答,作为题库的养料。

接下来的几题,将从一个需求逐步演进,让答题者给出方案设计。上一题已经小试牛刀,完成了所有 StatelessWdiget 组件名称的提取。本题将更进一步,焦点是探讨:

收集 SDK 所有 Widget 组件基本信息

请你设计一套方案,通过 dart 的命令行脚本,分析并提取 Flutter SDK 中 所有的 Widget 的派生类信息。每个组件的信息如下,包括名称、所在路径、超类列表、组件描述信息。


1. 组件数据

组件数据基于题目中的 json 数据,可以定义如下的 WidgetPo类承载解析后的数据。

dart 复制代码
class WidgetPo {
  final String name;
  final String path;
  final bool isAbstract;
  final String? desc;

  WidgetPo({
    required this.name,
    required this.path,
    required this.parents,
    required this.desc,
  });

  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'path': path,
      'parents': parents,
      'desc': desc,
    };
  }
}

2. 解析器 WidgetVisitor

上一题的这个流程就不赘述了,核心在于通过继承 RecursiveAstVisitor 实现解析器。其中可以校验一下当父类列表中包含 Widget,则表示该类型继承自 Widget, 这样就可以轻而易举地得到所有 Widget 的派生类。是不是非常简单。

如果按上一题,将名称打印出来,我们就可以得到 Flutter 源码中的所有组件名称。目前 Flutter 3.27.4 中非下划线的组件一共 555 个。

dart 复制代码
class WidgetVisitor extends RecursiveAstVisitor<void> {
  final String filePath;

  WidgetVisitor(this.filePath);

  final List<WidgetPo> components = [];

  @override
  void visitClassDeclaration(ClassDeclaration node) {
    List<InterfaceType>? types = node.declaredFragment?.element.allSupertypes;
    String target = 'Widget';
    bool? hit = types?.where((e) => e.element3.name3 == target).isNotEmpty;
    if (hit ?? false) {
      components.add(parserWidgetClass(node, types));
    }
    super.visitClassDeclaration(node);
  }

  WidgetPo parserWidgetClass(ClassDeclaration node,List<InterfaceType>? types){
    /// TODO 解析组件类信息
  }
}

上面的 WidgetVisitor 中维护了 List<WidgetPo> 列表,并定义了 parserWidgetClass 方法解析必要的字段,创建 WidgetPo 对象。然后在类是 Widget 生类时,触发 parserWidgetClass 添加到结果列表里。


3. 解析类信息并输出

最后,最需要的就是完成 parserWidgetClass 方法:

  • 这里超类只统计到 Widget 为止,否则会有很多冗余数据。毕竟 Widget 之上的父类都是一样的。
  • 通过 documentationComment 方法可以得到注释信息,每一行是一个 Token 对象。一般来说,组件源码的第一句,都会简单扼要地介绍这个组件,所有用它作为描述信息非常适合。
dart 复制代码
WidgetPo parserWidgetClass(ClassDeclaration node,List<InterfaceType>? types){
  final String className = node.name.lexeme;
  List<String> parents = [];
  for (InterfaceType type in types ?? []) {
    String? name = type.element3.name3;
    if (name != null) {
      parents.add(name);
    }
    if (name == 'Widget') {
      break;
    }
  }
  
  List<Token> tokens = node.documentationComment?.tokens??[];
  String desc = '';
  for(Token token in tokens){
    String value = token.lexeme;
    if(value=='///'){
      break;
    }
    desc+=value.replaceAll('///', '');
  }
  
  return WidgetPo(
    name: className,
    path: filePath,
    desc: desc,
    parents: parents,
  );
}

4. 成果与输出

最后将所有解析的 WidgetPo 列表按照名称排序,然后通过 json 编码成字符串,就可以得到 史上最全 组件信息数据。当你自己跑出来这些这些数据时,或许也会和我一样非常兴奋和开心。有了整理分析的手段,借助这些数据,我们就可以做出很多有趣的统计、展示。这也是后续的挑战 ~

dart 复制代码
components.sort((a, b) => a.name.compareTo(b.name));
File outfile = File('Widget.json');
await outfile.writeAsString(jsonEncode(components));
print('Results saved to: ${outfile.path}');

5. 小结

本题我们继续接触 基于 ClassDeclaration 统计信息 ,完成了所有组件列表基本数据的抓取工作。《匠心星问》系列题库设计的初衷:不仅是"会不会用",而是鼓励开发者去思考 "怎么做才更合理、更系统" 。希望你在解决本题的过程中,收获的不只是 Flutter 技术本身,更是一种解决问题的方式。

后续的《每日一题》也将持续围绕实战问题展开,欢迎持续关注和参与,一起构建一个 开放、精致、有深度 的 Flutter 题库生态!

你是否也有想出的题目,欢迎投稿加入《匠心星问》共同打造!


如果你有其他的看法,或者有什么想要的题目、或者想提供题目和答案,都欢迎在评论区留言。更多文章和视频知识资讯,大家可以关注我的公众号、掘金和 B 站 。

相关推荐
worxfr1 小时前
Flutter 入门指南:从基础到实战
flutter
yuanlaile1 小时前
Flutter Android打包学习指南
android·flutter·flutter打包·flutter android
教程分享大师2 小时前
中兴B860AV5.1-M2_S905L3SB最新完美版线刷包 解决指示灯异常问题
android
2501_915918412 小时前
iOS 性能监控工具全解析 选择合适的调试方案提升 App 性能
android·ios·小程序·https·uni-app·iphone·webview
冉冉同学2 小时前
【HarmonyOS NEXT】解决Repeat复用导致Image加载图片展示的是上一张图片的问题
android·前端·客户端
努力学习的小廉2 小时前
深入了解linux系统—— 信号的捕捉
android·linux·运维
0wioiw03 小时前
Flutter基础(前端教程①①-底部导航栏)
flutter
tomly20204 小时前
【小米训练营】C++方向 实践项目 Android Player
android·开发语言·c++·jni
你过来啊你4 小时前
RecyclerView与ListView深度对比分析
android
_一条咸鱼_4 小时前
Android Runtime内存访问越界检查源码解析(82)
android·面试·android jetpack