认识 Dart 语言检查器
在运行应用程序前,使用 Dart 语言检查器,通过分析代码,可以帮助开发者排除一些代码隐患。 当然,如果读者使用的是 Android Studio,Dart 检查器在默认情况下会自动启用。 若要手动测试代码,可以在工程根目录下执行:flutter analyze
命令,检查结果会稍后显示在命令行对话框中。 比如,在默认新建的计数器应用中,去掉一个语句结尾的分号:
void _incrementCounter() {
setState(() {
_counter++
});
}
看到 _counter++ 后面少了一个分号了吗?此时,运行 Dart 检查器,命令行输出:
error - Expected to find ';' - lib\main.dart:32:15 - expected_token
1 issue found. (ran in 8.9s)
如何在 IDE 中进行单步调试
在某些时候,我们需要进行单步调试。单步调试可以让程序逐条语句地进行,并可以看到当前运行的位置。另外,在单步调试过程中,还能实时关注相应范围内所有变量值的详细变化过程。
Android Studio 中提供了单步调试功能。这和开发原生 Android 平台 App 时的单步调试方法一样,其具体步骤可以分为三步进行,第一步是标记断点,第二步是运行程序到断点处,第三步则是使用 Debug 工具进行调试。
下面以默认的计数器应用为例,观察代码中 _counter 值的变化,体会单步调试的全过程。
第一步是标记断点,既然要观察 _counter 值的变化,则在每次 _counter 值发生变化后添加断点,观察数值变化是最理想的,因此在行号稍右侧点击鼠标,把断点加载下图所示的位置。
添加断点后,相应的行号右侧将会出现圆形的断点标记,并且整行将会高亮显示。
到此,断点就添加好了,当然,还可以同时添加多个断点,以便实现多个位置的调试。
接下来则是运行程序。和之前的运行方式不同,这一次需要以调试模式启动 App。方法是点击 Android Studio 上方工具栏的小虫子图标,如下图所示: 稍等片刻,程序就启动了。由于我们添加断点的位置在程序启动后会被立即运行到,因此,无需其他操作,即可进入调试视图。如果断点位置并不是在程序一启动就执行,则需要手动让程序运行到断点位置。我们可以在任何时候退出调试模式,只需点击停止运行按钮即可,它位于启动调试模式按钮的右侧。
点击该按钮后,Android Studio 会退出调试模式,运行在设备上的程序也会被强制关闭。
打印 Log 的技巧
为了跟踪和记录软件的运行情况,开发者们通常会输出一些日志 (Log),这些日志对于用户而言是不可见的。传统的 iOS 和 Android 平台都提供了完善的日志输出功能,Flutter 也不例外。要实时查看 Flutter 的日志,只需在控制台中输入:
flutter logs
即可。Android Studio 和 Visual Studio Code 中,默认集成了控制台 (console),使用这个集成的控制台或者启动一个新的控制台皆可。这里要注意的是,一旦执行了上面的命令,该控制台将会进入独占状态,即无法再使用其他的命令了,除非中断 Log 查看。
当我们想要在程序运行得某个地方输出 Log 时,通常使用 debugPrint() 方法。结合之前的示例,修改原先的 main() 方法,添加一个 Log 输出,内容为 "我开始启动了",未经修改的代码:
void main() => runApp(MyApp());
添加 Log 后的代码:
void main() { debugPrint("我开始启动了"); runApp(MyApp()); }
在控制台中使用 flutter logs 命令监视 Log 输出,然后重新安装并运行程序,控制台输出:
I/flutter (12705): 我开始启动了
结果如图所示:
要结束监视 Log 输出,可使用 Control + C 组合键,然后输入 y,回车确认,也可直接关闭控制台。最后,需要注意的是,为了保证 Log 输出正确无误,建议各位读者使用英文输出,而不是直接使用中文。因为在某些情况下,可能会导致显示乱码。经测试,在英文版的 Windows 下启动命令提示符,并执行上例,会得到如下输出:
I/flutter (13320): 我开始å¯åŠ¨äº†
利用 Dart 语言中的 "断言"
Dart 运行时提供两种运行方式: Production 和 Checked。默认情况下会以 Production 模式运行,在这种条件下,优先考虑性能,关闭类型检查和断言;反之,Checked 模式更利于开发阶段调试使用。
断言可以检查程序中某些可能出现的运行时逻辑错误。比如下面这段代码:
// assert
var intValue = 300;
assert(intValue == 299);
很明显,intValue 不满足和 299 相等的条件,此时在开发环境中运行程序,将看到控制台报错。而一旦切换到生产模式,则不会收到任何错误提示。这对于检查代码中某些隐含的逻辑问题十分有效。
如何查看界面 Widget 树形层级
Flutter 框架中的每一层都提供了转储当前状态或事件的方式,这些转储的信息将通过 debugPrint() 方式输出到控制台上。
组件层
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
RaisedButton(
onPressed: () => debugDumpApp(),
child: Text("Create app dump")),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
I/flutter ( 4489): WidgetsFlutterBinding - CHECKED MODE
I/flutter ( 4489): [root](renderObject: RenderView#d27b1)
I/flutter ( 4489): └MyApp
I/flutter ( 4489): └MaterialApp(state: _MaterialAppState#51668)
I/flutter ( 4489): └ScrollConfiguration(behavior: _MaterialScrollBehavior)
I/flutter ( 4489): └WidgetsApp-[GlobalObjectKey _MaterialAppState#51668](state:
_WidgetsAppState#04e30)
I/flutter ( 4489): └MediaQuery(MediaQueryData(size: Size(411.4, 797.7), devicePixelRatio: 2.6,
textScaleFactor: 1.1, platformBrightness: Brightness.light, padding: EdgeInsets(0.0, 24.0, 0.0, 0.0),
viewInsets: EdgeInsets.zero, alwaysUse24HourFormat: true, accessibleNavigation:
falsedisableAnimations: falseinvertColors: falseboldText: false))
I/flutter ( 4489): └Localizations(locale: en_US, delegates:
[DefaultMaterialLocalizations.delegate(en_US), DefaultCupertinoLocalizations.delegate(en_US),
DefaultWidgetsLocalizations.delegate(en_US)], state: _LocalizationsState#c0c98)
I/flutter ( 4489): └Semantics(container: false, properties: SemanticsProperties, label: null, value:
null, hint: null, textDirection: ltr, hintOverrides: null, renderObject: RenderSemanticsAnnotations#31c77)
I/flutter ( 4489): └_LocalizationsScope-[GlobalKey#60b05]
I/flutter ( 4489): └Directionality(textDirection: ltr)
I/flutter ( 4489): └Title(title: "Flutter Demo", color: MaterialColor(primary value:
Color(0xff2196f3)))
I/flutter ( 4489): └CheckedModeBanner("DEBUG")
I/flutter ( 4489): └Banner("DEBUG", textDirection: ltr, location: topEnd, Color(0xa0b71c1c),
text inherit: true, text color: Color(0xffffffff), text size: 10.2, text weight: 900, text height: 1.0x, dependencies:
[Directionality])
I/flutter ( 4489): └CustomPaint(renderObject: RenderCustomPaint#c2a34)
I/flutter ( 4489): └DefaultTextStyle(debugLabel: fallback style; consider putting your text in a
Material, inherit: true, color: Color(0xd0ff0000), family: monospace, size: 48.0, weight: 900, decoration:
double Color(0xffffff00) TextDecoration.underline, softWrap: wrapping at box width, overflow: clip)
I/flutter ( 4489): └Builder(dependencies: [MediaQuery])
……
RaisedButton(dependencies: [_LocalizationsScope-[GlobalKey#60b05], _InheritedTheme])
└RawMaterialButton(dirty, state: _RawMaterialButtonState#fe2da)
可见,它就是为了执行 debugDumpApp() 方法而增加的按钮。
由上一小节得知组件层提供了各个组件的详情信息。但某些时候,这些信息并不完全够使用,此时可以调用 debugDumpRenderTree() 方法转储渲染层。
基于上小节的示例,继续添加一个按钮,其操作就是触发 debugDumpRenderTree() 方法。如下:
RaisedButton(
onPressed: () => debugDumpRenderTree(),
child: Text("Create render tree dump"))
程序运行后,单击这个按钮,观察控制台输出 (节选):
I/flutter ( 7255): RenderView#7e860
I/flutter ( 7255): │ debug mode enabled - android
I/flutter ( 7255): │ window size: Size(1080.0, 2094.0) (in physical pixels)
I/flutter ( 7255): │ device pixel ratio: 2.6 (physical pixels per logical pixel)
I/flutter ( 7255): │ configuration: Size(411.4, 797.7) at 2.625x (in logical pixels)
I/flutter ( 7255): │
I/flutter ( 7255): └─child: RenderSemanticsAnnotations#62d7d
I/flutter ( 7255): │ creator: Semantics ← Localizations ← MediaQuery ←
I/flutter ( 7255): │ WidgetsApp-[GlobalObjectKey _MaterialAppState#d0498] ←
I/flutter ( 7255): │ ScrollConfiguration ← MaterialApp ← MyApp ← [root]
I/flutter ( 7255): │ parentData:
I/flutter ( 7255): │ constraints: BoxConstraints(w=411.4, h=797.7)
I/flutter ( 7255): │ size: Size(411.4, 797.7)
I/flutter ( 7255): │
I/flutter ( 7255): └─child: RenderCustomPaint#e2d03
I/flutter ( 7255): │ creator: CustomPaint ← Banner ← CheckedModeBanner ← Title ←
I/flutter ( 7255): │ Directionality ← _LocalizationsScope-[GlobalKey#6be84] ←
I/flutter ( 7255): │ Semantics ← Localizations ← MediaQuery ←
I/flutter ( 7255): │ WidgetsApp-[GlobalObjectKey _MaterialAppState#d0498] ←
I/flutter ( 7255): │ ScrollConfiguration ← MaterialApp ← ⋯
I/flutter ( 7255): │ parentData: (can use size)
I/flutter ( 7255): │ constraints: BoxConstraints(w=411.4, h=797.7)
I/flutter ( 7255): │ size: Size(411.4, 797.7)
I/flutter ( 7255): │
I/flutter ( 7255): └─child: RenderPointerListener#9b873
I/flutter ( 7255): │ creator: Listener ← Navigator-[GlobalObjectKey
I/flutter ( 7255): │ _WidgetsAppState#74612] ← IconTheme ← IconTheme ←
I/flutter ( 7255): │ _InheritedCupertinoTheme ← CupertinoTheme ← _InheritedTheme ←
I/flutter ( 7255): │ Theme ← AnimatedTheme ← Builder ← DefaultTextStyle ←
I/flutter ( 7255): │ CustomPaint ← ⋯
I/flutter ( 7255): │ parentData: (can use size)
I/flutter ( 7255): │ constraints: BoxConstraints(w=411.4, h=797.7)
I/flutter ( 7255): │ size: Size(411.4, 797.7)
I/flutter ( 7255): │ behavior: deferToChild
I/flutter ( 7255): │ listeners: down, up, cancel
I/flutter ( 7255): │
I/flutter ( 7255): └─child: RenderAbsorbPointer#52153
I/flutter ( 7255): │ creator: AbsorbPointer ← Listener ←
……
这段节选依然比实际输出少很多。
不过,这些转储信息,通常只关注 size 和 constrains 参数就可以了。因为它们表示了大小和约束条件。此外,针对盒约束,还有可能存在 relayoutSubtreeRoot,它表示有多少父控件依赖该组件的尺寸。
如果要调试有关合成的问题,就需要转储层级关系的信息。转储层级关系的方法是 debugDumpLayerTree()。我们继续添加一个按钮,其操作就是触发 debugDumpLayerTree() 方法。如下:
RaisedButton(
onPressed: () => debugDumpLayerTree(),
child: Text("Create layer tree dump"))
运行,并点击该按钮,得到控制台输出:
I/flutter (10050): TransformLayer#256a4
I/flutter (10050): │ owner: RenderView#6c917
I/flutter (10050): │ creator: [root]
I/flutter (10050): │ offset: Offset(0.0, 0.0)
I/flutter (10050): │ transform:
I/flutter (10050): │ [0] 2.625,0.0,0.0,0.0
I/flutter (10050): │ [1] 0.0,2.625,0.0,0.0
I/flutter (10050): │ [2] 0.0,0.0,1.0,0.0
I/flutter (10050): │ [3] 0.0,0.0,0.0,1.0
I/flutter (10050): │
I/flutter (10050): ├─child 1: OffsetLayer#03fdc
I/flutter (10050): │ │ creator: RepaintBoundary ← _FocusScopeMarker ← Semantics ←
I/flutter (10050): │ │ FocusScope ← PageStorage ← Offstage ← _ModalScopeStatus ←
I/flutter (10050): │ │
_ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#ea0b8]
I/flutter (10050): │ │ ← _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#d7b44] ←
I/flutter (10050): │ │ Stack ← _Theatre ←
I/flutter (10050): │ │ Overlay-[LabeledGlobalKey#404b4] ← ⋯
I/flutter (10050): │ │ offset: Offset(0.0, 0.0)
I/flutter (10050): │ │
I/flutter (10050): │ └─child 1: OffsetLayer#9ca96
I/flutter (10050): │ │ creator: RepaintBoundary-[GlobalKey#71e3e] ← IgnorePointer ←
I/flutter (10050): │ │ FadeTransition ← FractionalTranslation ← SlideTransition ←
I/flutter (10050): │ │ _FadeUpwardsPageTransition ← AnimatedBuilder ← RepaintBoundary
I/flutter (10050): │ │ ← _FocusScopeMarker ← Semantics ← FocusScope ← PageStorage ← ⋯
I/flutter (10050): │ │ offset: Offset(0.0, 0.0)
I/flutter (10050): │ │
I/flutter (10050): │ └─child 1: PhysicalModelLayer#9986e
I/flutter (10050): │ │ creator: PhysicalModel ← AnimatedPhysicalModel ← Material ←
I/flutter (10050): │ │ PrimaryScrollController ← _ScaffoldScope ← Scaffold ←
I/flutter (10050): │ │ MyHomePage ← Semantics ← Builder ←
I/flutter (10050): │ │ RepaintBoundary-[GlobalKey#71e3e] ← IgnorePointer ←
I/flutter (10050): │ │ FadeTransition ← ⋯
I/flutter (10050): │ │ elevation: 0.0
I/flutter (10050): │ │ color: Color(0xfffafafa)
I/flutter (10050): │ │
I/flutter (10050): │ ├─child 1: PictureLayer#1f44b
I/flutter (10050): │ │ paint bounds: Rect.fromLTRB(0.0, 0.0, 411.4, 797.7)
I/flutter (10050): │ │
I/flutter (10050): │ ├─child 2: PhysicalModelLayer#e486c
I/flutter (10050): │ │ │ creator: PhysicalShape ← _MaterialInterior ← Material ←
I/flutter (10050): │ │ │ ConstrainedBox ← _InputPadding ← Semantics ← RawMaterialButton
I/flutter (10050): │ │ │ ← RaisedButton ← Column ← Center ← MediaQuery ←
I/flutter (10050): │ │ │ LayoutId-[<_ScaffoldSlot.body>] ← ⋯
I/flutter (10050): │ │ │ elevation: 2.0
I/flutter (10050): │ │ │ color: Color(0xffe0e0e0)
I/flutter (10050): │ │ │
I/flutter (10050): │ │ └─child 1: PictureLayer#225de
I/flutter (10050): │ │ paint bounds: Rect.fromLTRB(130.2, 403.9, 281.2, 439.9)
I/flutter (10050): │ │
I/flutter (10050): │ ├─child 3: PhysicalModelLayer#f4d9a
I/flutter (10050): │ │ │ creator: PhysicalShape ← _MaterialInterior ← Material ←
I/flutter (10050): │ │ │ ConstrainedBox ← _InputPadding ← Semantics ← RawMaterialButton
I/flutter (10050): │ │ │ ← RaisedButton ← Column ← Center ← MediaQuery ←
I/flutter (10050): │ │ │ LayoutId-[<_ScaffoldSlot.body>] ← ⋯
I/flutter (10050): │ │ │ elevation: 2.0
I/flutter (10050): │ │ │ color: Color(0xffe0e0e0)
I/flutter (10050): │ │ │
I/flutter (10050): │ │ └─child 1: PictureLayer#c7baf
I/flutter (10050): │ │ paint bounds: Rect.fromLTRB(105.2, 451.9, 306.2, 487.9)
I/flutter (10050): │ │
I/flutter (10050): │ ├─child 4: PhysicalModelLayer#eb57b
I/flutter (10050): │ │ │ creator: PhysicalShape ← _MaterialInterior ← Material ←
I/flutter (10050): │ │ │ ConstrainedBox ← _InputPadding ← Semantics ← RawMaterialButton
I/flutter (10050): │ │ │ ← RaisedButton ← Column ← Center ← MediaQuery ←
I/flutter (10050): │ │ │ LayoutId-[<_ScaffoldSlot.body>] ← ⋯
I/flutter (10050): │ │ │ elevation: 2.0
I/flutter (10050): │ │ │ color: Color(0xffe0e0e0)
I/flutter (10050): │ │ │
I/flutter (10050): │ │ └─child 1: PictureLayer#2350d
I/flutter (10050): │ │ paint bounds: Rect.fromLTRB(111.2, 499.9, 300.2, 535.9)
I/flutter (10050): │ │
I/flutter (10050): │ ├─child 5: AnnotatedRegionLayer#a5e42
I/flutter (10050): │ │ │ value: {systemNavigationBarColor: 4278190080,
I/flutter (10050): │ │ │ systemNavigationBarDividerColor: null, statusBarColor: null,
I/flutter (10050): │ │ │ statusBarBrightness: Brightness.dark, statusBarIconBrightness:
I/flutter (10050): │ │ │ Brightness.light, systemNavigationBarIconBrightness:
I/flutter (10050): │ │ │ Brightness.light}
I/flutter (10050): │ │ │ size: Size(411.4, 80.0)
I/flutter (10050): │ │ │ offset: Offset(0.0, 0.0)
I/flutter (10050): │ │ │
I/flutter (10050): │ │ └─child 1: PhysicalModelLayer#32968
I/flutter (10050): │ │ │ creator: PhysicalModel ← AnimatedPhysicalModel ← Material ←
I/flutter (10050): │ │ │ AnnotatedRegion ← Semantics ← AppBar ←
I/flutter (10050): │ │ │ FlexibleSpaceBarSettings ← ConstrainedBox ← MediaQuery ←
I/flutter (10050): │ │ │ LayoutId-[<_ScaffoldSlot.appBar>] ← CustomMultiChildLayout ←
I/flutter (10050): │ │ │ AnimatedBuilder ← ⋯
I/flutter (10050): │ │ │ elevation: 4.0
I/flutter (10050): │ │ │ color: MaterialColor(primary value: Color(0xff2196f3))
I/flutter (10050): │ │ │
I/flutter (10050): │ │ └─child 1: PictureLayer#e562b
I/flutter (10050): │ │ paint bounds: Rect.fromLTRB(0.0, 0.0, 411.4, 80.0)
I/flutter (10050): │ │
I/flutter (10050): │ └─child 6: TransformLayer#4e3f3
I/flutter (10050): │ │ offset: Offset(0.0, 0.0)
I/flutter (10050): │ │ transform:
I/flutter (10050): │ │ [0] 1.0,2.4492935982947064e-16,0.0,-1.7053025658242404e-13
I/flutter (10050): │ │ [1] -2.4492935982947064e-16,1.0,0.0,1.1368683772161603e-13
I/flutter (10050): │ │ [2] 0.0,0.0,1.0,0.0
I/flutter (10050): │ │ [3] 0.0,0.0,0.0,1.0
I/flutter (10050): │ │
I/flutter (10050): │ └─child 1: PhysicalModelLayer#79c2c
I/flutter (10050): │ │ creator: PhysicalShape ← _MaterialInterior ← Material ←
I/flutter (10050): │ │ ConstrainedBox ← _InputPadding ← Semantics ← RawMaterialButton
I/flutter (10050): │ │ ← Semantics ← Listener ← RawGestureDetector ← GestureDetector ←
I/flutter (10050): │ │ Tooltip ← ⋯
I/flutter (10050): │ │ elevation: 6.0
I/flutter (10050): │ │ color: Color(0xff2196f3)
I/flutter (10050): │ │
I/flutter (10050): │ └─child 1: PictureLayer#0e8dc
I/flutter (10050): │ paint bounds: Rect.fromLTRB(339.4, 725.7, 395.4, 781.7)
I/flutter (10050): │
I/flutter (10050): └─child 2: PictureLayer#1ae80
I/flutter (10050): paint bounds: Rect.fromLTRB(0.0, 0.0, 1080.0, 2094.0)
I/flutter (10050):
怎样获取语义树
语义调试通常用于提供系统辅助功能的 App 中,当系统辅助功能开启时,系统会根据 App 提供的语义理解某个组件是做什么用的,或简单地表明组件的内容。 调试语义实际上就是输出 "语义树"。 和前两小节不同,要获得语义树,首先要在一开始做声明,如下所示:class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
showSemanticsDebugger: true,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
特别留意上述代码中的 showSemanticsDebugger,它就是开启语义调试的前提。接下来就是添加语义树输出的方法了,这一步骤和前两小节类似,如下所示:
RaisedButton(
onPressed: () => debugDumpSemanticsTree(DebugSemanticsDumpOrder.traversalOrder),
child: Text("Create semantics tree dump"))
I/flutter ( 8341): SemanticsNode#0
I/flutter ( 8341): │ Rect.fromLTRB(0.0, 0.0, 1080.0, 1794.0)
I/flutter ( 8341): │
I/flutter ( 8341): └─SemanticsNode#1
I/flutter ( 8341): │ Rect.fromLTRB(0.0, 0.0, 411.4, 683.4) scaled by 2.6x
I/flutter ( 8341): │ textDirection: ltr
I/flutter ( 8341): │
I/flutter ( 8341): └─SemanticsNode#2
I/flutter ( 8341): │ Rect.fromLTRB(0.0, 0.0, 411.4, 683.4)
I/flutter ( 8341): │ flags: scopesRoute
I/flutter ( 8341): │
I/flutter ( 8341): ├─SemanticsNode#9
I/flutter ( 8341): │ │ Rect.fromLTRB(0.0, 0.0, 411.4, 80.0)
I/flutter ( 8341): │ │ thicknes: 4.0
I/flutter ( 8341): │ │
I/flutter ( 8341): │ └─SemanticsNode#10
I/flutter ( 8341): │ Rect.fromLTRB(16.0, 40.5, 242.0, 63.5)
I/flutter ( 8341): │ flags: isHeader, namesRoute
I/flutter ( 8341): │ label: "Flutter Demo Home Page"
I/flutter ( 8341): │ textDirection: ltr
I/flutter ( 8341): │ elevation: 4.0
I/flutter ( 8341): │
I/flutter ( 8341): ├─SemanticsNode#3
I/flutter ( 8341): │ Rect.fromLTRB(65.7, 257.7, 345.7, 273.7)
I/flutter ( 8341): │ label: "You have pushed the button this many times:"
I/flutter ( 8341): │ textDirection: ltr
I/flutter ( 8341): │
I/flutter ( 8341): ├─SemanticsNode#4
I/flutter ( 8341): │ Rect.fromLTRB(195.7, 273.7, 215.7, 313.7)
I/flutter ( 8341): │ label: "0"
I/flutter ( 8341): │ textDirection: ltr
I/flutter ( 8341): │
I/flutter ( 8341): ├─SemanticsNode#5
I/flutter ( 8341): │ Rect.fromLTRB(135.7, 313.7, 275.7, 361.7)
I/flutter ( 8341): │ actions: tap
I/flutter ( 8341): │ flags: isButton, hasEnabledState, isEnabled
I/flutter ( 8341): │ label: "Create app dump"
I/flutter ( 8341): │ textDirection: ltr
I/flutter ( 8341): │ thicknes: 2.0
I/flutter ( 8341): │
I/flutter ( 8341): ├─SemanticsNode#6
I/flutter ( 8341): │ Rect.fromLTRB(113.2, 361.7, 298.2, 409.7)
I/flutter ( 8341): │ actions: tap
I/flutter ( 8341): │ flags: isButton, hasEnabledState, isEnabled
I/flutter ( 8341): │ label: "Create render tree dump"
I/flutter ( 8341): │ textDirection: ltr
I/flutter ( 8341): │ thicknes: 2.0
I/flutter ( 8341): │
I/flutter ( 8341): ├─SemanticsNode#7
I/flutter ( 8341): │ Rect.fromLTRB(118.2, 409.7, 293.2, 457.7)
I/flutter ( 8341): │ actions: tap
I/flutter ( 8341): │ flags: isButton, hasEnabledState, isEnabled
I/flutter ( 8341): │ label: "Create layer tree dump"
I/flutter ( 8341): │ textDirection: ltr
I/flutter ( 8341): │ thicknes: 2.0
I/flutter ( 8341): │
I/flutter ( 8341): ├─SemanticsNode#8
I/flutter ( 8341): │ Rect.fromLTRB(100.7, 457.7, 310.7, 505.7)
I/flutter ( 8341): │ actions: tap
I/flutter ( 8341): │ flags: isButton, hasEnabledState, isEnabled
I/flutter ( 8341): │ label: "Create semantics tree dump"
I/flutter ( 8341): │ textDirection: ltr
I/flutter ( 8341): │ thicknes: 2.0
I/flutter ( 8341): │
I/flutter ( 8341): └─SemanticsNode#11
I/flutter ( 8341): │ merge boundary ⛔️
I/flutter ( 8341): │ Rect.fromLTRB(0.0, 0.0, 56.0, 56.0) with transform
I/flutter ( 8341): │ [1.0,2.4492935982947064e-16,0.0,339.42857142857144;
I/flutter ( 8341): │ -2.4492935982947064e-16,1.0,0.0,611.4285714285714;
I/flutter ( 8341): │ 0.0,0.0,1.0,0.0; 0.0,0.0,0.0,1.0]
I/flutter ( 8341): │ label: "Increment"
I/flutter ( 8341): │ textDirection: ltr
I/flutter ( 8341): │
I/flutter ( 8341): └─SemanticsNode#12
I/flutter ( 8341): merged up ⬆️
I/flutter ( 8341): Rect.fromLTRB(0.0, 0.0, 56.0, 56.0)
I/flutter ( 8341): actions: tap
I/flutter ( 8341): flags: isButton, hasEnabledState, isEnabled
I/flutter ( 8341): thicknes: 6.0
I/flutter ( 8341):
"开发者说·DTalk" 面向中国开发者们征集 Google 移动应用 (apps & games) 相关的产品/技术内容。欢迎大家前来分享您对移动应用的行业洞察或见解、移动开发过程中的心得或新发现、以及应用出海的实战经验总结和相关产品的使用反馈等。我们由衷地希望可以给这些出众的中国开发者们提供更好展现自己、充分发挥自己特长的平台。我们将通过大家的技术内容着重选出优秀案例进行谷歌开发技术专家 (GDE) 的推荐。
点击屏末 | 阅读原文 | 了解更多 "开发者说·DTalk" 活动详情与参与方式
长按右侧二维码
报名参与