Flutter入门——面向Android开发者的Flutter——Views(总)
最近几天开始记录一下一些基础的内容。
我相信很多人都和我差不多,使用Flutter都是简单看一下文档,了解一下widget,然后开始实践做项目,之后遇到问题查问题解决一下,继续做项目。
实践虽然是最快的上手方式,但是慢慢的发现瓶颈,有些问题如果网络上搜索不到,就很难解决。
更有甚者,当遇到一些问题解决不了,就说这个语言不怎么样,这样的问题都解决不了。偶尔还会和同行们吐槽,这就是UI的语言,有些问题就是没办法处理的很好...
我开始反思,真的是语言本身的缺陷吗?会不会是我自己对于一些基础理解的比较浅,才妄下定论?
带着这个思考,我开始查缺补漏,开始思考一些原理性的问题,希望自己能找到答案。
今天针对Views来解答一些问题
什么相当于Flutter中的视图
在Android中,View
是屏幕上显示的所有内容的基础。按钮、工具栏和输入,一切都是视图。在Flutter中,可以大体把View
和Widget
视为等价。Widget
并不完全映射到Android视图,但是当你熟悉Flutter的工作原理时,你可以将他们视为“你声明和构建UI的方式”。
这里面遗留一个小问题,什么是声明式UI、什么是反应式?
然而,和Android的View相比,还是有很多不同的地方。首先,Widget
具有不同的生命周期:它们是不可变的,一直存在到它们需要改变的。每当Widget
或其状态发生变化时,Flutter的框架都后悔创建一个新的小部件实例树。相比之下,Android视图只会值一次并且在invalidate
调用之前不会重绘。
Flutter的小部件是轻量级的,部分原因在于它们的不变性。因为它们本身不是视图,也没有直接绘制任何东西,而是对UI及其语义的描述,这些描述时被inflated
到引擎下的实际视图对象中。
Flutter包含Material Components库。这些是实现Material Design 指南的小部件。Material Dessign是一个灵活的设计系统,针对包括iOS在内的所有平台进行了优化。
但Flutter的灵活性和表现力足以实现任何设计语言。例如,在iOS上,可以使用Cupertino 小部件 来生成类似于Apple 的 iOS 设计语言的界面。
如何更新Widget?
在Android中,你可以通过直接改变视图来更新它们。然而,在Flutter中,Widget
是不可变的并且不会直接更新,相反你必须使用widget的状态。
这就是Stateful``Stateless
widget概念的由来。StatelessWidget
就像它听起来那样————一个没有状态信息的widget。
StatelessWidget
当你描述的用户界面部分不依赖于对象中的配置信息以外的任何内容时,这很有用。
例如。在Android中,类似于放置一个带logoImageViiew
,logo在运行时不会更改,相对应在Flutter用StatelessWidget
。
如果你想根据在进行HTTP调用或用户交互后接收到数据状态更改UI,那么你必须使用StatefulWidget
并告诉Flutter框架小部件State
已更新,以便它可以更新该小部件。
这里要注意的重要一点是,无状态和有状态小部件的核心行为是相同的。它们重建每一帧,不同之处在于StatefulWidgget
有一个State
对象可以跨帧存储状态数据并恢复它。
如果你有疑问,请始终记住这条规则:如果小部件发生变化(例如,由于用户交互),它是 stateful。然而,如果一个小部件对变化做出反应,如果它本身不对变化做出反应,那么包含它的父小部件仍然可以是stateless。
下面一个例子,显示如何使用StatelessWidget
,一个常见StatelessWidget
的是Text
小部件。如果你查看Text
小部件的实现,你会发现它是StatelessWidget
的子类。
Text(
'I like Flutter!',
style: TextStyle(fontWeight: FontWeight.bold),
)
如你所见,Text
没有与之关联的状态信息,它只呈现其构造函数传递的内容,仅此而已。
但是,如果你想让“I like Flutter”动态变化怎么办,例如当点击一个FloatingActionButton
?
为此,将Text
小部件包装在一个StatefulWidget
并在用户单击按钮时更新它。
import 'package:flutter/material.dart';
void main() {
runApp(const SampleApp());
}
class SampleApp extends StatelessWidget {
const SampleApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
const SampleAppPage({Key? key}) : super(key: key);
@override
State<SampleAppPage> createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
// Default placeholder text
String textToShow = 'I like Flutter';
void _updateText() {
setState(() {
// Update the text
textToShow = 'Flutterr is Awesome!';
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sample App'),
),
body: Center(child: Text(textToShow)),
floatingActionButton: FloatingActionButton(
onPressed: _updateText,
tooltip: 'Update text',
child: const Icon(Icons.update),
),
);
}
}
我如何布置我的小部件?我的XML布局文件在哪里?
在Android中,使用XML编辑布局,但在Flutter中,使用小部件树编写布局。
以下示例显示如何显示带填充的简单小部件
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sample App'),
),
body: Center(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.only(left: 20.0, right: 30),
),
onPressed: () {},
child: const Text('Hello'),
),
),
);
}
您可以在widget 目录中查看 Flutter 必须提供的一些布局 。
如何从我的布局中添加或删除小部件?
在Android中,调用addChild()
或调用removeChild()
父对象以动态添加或删除子视图。在Flutter中,因为小部件是不可变的,所以没有直接等同于addChild()
,相反,可以将一个函数传递给返回小部件
的父级,并使用bool标志控制该子级的创建。
例如,以下是点击如何在两个小部件中切换FloatingActionButton。
import 'package:flutter/material.dart';
void main() {
runApp(const SampleApp());
}
class SampleApp extends StatelessWidget {
const SampleApp({Key? key}) : super(key: key);
// This widget is the root of your application
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
const SampleAppPage({Key? key}) : super(key: key);
@override
State<SampleAppPage> createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
//Default value for toggle
bool toggle = true;
void _toggle() {
setState(() {
toggle = !toggle;
});
}
Widget _getToggleChild() {
if (toggle) {
return const Text('Toggle One');
} else {
return ElevatedButton(
onPressed: () {},
child: const Text('Toggle Two'),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sample App'),
),
body: Center(
child: _getToggleChild(),
),
floatingActionButton: FloatingActionButton(
onPressed: _toggle,
tooltip: 'Update Text',
child: const Icon(Icons.update),
),
);
}
}
如何为小部件设置动画?
在Android中,你要么使用XML创建动画,要么animate()
在视图上方调用方法。在Flutter中,通过将小部件包装在动画小部件中,使用动画库为小部件设置动画。
在Flutter中,使用AnimationController
,一个Animation<double>
可以暂停、寻找、停止和反转动画。它需要一个在vsync
发生的Ticker
信号,并在它运行时在每帧上产生0和1之间的线性插值。然后你创建一个或多个Animation
并将他们添加到控制器。
例如,可以使用CurvedAnimation
,它沿着差值曲线实现动画。从这个意义上讲,控制器是动画进度的“主要”来源,CurvedAnimation
计算替代控制器默认先行运动的曲线。与小部件一样。
在构建小部件树时,将分配给小部件Animation
的动画属性,例如一个有关透明度的FadeTransition
,并告诉控制器启动动画。
例子:编写一个FadeTransition
,在按下时使小部件淡入logo
import 'package:flutter/material.dart';
void main() {
runApp(const FadeAppTest());
}
class FadeAppTest extends StatelessWidget {
const FadeAppTest({Key? key}) : super(key: key);
//This widget iss the root of your application
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fade Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyFadeTest(title: 'Fade Demo'),
);
}
}
class MyFadeTest extends StatefulWidget {
const MyFadeTest({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyFadeTest> createState() => _MyFadeTest();
}
class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin {
late AnimationController controller;
late 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: FadeTransition(
opacity: curve,
child: const FlutterLogo(
size: 100.0,
),
),
),
floatingActionButton: FloatingActionButton(
tooltip: 'Fade',
onPressed: () {
controller.forward();
},
child: const Icon(Icons.brush),
),
);
}
}
有关详细信息,请参阅 动画和动作小部件、动画教程和动画概述。
如何使用Canvas进行绘制/绘画?
在Android中,可以使用Canvas
和Drawable
将图像和形状绘制到屏幕上。Flutter也有类似的Canvas
API,因为它基于相同的地渲染引擎Skia。因此,在Flutter中绘制画布是Android开发人员非常熟悉的任务。
Flutter有两个类可以帮助你绘制到画布上:CustomPaint
和CustomPainter
,后者实现了绘制到画布上的算法。
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: DemoApp()));
class DemoApp extends StatelessWidget {
const DemoApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => const Scaffold(body: Signature());
}
class Signature extends StatefulWidget {
const Signature({Key? key}) : super(key: key);
@override
SignatureState createState() => SignatureState();
}
class SignatureState extends State<Signature> {
List<Offset?> _points = <Offset>[];
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanUpdate: (details) {
setState(() {
RenderBox? referenceBox = context.findRenderObject() as RenderBox;
Offset localPosition =
referenceBox.globalToLocal(details.globalPosition);
_points = List.from(_points)..add(localPosition);
});
},
onPanEnd: (details) => _points.add(null),
child: CustomPaint(
painter: SignaturePainter(_points),
size: Size.infinite,
),
);
}
}
class SignaturePainter extends CustomPainter {
SignaturePainter(this.points);
final List<Offset?> points;
@override
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);
}
}
}
@override
bool shouldRepaint(SignaturePainter oldDelegate) =>
oldDelegate.points != points;
}
如何构建自定义小部件?
在Android中,通常会继承View
或使用预先存在的视图来覆盖和实现所需行为的方法。
在Flutter,通过组合较小的小部件(而不是扩展它们)来构建自定义小部件。它有点儿类似于ViewGroup
在Android中实现自定义,其中所构建块都已经存在,但你提供不同的行为,例如,自定义布局逻辑。
例如,你如何构建一个CustomButton
在构造函数中带有标签的?创建一个由标签组成的CustomButton ElevatedButton
,而不是通过扩展ElevatedButton
import 'package:flutter/material.dart';
class CustomButton extends StatelessWidget {
final String label;
const CustomButton({Key? key, required this.label}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: (){},
child: Text(label),
);
}
}
然后使用CustomButton
,就像使用任何其他Flutter小部件一样:
@override
Widget build(BuildContext context) {
return const Center(
child: CustomButton(label: 'Hello'),
);
}
转载自:https://juejin.cn/post/7181404264814608441