Flutter学习笔记&学习资料推荐,15分钟的字节跳动视频面试

简介: Flutter学习笔记&学习资料推荐,15分钟的字节跳动视频面试
children: [
FlatButton(onPressed: onToggle, child: Text(‘On’)),
FlatButton(onPressed: onToggle, child: Text(‘Off’)),
],
);
}
}

上面方法中有两个FlatButton是一样的使用方式,那么你可能会考虑这样去重构:

//…
Widget _toggleButton(String text) =>
FlatButton(onPressed: onToggle, child: Text(text));
@override
Widget build(BuildContext context) {
return Row(
children: [
_toggleButton(‘On’),
_toggleButton(‘Off’),
],
);
}

按照我的理解,其实这样做是完全可以的,因为这两种方式最终是等价的,包括我们经常会写的构建列表组件会使用map方法

final List fruits;
//…
@override
Widget build(BuildContext context) {
return Column(
children: _buildChildren(),
);
}
List _buildChildren() {
return fruits.map((f) => _FruitInfo(fruit: f)).toList();
}

这样应该也是完全OK的,也就是说使用箭头函数和inline组件的方式完全没有任何区别。更多的可能还是要考虑实际业务场景,如下面的代码,flutter就很难理解,某些情况下可能会造成不可预期的结果,这时应该去函数化来得到更加安全的保障。

bool condition;
Widget _foo();
Widget _bar();
Widget build(BuildContext context) {
return condition
? _foo()_bar();
}

为了更好理解再看两组代码

以下两组代码等价:

Widget functionA() => Container()
@override
Widget build() {
return functionA()
}
@override
Widget build() {
return Container();
}

以下两组代码等价:

class ClassA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container();
}
}
@override
Widget build() {
return ClassA()
}
@override
Widget build() {
return KeyedSubtree(
key: ObjectKey(ClassA),
child: Container(),
);
}

可以看出两种方式本质上的不同,类的优势明显优于函数。当然如果你还是比较习惯写函数式的组件,Rousselet大神写了一个库functional_widget可以让你以写函数组件的方式来写类组件:

@swidget
Widget foo(BuildContext context, int value) {
return Text(‘$value’);
}

比如,当你写上面的代码时,使用他这个库会生成下面的代码:

class Foo extends StatelessWidget {
const Foo(this.value, {Key key}) : super(key: key);
final int value;
@override
Widget build(BuildContext context) {
return foo(context, value);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IntProperty(‘value’, value));
}
}

第二个问题:如何避免没必要的重复构建?

How to deal with unwanted widget build? (如何处理多余的组件构建?)

这个链接同样也是Rousselet的回答,他的意思,简而言之就是build方法是Flutter中刷新界面时会高频调用的方法,所以尽量保持它的纯净。这对提高刷新性能尤为重要,我们需要把跟UI构建不直接相关的处理逻辑全部移出build方法。例如他提到的这部分优化代码就是将Future对象的构造从build中移了出来:

class Example extends StatefulWidget {
@override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State {
Future future;
@override
void initState() {
future = Future.value(42);
super.initState();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, snapshot) {
// create some layout here
},
);
}
}

Flutter中实例相同的组件构建时不会被重新构建,利用这一点我们可以想办法缓存部分组件,如:

@override
Widget build(BuildContext context) {
return const DecoratedBox(
decoration: BoxDecoration(),
child: Text(“Hello World”),
);
}

其中const修饰的组件不会每次都被重新创建,使用final关键字也可以做到:

@override
Widget build(BuildContext context) {
final subtree = MyWidget(
child: Text(“Hello World”)
);
return StreamBuilder(
stream: stream,
initialData: “Foo”,
builder: (context, snapshot) {
return Column(
children: [
Text(snapshot.data),
subtree,
],
);
},
);
}

这里final修饰的组件也不会每次都被重新创建,这种模式在动画中被大量使用如AnimatedBuilder。另外,也提到其他解决方法如尽可能拆分到更小的独立Statefull组件中,或者使用Provider库来解决。

