首先咱們來到第一次看到Widget類的場景,那時還對這個世界一無所知,
進入程序的入口時runApp函數中須要傳入一個Widget對象,這即是第一眼。
初始項目中的作法是自定義了一個MyApp類繼承自StatelessWidget。canvas
void main()=>runApp(MyApp());
---->[flutter/lib/src/widgets/binding.dart:778]----
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
class MyApp extends StatelessWidget {
//略...
}
複製代碼
位置:
flutterSDK/packages/flutter/lib/src/widgets/framework.dart:369
首先,它在framework包中,能夠說相當重要。其次它繼承自DiagnosticableTree
下圖可見Widget類在Flutter的框架層中是比較頂尖的類。bash
你以後就會知道,Widget是Flutter界面的中心,可顯示在頁面上的一切,都和Widget相關。微信
首先,Widget是一個抽象類,擁有一個
createElement()
的抽象方法返回一個Element
對象。
其次,Widget類自己只有一個字段、一個構造方法、一個抽象方法、一個靜態方法和兩個普通方法。app
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
@protected
Element createElement();
@override
String toStringShort() {
return key == null ? '$runtimeType' : '$runtimeType-$key';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
}
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
複製代碼
關於Widget的源碼,這裏暫時不作解讀,隨着瞭解的深刻,再去看源碼,效果會好很,如今火候還未到,有個大概印象就好了。框架
在Widget源碼中明顯指出了關於Widget狀態的問題:less
/// Widgets themselves have no mutable state (all their fields must be final).
/// If you wish to associate mutable state with a widget, consider using a
/// [StatefulWidget], which creates a [State] object (via
/// [StatefulWidget.createState]) whenever it is inflated into an element and
/// incorporated into the tree.
Widget的自己沒有可變狀態(全部字段都必須是final)。
若是您但願將一個widget擁有可變狀態,請考慮使用 StatefulWidget,
每當它被加載爲元素併合併到渲染樹中時,會建立State對象(經過 StatefulWidget.createState)。
複製代碼
對StatefulWidget和StatelessWidget也作了簡要的描述ide
/// * [StatefulWidget] and [State], for widgets that can build differently
/// several times over their lifetime.
/// * [StatelessWidget], for widgets that always build the same way given a
/// particular configuration and ambient state.
StatefulWidget和State,用於能夠在其生命週期內屢次構建的widget。
StatelessWidget,用於在給定配置和環境的狀態的下始終以相同方式構建的widget。
複製代碼
該類的自己很是簡潔,因爲Widget有一個createElement抽象方法,
StatelessWidget類中經過StatelessElement對象完成了該抽象方法,
因此StatelessWidget只須要關注build這個抽象方法便可。函數
abstract class StatelessWidget extends Widget {
const StatelessWidget({ Key key }) : super(key: key);
@override
StatelessElement createElement() => StatelessElement(this);
@protected
Widget build(BuildContext context);
複製代碼
如初始項目中,MyApp是繼承了StatelessWidget,它的任務在於重寫build方法。post
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'),
);
}
}
複製代碼
該類自己也比較簡單,繼承自Widget,createElement方法經過StatefulElement實現
因此該類須要注意的只有抽象方法createState(),負責返回一個State狀態對象測試
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key);
@override
StatefulElement createElement() => StatefulElement(this);
@protected
State createState();
}
複製代碼
初始代碼也是用心良苦,爲咱們準備了一個簡單的有狀態組件MyHomePage
能夠看到,該類的核心是createState方法,返回一個自定義的_MyHomePageState對象
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
複製代碼
比較引人注目的就是State對象中有一個泛型,從源碼中來看,
該泛型值接受StatefulWidget,即有狀態組件類。
State做爲一個抽象類,存在一個build抽象方法來返回一個Widget對象
abstract class State<T extends StatefulWidget> extends Diagnosticable {
//略...
@protected
Widget build(BuildContext context);
}
複製代碼
初始代碼中也爲咱們作了示例:
這裏將_counter做爲可變狀態,經過點按鈕改變狀態,再經過setState從新渲染,
執行build方法,從而達到了點擊按鈕數字增長的一個組件,
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),
),
//略...
);
}
}
複製代碼
這裏須要注意的一點是State類中的widget屬性究竟是什麼,這裏經過debug能夠看出,就是傳入的泛型類,
至於如何widget屬性什麼時候賦值以及渲染的,先別急,還有一段很長的路要走。
如今回頭看一下,你是否已經對曾經陌生無比的初始項目有了忽然熟悉了許多。
驀然回首,但願這會成爲你在一段旅行中美麗的一瞬,也是我這個導遊的成功。
趁人打鐵,爲了讓你們對Widget有更好的理解,這裏挑選了兩個Widget。
經過源碼賞析一下:一個Widget是如何構成的。第一個是無狀態家族的Icon組件
Icon主要有三個屬性,分別控制圖標,顏色,大小
Icon(
Icons.local_shipping,
color: Colors.pink,
size: 30.0,
)
複製代碼
從源碼中能夠看出,Icon類中主要作了四件事:
構造函數--> 聲明屬性字段--> 實現build方法,返回Widget對象-->debugFillProperties
class Icon extends StatelessWidget {
const Icon(
this.icon, {
Key key,
this.size,
this.color,
this.semanticLabel,
this.textDirection,
}) : super(key: key);
final IconData icon;
final double size;
final Color color;
final String semanticLabel;
final TextDirection textDirection;
@override
Widget build(BuildContext context) {
//暫略...
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
//暫略...
}
}
複製代碼
能夠看出,構造函數中有一個必須的參數icon,從定義中來看是一個IconData對象
注意:構造函數用const關鍵字修飾,字段全被修飾爲final,這就意味着字段不可再修改。
這也是被稱爲無狀態的緣由,一旦對象構建完成,它就樣子就沒法再被改變。
build方法做爲StatelessWidget的抽象方法,子類必須去實現
這個方法也將決定一個Widget在界面上的樣子,因此它相當重要
從源碼中能夠看出Icon主要是經過RichText來實現的,核心是text屬性
@override
Widget build(BuildContext context) {
//略...
Widget iconWidget = RichText(
overflow: TextOverflow.visible, // Never clip.
textDirection: textDirection, // Since we already fetched it for the assert...
text: TextSpan(
text: String.fromCharCode(icon.codePoint),//文字
style: TextStyle(
inherit: false,
color: iconColor,
fontSize: iconSize,
fontFamily: icon.fontFamily,
package: icon.fontPackage,
),
),
);
if (icon.matchTextDirection) {//作文本方向的處理
switch (textDirection) {
case TextDirection.rtl://文本方向從右到左
iconWidget = Transform(
transform: Matrix4.identity()..scale(-1.0, 1.0, 1.0),
alignment: Alignment.center,
transformHitTests: false,
child: iconWidget,
);
break;
case TextDirection.ltr://文本方向從左到右
break;
}
}
// 暫略...
}
複製代碼
映入眼簾的是
String.fromCharCode()
方法,它接受一個int值
這個int值是由IconData對象的codePoint屬性提供的,爲了方便開發,
Flutter框架給了不少Icon靜態常量,固然你也可使用自定義的圖標。
---->[flutter/bin/cache/pkg/sky_engine/lib/core/string.dart:134]----
external factory String.fromCharCode(int charCode);
----[flutter/lib/src/widgets/icon_data.dart:22]----
const IconData(
this.codePoint, {
this.fontFamily,
this.fontPackage,
this.matchTextDirection = false,
});
/// The Unicode code point at which this icon is stored in the icon font.
final int codePoint;
----[flutter/lib/src/widgets/icon_data.dart:22]----
static const IconData local_shipping = IconData(0xe558, fontFamily: 'MaterialIcons');
複製代碼
一個圖標實際上就是一個字體,能夠根據一個int值和字庫名匹配到它。
因此圖標才支持變色和改變大小等方便的功能。
還有一點不知你是否注意,最後返回的是一個包裹了iconWidget的Semantics對象
字面上來看,它是語義化的意思,那他有什麼用處呢?
return Semantics(
label: semanticLabel,
child: ExcludeSemantics(
child: SizedBox(
width: iconSize,
height: iconSize,
child: Center(
child: iconWidget,
),
),
),
);
複製代碼
Flutter中的MaterialApp有一個
showSemanticsDebugger
的屬性能夠用來查看語義化界面
---->[main.dart:3]----
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
var icon = Icon(
Icons.local_shipping,
color: Colors.pink,
size: 40.0,
semanticLabel: "一個貨車圖標",
);
var scaffold=Scaffold(
appBar: AppBar(
title: Text('Flutter Demo Home Page'),
),
body:icon,
);
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
showSemanticsDebugger: true,
home: scaffold,
);
}
}
複製代碼
好了,這樣就是對於一個簡單的無狀態組件構成的簡要介紹。
有狀態組件很好理解,首先它有一個容許改變的狀態量,不如Checkbox就是選中與否
下面的測試代碼實現了,點擊切換Checkbox選中或未選中的狀態
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
var scaffold=Scaffold(
appBar: AppBar(
title: Text('Flutter Demo Home Page'),
),
body:CheckBoxWidget(),
);
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: scaffold,
);
}
}
class CheckBoxWidget extends StatefulWidget {
@override
_CheckBoxWidgetState createState() => _CheckBoxWidgetState();
}
class _CheckBoxWidgetState extends State<CheckBoxWidget> {
bool _checked=true;//維護CheckBox框狀態
@override
Widget build(BuildContext context) {
return
Checkbox(
value: _checked,
activeColor: Colors.blue, //選中時的顏色
onChanged:(value){
setState(() {
_checked=value;
});
} ,
);
}
}
複製代碼
下面是Checkbox的代碼,繼承自StatefulWidget,因此須要實現createState方法
這時,源碼中使用自定義的_CheckboxState類來管理狀態。
class Checkbox extends StatefulWidget {
const Checkbox({
Key key,
@required this.value,
this.tristate = false,
@required this.onChanged,
this.activeColor,
this.checkColor,
this.materialTapTargetSize,
}) : assert(tristate != null),
assert(tristate || value != null),
super(key: key);
final bool value;//是否選中
final ValueChanged<bool> onChanged;//點擊回調
final Color activeColor;//激活態框顏色
final Color checkColor;//激活態對勾顏色
final bool tristate;//三態
final MaterialTapTargetSize materialTapTargetSize;
static const double width = 18.0;
@override
_CheckboxState createState() => _CheckboxState();
}
複製代碼
經過這兩個組件源碼,能夠總結出一些風格特色:
1.構造函數用const修飾,每行寫一個屬性
2.必須的屬性用@required註解
3.非空的屬性用assert斷言
4.字段全是final類型
複製代碼
_CheckboxState中的build方法返回_CheckboxRenderObjectWidget對象
CheckBox具體繪製邏輯及狀態改變,在_RenderCheckbox中實現
---->[flutter/packages/flutter/lib/src/material/checkbox.dart:140]----
class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
//略...
}
return _CheckboxRenderObjectWidget(
//略...
);
}
}
---->[flutter/packages/flutter/lib/src/material/checkbox.dart:168]----
class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
//略...
@override
_RenderCheckbox createRenderObject(BuildContext context) => _RenderCheckbox(
//略...
);
@override
void updateRenderObject(BuildContext context, _RenderCheckbox renderObject) {
//略...
}
複製代碼
_RenderCheckbox繼承自RenderToggleable,能夠重寫paint方法
這邊簡單看一下主要的邊框和對勾的繪製方法
// 能夠看出畫筆的顏色是checkColor,以線條的形式
void _initStrokePaint(Paint paint) {
paint
..color = checkColor
..style = PaintingStyle.stroke
..strokeWidth = _kStrokeWidth;
}
//繪製邊線
void _drawBorder(Canvas canvas, RRect outer, double t, Paint paint) {
assert(t >= 0.0 && t <= 0.5);
final double size = outer.width;
// 當t從0.0到1.0時,逐漸填充外部矩形。
final RRect inner = outer.deflate(math.min(size / 2.0, _kStrokeWidth + size * t));
canvas.drawDRRect(outer, inner, paint);
}
//繪製對勾
void _drawCheck(Canvas canvas, Offset origin, double t, Paint paint) {
assert(t >= 0.0 && t <= 1.0);
final Path path = Path();
const Offset start = Offset(_kEdgeSize * 0.15, _kEdgeSize * 0.45);//起始偏移點
const Offset mid = Offset(_kEdgeSize * 0.4, _kEdgeSize * 0.7);//中間偏移點
const Offset end = Offset(_kEdgeSize * 0.85, _kEdgeSize * 0.25);//終止偏移點
if (t < 0.5) {//t<0.5時,繪製短邊
final double strokeT = t * 2.0;
final Offset drawMid = Offset.lerp(start, mid, strokeT);
path.moveTo(origin.dx + start.dx, origin.dy + start.dy);
path.lineTo(origin.dx + drawMid.dx, origin.dy + drawMid.dy);
} else {//t>0.5時,繪製長邊
final double strokeT = (t - 0.5) * 2.0;
final Offset drawEnd = Offset.lerp(mid, end, strokeT);
path.moveTo(origin.dx + start.dx, origin.dy + start.dy);
path.lineTo(origin.dx + mid.dx, origin.dy + mid.dy);
path.lineTo(origin.dx + drawEnd.dx, origin.dy + drawEnd.dy);
}
canvas.drawPath(path, paint);
}
複製代碼
這樣你一個StatefulWidget組件從定義到狀態,到繪製的流程應該有所瞭解
經過本篇,但願你能夠對Widget有一些更深入的理解,然而這只是開始。以後的還會對Widget作更深刻的探索 本文到此接近尾聲了,若是想快速嚐鮮Flutter,《Flutter七日》會是你的必備佳品;若是想細細探究它,那就跟隨個人腳步,完成一次Flutter之旅。
另外本人有一個Flutter微信交流羣,歡迎小夥伴加入,共同探討Flutter的問題,本人微信號:zdl1994328
,期待與你的交流與切磋。