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 站 。

相关推荐
liang_jy1 天前
Android SparseArray
android·源码
liang_jy1 天前
Activity 启动流程扩展篇(一)—— startActivityInner 任务决策全解析
android·源码
jiejiejiejie_1 天前
Flutter for OpenHarmony 心情日记功能实战指南
flutter·华为
jiejiejiejie_1 天前
Flutter for OpenHarmony 倒计时功能实战开发
flutter
NPE~1 天前
[App逆向]脱壳实战
android·教程·逆向·android逆向·逆向分析
木易 士心1 天前
别再只会用 drawCircle 了!一文搞懂 Android Canvas 底层机制
android
Math_teacher_fan1 天前
Flutter 跨平台开发实战:鸿蒙与音乐律动艺术(六)、Lissajous 利萨茹曲线:频率耦合的轨迹艺术
flutter·ui·数学建模·华为·harmonyos·鸿蒙系统
里欧跑得慢1 天前
17. Flutter Hero动画实现:让界面过渡更加优雅
前端·css·flutter·web
liulian09161 天前
Flutter for OpenHarmony 跨平台开发:秒表功能实战指南
flutter
AtOR CUES1 天前
MySQL——表操作及查询
android·mysql·adb