build方法在下面的场景下会被调用:

  • 调用 initState 方法之后
  • 调用 didUpdateWidget 方法之后
  • 调用 setState()方法时
  • 键盘被打开时
  • 屏幕方向发生旋转时
  • 父组件被构建子组件也会重新构建

自定义组件的一个重要点就是在didUpdateWidget中根据新旧组件的状态值比较决定是否要重新构建UI:

@override
void didUpdateWidget(MyRichText oldWidget) {
if (widget.text != oldWidget.text) {
_textSpan = parseText(widget.text);
}
super.didUpdateWidget(oldWidget);
}

又如:

@override
void didUpdateWidget(CounterWidget oldWidget) {
//通过新旧widget的一些属性来判断是否变化
// 检查新旧child是否发生变化(key和类型同时相等则返回true,认为没变化)
if (Widget.canUpdate(widget.child, oldWidget.child)) {
// child没变化,…
} else {
super.didUpdateWidget(oldWidget);
}
}

Flutter 解决嵌套地狱的几种方案


开头介绍过Flutter的不好的地方,第一个就是嵌套层级过多的问题,

第一种方法,将复杂的widget抽取变量,或者抽取类组件

详细的戳这篇文章:Flutter避免代码嵌套,写好build方法

这篇文章的主要思想是不断的复用抽取的控件变量来减少嵌套层级,包括利用控件变量结合if条件渲染, 以及将组件抽取到类当中(Stateful或Stateless)。注意这里提到的是抽取变量,而不是抽取函数,详细前面优化部分已经介绍过了。

其实简单来说,这种方法做的事情就是两个字:重构!不借助任何外力或者依赖多余的库,重新设计和组织你的代码,以得到更加清晰的代码架构和逻辑,并且能够减少bug。我觉得这个已经不算是什么新鲜的姿势了,而是作为一只优秀程序猿的基本素养。

总的来说这种方法是比较原生态的,我个人也比较推荐这种。

第二种方法,通过Dart 2.7的新属性扩展函数来解决

主要看这篇文章:https://blog.csdn.net/c6E5UlI1N/article/details/104057737

虽然这种方式也能解决嵌套问题,但是最终使用起来你会发现它的思想都是反着去添加的,跟日常使用习惯部分,有点反人类。。所以我个人不推荐这种方式。

第三种方法,利用建造者模式来解决

这个可以看这里:flutter解决布局嵌套问题

设计模式是用来优化代码和重构的终极法宝,很多时候你想不到的答案可能先人已经总结了方法。按照build模式我们可以将常用的布局组件都封装一下使用,不限于文中介绍的。只是提供了一种思路。

第四种方法,将组件属性封装为方法来解决

这种其实是由上面第三种建造者模式得到的启发,但是这里我不用建造者模式,因为建造者模式到最后一步build才会生成对象实例,而我想先生成对象实例,然后再调用实例的方法来为其添加属性。最终想要的效果如下:

import ‘package:flutter/material.dart’;
import ‘column_row_wrap.dart’;
class UnNestLayoutTestPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(“去除嵌套示例”),),
body: ColumnWidget()
.addChild(Text(“呵呵”))
.addChild(Text(“怎样”))
.addChildByPadding(Text(“可以”), EdgeInsets.all(10.0))
.addChild(CenterWidget(Text(“测试”)))
.addChild(RowWidget()
.addChild(Text(“你好”))
.addChildByMargin(Text(“很好”), EdgeInsets.all(20.0))
.addChild(Text(“大家好”)))
.addChild(ContainerWidget(Text(“中国”))
.addPadding(EdgeInsets.all(10.0))
.addDecoration(BoxDecoration(color: Colors.red)))
.addChild(RaisedButtonWrap(“提交”)
.textColor(Colors.blue)
.onClick(() => { print(“点击了提交”)}))
);
}
}

可以看到,在build方法中我用了20行不到的方法添加了很多控件,并且可以轻松的设置颜色、padding、margin、点击事件等,如果要用Flutter原生的写法实现上面的代码远远不止20行,因为每一行要扩展出来好几行,代码量要暴增好几倍。

很显然,这样的代码,肯定是更加清晰且易于维护的。那怎么实现的呢,没有很神秘的东西,直接上代码:

