3-7 【文档讲解】认识视图(Views)
声明:Flutter专栏文档均来自慕课网
https://coding.imooc.com/class/321.html
认识视图(Views)
- 谁是Flutter中View?
- 如何更新Widgets?
- 如何布局?
- 如何在布局中添加或删除组件?
- 如何对 Widget 做动画?
- 如何绘图(Canvas draw/paint)?
- 如何构建自定义Widgets?
- 如何设置Widget的透明度?
谁是Flutter中View?
- 在Android中,View是屏幕上显示的所有内容的基础, 按钮、工具栏、输入框等一切都是View。
- 在 iOS 中,构建 UI 的过程中将大量使用 view 对象。这些对象都是 UIView 的实例。它们可以用作容器来承载其他的 UIView,最终构成你的界面布局。
- 在React Native中,View是一个支持Flexbox布局的容器,样式,触摸处理和辅助控制。
Android中的View与iOS中的UIView在下文中统称为:View,React Native统称为RN。
那么,在Flutter中我们可以将Widget当做是Android、iOS、RN中的View,但他们并不完全等价,但当我们试图去理解 Flutter 是如何工作的时候,我们可以认为它是“声明和构建 UI 的方法”。
但是,Widget与View有一些区别。 首先,Widget具有不同的生命周期:它们是不可变的,它们会存在于状态被改变之前。 每当Widget或其状态发生变化时,Flutter的框架都会创建一个新的Widget实例树。 相比之下,Android/iOS
视图被绘制一次,并且在调用invalidate/setNeedsDisplay
之前不会重绘。
此外,与View不同,Flutter的Widget很轻巧,部分原因在于它的不变性。 因为它本身不是视图,并且不是直接绘制任何东西,而是对UI及其语义的描述。
Flutter 包含了 Material 组件库。这些 widgets 遵循了 Material 设计规范。材料设计是一个灵活的设计系统,并且为包括 iOS 在内的所有系统进行了优化。
但是用 Flutter 实现任何的设计语言都非常的灵活和富有表现力。在 iOS 平台,你可以使用 Cupertino widgets 来构建遵循了 Apple’s iOS design language 的界面。
在Flutter中,您可以使用Widgets库中的核心布局小部件 如 Container, Column, Row, 和 Center,关于widget的更多内容可参考:Layout Widgets目录。
如何更新Widgets?
在Android/iOS中要更新视图,我们可以直接通过对应的方法来操作更改。 在Flutter中,Widget是不可变的,不会直接更新。 相反,我们可以通过操纵Widget的状态来更新它们。
这就是有状态和无状态Widget的概念来源。 StatelessWidget听起来就像是一个没有状态信息的Widget。
StatelessWidgets适用于当我们描述的用户界面不依赖于对象中的配置信息时。
例如,在Android/iOS中,我们需要用ImageView/UIImageView来显示logo。 logo在运行时不会改变,因此在Flutter中使用StatelessWidget是最好不过了。
如果要根据HTTP网络请求或用户交互后收到的数据动态更改UI,则必须使用StatefulWidget并告诉Flutter框架Widget的状态已更新,以便更新该Widget。
无状态Widget和有状态Widget之间的重要区别在于StatefulWidgets具有一个State对象,该对象存储状态数据并将其传递到树重建中,因此状态不会丢失。
请记住以下规则:如果Widget在build之外更改(例如,由于运行时用户交互),则它是有状态的。 如果Widget永远不会改变,一旦构建,它就是无状态的。 但是,即使Widget是有状态的,如果包含它的父窗口小部件本身不对这些更改(或其他输入)做出反应,父Widget仍然可以是无状态的。
接下来,我们来看看你如何使用一个StatelessWidget。Text就是一个常见的StatelessWidget。如果你查看Text Widget的实现,你会发现它是一个StatelessWidget的子类:
new Text(
'I like Flutter!',
style: new TextStyle(fontWeight: FontWeight.bold),
);
正如你所看到的,Text 没有与之关联的状态信息,它呈现了构造函数中传递的内容,仅此而已。
但是,如果你想让“I Like Flutter”动态变化,例如点击一个FloatingActionButton?可以通过将Text包装在StatefulWidget中并在点击按钮时更新它来实现,如:
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 placeholder text
String textToShow = "I Like Flutter";
void _updateText() {
setState(() {
// update the text
textToShow = "Flutter is Awesome!";
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(child: Text(textToShow)),
floatingActionButton: FloatingActionButton(
onPressed: _updateText,
tooltip: 'Update Text',
child: Icon(Icons.update),
),
);
}
}
如何布局?
- 在Android中,我们通过XML编写布局;
- 在iOS 中,我们会用 Storyboard 文件来组织 views,并对它们设置约束,或在 view controller 中使用代码来设置约束;
在 Flutter中,我们通过编写一个 widget 树来声明布局。
下面这个例子展示了如何展示一个带有 padding 的简单 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),
),
),
);
}
另外推荐大家在widget catalog中查看 Flutter提供的布局。
如何在布局中添加或删除组件?
- 在Android中,我们可以调用父级控件的addChild或removeChild方法以动态添加或删除View。
- 在 iOS 中,我们可以调用父view的addSubview() 或在子view的removeFromSuperview()来动态地添加或移除子 view。
在Flutter中,因为Widget是不可变的,所以没有类似的方法。相反,我们可以传入一个函数或表达式,该函数或表达式返回一个Widget给父项,并通过布尔值控制该Widget的创建。
例如,当点击一个FloatingActionButton时,如何在两个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做动画?
- 在Android中,我们可以通过XML创建动画或调用view.animate()。
- 在 iOS 中,你通过调用 animate(withDuration:animations:) 方法来给一个 view 创建动画。
在 Flutter 中,使用动画库来包裹 widgets,而不是创建一个动画 widget。
在 Flutter 中,使用 AnimationController,这是一个可以暂停、寻找、停止、反转动画的 Animation 类型。它需要一个 Ticker 当 vsync 发生时来发送信号,并且在每帧运行时创建一个介于 0 和 1 之间的线性插值(interpolation)。我们可以创建一个或多个的 Animation 并附加给一个 controller。
例如,我们可能会用 CurvedAnimation 来实现一个 interpolated 曲线。在这个场景中,controller 是动画过程的“主人”,而 CurvedAnimation 计算曲线,并替代 controller 默认的线性模式。
当构建 widget 树时,你会把 Animation 指定给一个 widget 的动画属性,比如 FadeTransition 的 opacity,并告诉控制器开始动画。
下面这个例子展示了在点击 FloatingActionButton 之后,如何使用 FadeTransition 来让 widget 淡出到 logo 图标:
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 draw/paint)?
- 在Android中,可以使用
Canvas
与Drawable
在屏幕上绘制出自定义形状和图片; - 在 iOS 上,可以通过
CoreGraphics
来在屏幕上绘制线条和形状; - 在RN中我们通常是由
react-native-canvas
插件来进行绘图;
Flutter也有类似的Canvas API,因为它基于相同的底层渲染引擎Skia。 因此,对于Android开发人员来说,在Flutter中绘制到画布是一项非常熟悉的任务。Flutter有两个类可以帮助我们绘制画布,CustomPaint和CustomPainter,它们实现您的算法以绘制到画布。
要了解如何在Flutter中实现签名Painter,可参阅Collin在StackOverflow上的答案。
绘制圆形和方形
在Flutter中,你可以使用 CustomPain
t 和 CustomPainter
类去绘制到画布。
以下示例显示如何使用CustomPaintwidget
在绘制阶段绘制。 它实现了抽象类CustomPainter
,并将其传递给CustomPaint的painter
属性。 CustomPaint
子类必须实现paint
和shouldRepaint
方法:
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;
}
import 'package:flutter/material.dart';
import 'package:flutter_app/navigator/tab_navigator.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter bottomNavigationBar',
theme: new ThemeData.fallback(),
home: _MyCanvas(),
);
}
}
// Flutter
class MyCanvasPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
Paint paint = Paint();
paint.color = Colors.amber;
canvas.drawCircle(Offset(100.0, 200.0), 40.0, paint);
Paint paintRect = Paint();
paintRect.color = Colors.lightBlue;
Rect rect = Rect.fromPoints(Offset(150.0, 300.0), Offset(300.0, 400.0));
canvas.drawRect(rect, paintRect);
}
bool shouldRepaint(MyCanvasPainter oldDelegate) => false;
bool shouldRebuildSemantics(MyCanvasPainter oldDelegate) => false;
}
class _MyCanvas extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomPaint(
painter: MyCanvasPainter(),
),
);
}
}
如何构建自定义Widgets?
- 在Android中,可以通过继承View或已经存在的某个控件,然后覆盖其绘制方法来实现自定义View;
- 在iOS中,可以通过编写 UIView 的子类,或使用已经存在的 view 来重载并实现方法,以达到特定的功能;
在 Flutter 中,推荐组合多个小的 widgets 来构建一个自定义的 widget(而不是扩展它)。
举个例子,如果你要构建一个 CustomButton ,并在构造器中传入它的 label?那就组合 RaisedButton 和 label,而不是扩展 RaisedButton。
class CustomButton extends StatelessWidget {
final String label;
CustomButton(this.label);
@override
Widget build(BuildContext context) {
return new RaisedButton(onPressed: () {}, child: new Text(label));
}
}
//使用自定义组件
@override
Widget build(BuildContext context) {
return new Center(
child: new CustomButton("Hello"),
);
}
}
如何设置Widget的透明度?
- 在 iOS 中,什么东西都会有一个 .opacity 或是 .alpha 的属性;
- 在Android中View有setAlpha方法;
在 Flutter中如果要改变透明度,我们可以给widget 包裹一个 Opacity widget 来做到这一点。
Opacity(
opacity: 0.5,
child: Text('透明度50%')
)