Android 开发者的 Flutter 指南 — View
View 在 Flutter 中对应什么?
对应的是 Widget,一种声明和构建 UI 的方式。
特点
- Widget 仅存在于每一帧之间。当 Widget 或其状态需要改变刷新时,都会重新创建实例以生成新的 Widget 实例树;
- 不可变,轻量级。不可变是轻量级的部分原因,Widget 不是视图,也不绘制,仅对实际视图中的对象进行描述;
- 灵活且富有表现力的设计语言。 可以实现任何设计语言,如遵循 Material Design 设计标准且支持所有平台的 Material 组件库;类似 iOS 设计风格的 Cupertino widgets 等。
如何更新 Widget ?
Widget 本身不可变且不能直接更新,只能通过修改 Widget 的状态来达到效果(间接更新:重新构建需要改变的 Widget 以替代原来的)。
由此引申出:
StatelessWidget:无状态,如用于显示只加载一次的 Logo。
StatefulWidget:有状态,如时常请求网络数据更新页面。
不同点:
StatefulWidget 含有一个可跨帧存取数据的 State 对象。
Widget 可变化,则是有状态的。但子 Widget 是有状态的,其父 Widget 仍可以是 StatelessWidget 。
如何布局 Widget ?
通过 Widget 树实现。
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(
child: MaterialButton(
onPressed: () {},
child: Text('Hello'),
padding: EdgeInsets.only(left: 10.0, right: 10.0),
),
),
);
}
可在 Flutter 提供的 Widget 目录 查看相关布局。
如何从布局中移除或添加组件 ?
Flutter 没有类似 addChild() 这种方法,但可通过控制 boolean 类型的标志,来创建显示不同 Widget。
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
// Default value for toggle
bool toggle = true;
void _toggle() {
setState(() {
toggle = !toggle;
});
}
_getToggleChild() {
if (toggle) {
return Text('Toggle One');
} else {
return MaterialButton(onPressed: () {}, child: Text('Toggle Two'));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(
child: _getToggleChild(),
),
floatingActionButton: FloatingActionButton(
onPressed: _toggle,
tooltip: 'Update Text',
child: Icon(Icons.update),
),
);
}
}
如何让 Widget 产生动画 ?
通过 animation 库中的动画 Widget 包裹 Widget 。
使用 Animation<double> 子类 AnimationController 控制动画 pause、seek、stop 、reverse 。
淡入淡出动画举例:
import 'package:flutter/material.dart';
void main() {
runApp(FadeAppTest());
}
class FadeAppTest extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fade Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyFadeTest(title: 'Fade Demo'),
);
}
}
class MyFadeTest extends StatefulWidget {
MyFadeTest({Key key, this.title}) : super(key: key);
final String title;
@override
_MyFadeTest createState() => _MyFadeTest();
}
class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin {
AnimationController controller;
CurvedAnimation curve;
@override
void initState() {
super.initState();
controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(
child: FadeTransition(
opacity: curve,
child: FlutterLogo(
size: 100.0,
)))),
floatingActionButton: FloatingActionButton(
tooltip: 'Fade',
child: Icon(Icons.brush),
onPressed: () {
controller.forward();
},
),
);
}
}
更多参阅 Animation & Motion widgets,Animations tutorial,Animations overview。
如何使用 Canvas 绘制 ?
与 Android 拥有类似的 Canvas API,因为都是基于低级渲染引擎 Skia 。
两个相关类:
- CustomPaint:
- CustomPainter:通过实现自定义的算法来绘制(implements your algorithm to draw to the canvas)。
可到 StackOverflow 查看 Collin 关于签名绘制的回答。
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: DemoApp()));
class DemoApp extends StatelessWidget {
Widget build(BuildContext context) => Scaffold(body: Signature());
}
class Signature extends StatefulWidget {
SignatureState createState() => SignatureState();
}
class SignatureState extends State<Signature> {
List<Offset> _points = <Offset>[];
Widget build(BuildContext context) {
return GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
setState(() {
RenderBox referenceBox = context.findRenderObject();
Offset localPosition =
referenceBox.globalToLocal(details.globalPosition);
_points = List.from(_points)..add(localPosition);
});
},
onPanEnd: (DragEndDetails details) => _points.add(null),
child: CustomPaint(painter: SignaturePainter(_points), size: Size.infinite),
);
}
}
class SignaturePainter extends CustomPainter {
SignaturePainter(this.points);
final List<Offset> points;
void paint(Canvas canvas, Size size) {
var paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null)
canvas.drawLine(points[i], points[i + 1], paint);
}
}
bool shouldRepaint(SignaturePainter other) => other.points != points;
}
如何自定义 Widget ?
通过组合粒度更小的 Widget ,而非继承。
class CustomButton extends StatelessWidget {
final String label;
CustomButton(this.label);
@override
Widget build(BuildContext context) {
return RaisedButton(onPressed: () {}, child: Text(label));
}
}
@override
Widget build(BuildContext context) {
return Center(
child: CustomButton("Hello"),
);
}
原文给 Android 开发者的 Flutter 指南,参考 Android 开发者参考。
此文目的:仅与 Android 对比,尽可能言简意骇的翻译,且不遗漏、不曲解词义。