import ‘package:flutter/material.dart’;
class ColumnWidget extends StatelessWidget {
List _children = [];
ColumnWidget addChild(Widget widget) {
_children.add(widget);
return this;
}
ColumnWidget addChildByPadding(Widget widget, EdgeInsetsGeometry padding) {
_children.add(Padding(
child: widget,
padding: padding,
));
return this;
}
@override
Widget build(BuildContext context) {
return Column(
children: _children,
);
}
}
class RowWidget extends StatelessWidget {
List _children = [];
RowWidget addChild(Widget widget) {
_children.add(widget);
return this;
}
RowWidget addChildByMargin(Widget widget, EdgeInsetsGeometry margin) {
_children.add(Container(
child: widget,
margin: margin,
));
return this;
}
@override
Widget build(BuildContext context) {
return Row(
children: _children,
);
}
}
class ContainerWidget extends StatelessWidget {
final Widget children;
ContainerWidget(this.children);
EdgeInsetsGeometry _padding;
EdgeInsetsGeometry _margin;
Decoration _decoration;
ContainerWidget addPadding(EdgeInsetsGeometry padding) {
_padding = padding;
return this;
}
ContainerWidget addMargin(EdgeInsetsGeometry margin) {
_margin = margin;
return this;
}
ContainerWidget addDecoration(Decoration decoration) {
_decoration = decoration;
return this;
}
@override
Widget build(BuildContext context) {
return Container(
child: children,
padding: _padding,
margin: _margin,
decoration: _decoration,
);
}
}
class RaisedButtonWrap extends StatelessWidget {
String title;
RaisedButtonWrap(this.title);
Color _textColor;
Color _bgColor;
VoidCallback _clickListener;
RaisedButtonWrap onClick(VoidCallback clickListener) {
_clickListener = clickListener;
return this;
}
RaisedButtonWrap textColor(Color textColor) {
_textColor = textColor;
return this;
}
RaisedButtonWrap bgColor(Color bgColor) {
_bgColor = bgColor;
return this;
}
@override
Widget build(BuildContext context) {
return RaisedButton(
child: Text(title),
textColor: _textColor,
color: _bgColor,
onPressed: _clickListener,
);
}
}
class CenterWidget extends StatelessWidget {
final Widget child;
CenterWidget(this.child);
@override
Widget build(BuildContext context) {
return Center(
child: child,
);
}
}

这里我只是最简单的封装实现,实际你可以做的更精细将方法封装的更全面一些。当然Flutter当中有300多个组件,我们并不需要将所有的组件都封装一遍,你可以只将常用或者高频使用的组件进行封装,不常用的可以暂时不用去管直接按原来的方式使用即可。

当然并不一定是全部只用这种写法写的,也可以混合在嵌套的布局中使用:

class UnNestLayoutTestPage2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(“去除嵌套示例”),),
body: ColumnWidget()
.addChild(Text(“测试”))
.addChild(Container(
child: RowWidget()
.addChild(Text(“你好”))
.addChildByMargin(Text(“很好”), EdgeInsets.all(20.0))
.addChild(Text(“大家好”)),
))
.addChild(Container(
child:Center(
child: RaisedButtonWrap(“提交”)
.textColor(Colors.blue)
.onClick(() => { print(“点击了提交”)}),
),
))
);
}
}

但是很明显,一旦我们开始了嵌套,代码行数就开始多了起来,看起来就没有那么清爽了。

之所以会有这种感觉,本质上是因为Flutter当中所有的组件属性都是通过构造函数的命名参数传递的,并且没有提供任何的属性操作方法,这就使得你不得不去层层嵌套的使用,这跟android中的java类组件完全不一样,android中的所有类都是提供方法的。很难想象如果我定义了一个类和N多个属性,但是不提供操作方法来使用,而是只提供构造函数来传递这些属性参数。

Flutter 调试相关


普通的断点调试跟纯android的没有太大区别,比如当我点击按钮执行某个方法时可以断点分步执行等待。

Dart DevTools

这里只要记录一下Dart DevTools这个工具:

https://flutter.cn/docs/development/tools/devtools/inspector

