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

相关推荐
安东尼肉店19 分钟前
Android compose屏幕适配终极解决方案
android
2501_9160074733 分钟前
HTTPS 抓包乱码怎么办?原因剖析、排查步骤与实战工具对策(HTTPS 抓包乱码、gzipbrotli、TLS 解密、iOS 抓包)
android·ios·小程序·https·uni-app·iphone·webview
feiyangqingyun2 小时前
基于Qt和FFmpeg的安卓监控模拟器/手机摄像头模拟成onvif和28181设备
android·qt·ffmpeg
用户2018792831676 小时前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子6 小时前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜82276 小时前
安卓接入Max广告源
android
齊家治國平天下6 小时前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO6 小时前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel6 小时前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢6 小时前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