在AS中点击debug运行或者运行Flutter Attach:

运行起来之后在控制台的会有一个dart的蓝色图标按钮,点击这个按钮就会在浏览器中打开Dart调试面板:

当我们选中Flex类型的布局(如Column和Row)时, 我们可以点击右侧的Layout Explorer查看布局的详细信息,不过这个目前支持flex类型的容器才行:

这里可以改变主轴和交叉轴的对齐方向,或者可以点击设置子组件的flex属性,可以在设备上实时预览改变的结果。这对于理解布局和解决一些UI溢出问题比较有帮助。

默认打开左侧是列出的首页界面的代码,如果想查看指定的组件,需要点击Select Widget Mode模式,然后在手机上点击想要查看的控件,浏览器就会刷新到对应的界面。

但是这个工具感觉目前还不成熟,点几下经常会报错或者失去反应,有时没有指定 textDirection: TextDirection.ltr 也可能导致Layout Explorer无法查看。

另外这个工具可以用来分析性能时间、内存、网络等,但是需要以--profile模式运行,这里可以先了解一下Flutter的四种运行模式:Debug、Release、Profile和test

我们现在terminal中运行 flutter run --profile,运行成功最终会看到如下界面 :

点击上面红框中的地址打开浏览器:

复制上面红框中的地址然后打开Tools-->Flutter-->Open Dart DevTools,并粘贴到打开页面的输入框当中(以http开头):

最后我还整理了很多Android中高级的PDF技术文档。以及一些大厂面试真题解析文档。需要的朋友都可以点击GitHub直接获取方式

Android高级架构师之路很漫长,一起共勉吧!


相关文章
|
1月前
|
设计模式 移动开发 开发框架
如何学习 Flutter 框架?
学习 Flutter 需要耐心和持续的努力,通过系统的学习、实践、交流和不断跟进最新技术,你将逐渐掌握 Flutter 框架,并能够开发出高质量的移动应用。
|
2月前
|
算法 前端开发 Java
数据结构与算法学习四:单链表面试题,新浪、腾讯【有难度】、百度面试题
这篇文章总结了单链表的常见面试题,并提供了详细的问题分析、思路分析以及Java代码实现,包括求单链表中有效节点的个数、查找单链表中的倒数第k个节点、单链表的反转以及从尾到头打印单链表等题目。
36 1
数据结构与算法学习四:单链表面试题,新浪、腾讯【有难度】、百度面试题
|
1月前
|
存储 缓存 JavaScript
Flutter 学习之封装 WebView
【10月更文挑战第24天】通过以上的探讨,我们可以看出,在 Flutter 中封装 WebView 是非常有必要的,它可以提高代码的复用性、增强可维护性、提供统一接口。在实际应用中,我们需要根据具体的需求和场景,选择合适的封装方法和技术,以实现更好的效果。
|
2月前
|
Dart 开发者 Windows
flutter:dart的学习
本文介绍了Dart语言的下载方法及基本使用,包括在Windows系统上和VSCode中的安装步骤,并展示了如何运行Dart代码。此外,还详细说明了Dart的基础语法、构造函数、泛型以及库的使用方法。文中通过示例代码解释了闭包、运算符等概念,并介绍了Dart的新特性如非空断言操作符和延迟初始化变量。最后,提供了添加第三方库依赖的方法。
34 12
|
2月前
|
Java 应用服务中间件 程序员
JVM知识体系学习八:OOM的案例(承接上篇博文,可以作为面试中的案例)
这篇文章通过多个案例深入探讨了Java虚拟机(JVM)中的内存溢出问题,涵盖了堆内存、方法区、直接内存和栈内存溢出的原因、诊断方法和解决方案,并讨论了不同JDK版本垃圾回收器的变化。
36 4
|
2月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
175 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
3月前
|
网络协议 算法 数据库
|
4月前
|
Dart 开发工具 Android开发
Flutter学习:从搭建环境到运行
Flutter学习:从搭建环境到运行
48 0
|
5月前
flutter-provider学习笔记
flutter-provider学习笔记
|
5月前
|
索引
flutter-其他学习
flutter-其他学习