0.前言
刚接触Flutter的小伙伴在StatefulWidget控件时会感觉难以接受
本人一开始也是,不过对React的了解让我很快理解了Flutter的状态观念
本篇就说一下我对StatefulWidget一族的理解,希望可以帮你解决一些疑虑
1.从Slider开始说起
也许你在第一次使用Slider的时候会碰壁,你会发现它拖不动!
但如果你比较细心可以发现监听的值是在变化的,这跟Android是不同的
var slider = Slider( value: 0, max: 100, min: 0, onChanged: (e) { print('onChanged:$e'); }, onChangeStart: (e) { print('onChangeStart:$e'); }, onChangeEnd: (e) { print('onChangeEnd:$e'); }); ///------主场景------- var scaffold = Scaffold( body: Center(child: slider,) ); var app = MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: scaffold, ); void main() => runApp(app); 复制代码
2:如何让Slider有用武之地
现在回想一下Android怎么改变属性
在Android里控件修改其属性可以直接`对象.set属性`来设置 但在FLutter里你会奇怪的发现:当你`slider.value=20;`时会报错 这真是让人不爽,对象更改属性不是天经地义吗?但Flutter说:对不起,你不能 复制代码
这让我恍然大悟,为什么Widget源码里说
所有的组件都是恒定的
,它只是对元素的描述
组件的属性无法被改变因为属性都是final修饰的,既然无法修改,那又为什么会有状态一说?
其实恒定和变化是相对的,多个恒定的状态的连续重演就会产生动态效果 就像电影也只是图片的叠加,一张图片是恒定的,它也只是用像素对一个场景的色彩信息进行的描述 但多个恒定的照片连续播放时就会产生动态的效果,让我们感觉里面的人是活的,世界是运动的 这其中化腐朽为神奇的关键就是如何持续渲染,就像电影如何连续一帧帧的播放 这时状态类中的setState()应声而出,交给我,只要喊我一声,我就为你们更新状态 复制代码
这和React是如出一辙的,这种方式在我看来是非常优雅的。对象更改自身属性与之相比就笨重了许多
前者可以通过一个状态来表述、更新、修改自己,而后者只是能通过他本身来亲力亲为
3:如何正确打开Slider
上面说需要状态,那就需要一个StatefulWidget,如下:有一个私有的变量_value,
在Slider拖动的过程中执行_render方法进行渲染,在渲染时先将Slider的值给_value
在setState方法调用之后,build将会重新执行,那么Slider的值就会使用_value,从而实现状态的更新
class TextSlider extends StatefulWidget { @override State<StatefulWidget> createState() => _TextSliderState(); } class _TextSliderState extends State<TextSlider> { double _value = 0; @override Widget build(BuildContext context) { var show = _buildSlider(_value); return show; } _buildSlider(double value) { var slider = Slider( value: value, max: 100, min: 0, onChanged: (e) { print('onChanged:$e'); _render(); }, onChangeStart: (e) { print('onChangeStart:$e'); }, onChangeEnd: (e) { print('onChangeEnd:$e'); }, ); return slider; } _render(double value) { _value = value; setState(() {}); } } 复制代码
4:这样的优势
可能你会觉得只是使用一个Slider,还要写个类,未免有点小题大做
麻烦必然有其价值,简单必然有其局限。这便是宇宙的平衡。 一开始学编程时,定义了一个Circle类,可以用对象来算面积, 当时就想,这有必要吗,一个方法就搞定了啊,是不是有点小题大做。 之后渐渐发现面向对象的魅力,我不知你们对万物皆对象如何理解,这里说一下我的看法: 万物皆对象并不是站在人类的角度说世间的实体都是对象,而是站在另一个维度 一个应用便是一个小世界,里面有众多对象相互协调合作,来完成应用的功能。 这个小世界中的一切皆为对象。Coder需要管理这些对象的样貌,生死,家族关系,社交关系以及工作流程。 而对象的产生是要靠类来创建,所以类是至关重要的,其创建需要站在统领世界的上帝视角。 所以编程对我而言就是在创世,而我便是创世神,思想的高度可以让你的眼前有一个完全不一样的世界。 复制代码
话说回来,为什么要这样做呢?
三个词: 易复用、好维护、可拓展 这三个词会伴随Coder的编程生涯,如何让自己创造的世界更好的运作,是我们殚精竭虑的 从设计模式到数据结构,从编码到重构,我们努力调整维持这个世界的秩序,让它们脱离bug的魔爪 面向过程中的零星代码通过一个类的整合,形成一个创物的蓝图,用来召唤(new)对象 不知你是否有所感觉,Android中控件用起来是比较卡手的,总的来说就是太难复用,代码零星 比如,一个Slider滑动时Text跟随显示,在Activity中创建两个对象,让两者协调, 一两个还好,多了就会感觉分布零散,而且冗余难看,为此自定义一个View?还是饶了我吧 Android中控件的组合感觉很笨重,就连点击一下还有先找个id,但我也此心不改,未之乐此不疲,没办法,这就是爱 玩前端接触React的时候我就像寻到新欢,React的组件非常吸引我,灵活,简洁,优雅 再看Android,恨铁不成钢。直到现在Flutter出现了,它带着React的风采出现在移动端,甚至全端 Flutter中对于界面感觉非常友好,虽然刚来时一堆括号的嵌套让人难以适应,但渐渐你会发现他的美 Widget认为界面上的元素都成为组件,使用简单,非常容易复用。 复制代码
5:组件间的组合
看一下Flutter中组合Slider和Text是多么简洁,只要添加一些就行了
如果Android自定义这样的控件,需要自定义ViewGroup,将两个组件拼合
所以Flutter中组件的拼合是非常方便的,使用也很简洁
---->[_TextSliderState#build]---- var show = Column( mainAxisSize: MainAxisSize.min, children: <Widget>[_buildText(_value), _buildSlider(_value)], ); _buildText(double value) { return Text( value.toStringAsFixed(2),//保留两位有效数字 style: TextStyle(fontSize: 20), ); } 复制代码
6:状态的魅力
比如需要象下面这样滑动到50之后复选框选中,当点击复选框清零
放在Android中想想都觉得凌乱,但自定义控件有麻烦,就像炉石起手全是高费的卡手心情
在Flutter中你想怎么封怎么封,只要状态改变,我就给你响应,这是很优雅的。
---->[_TextSliderState#build]---- var show = Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ _buildCheckBox(_value), _buildText(_value), ], ), _buildSlider(_value) ],); _buildCheckBox(double value) { var checked = value > 50; return Checkbox( value: checked, onChanged: (bool) { _render(0); }, ); } 复制代码
只是修改嵌套是有点小麻烦,如果有类似
wedgetChild.father=wedgetFather
这样的认干爹就好了
7:关于修改
你也可以很容易的通过布局容器修改组件的相对位置
var show = Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ _buildCheckBox(_value), _buildText(_value), ], ), _buildSlider(_value) ], ); 复制代码
8.关于监听
要知道你定义的每个组件都是可以拿去复用的,和Flutter原生组件地位是一样的
我们在需要拖动的监听,那么就需要在渲染之前进行回调,让使用者可以接受回参
class TextSlider extends StatefulWidget { ValueChanged onChanged; TextSlider({this.onChanged}); @override State<StatefulWidget> createState() => _TextSliderState(); } typedef ValueChanged = void Function(double value); _render(double value) { if(widget.onChanged!=null){ widget.onChanged(value); } _value = value; print(value); setState(() {}); } 复制代码
9.复用的灵活
一个组件类形成之后,复用就非常方便了,如果Android实现下面的拖动更新
逻辑上不复杂,但是代码将会非常多,因为Android很难复用组件,只能一个个来。
Flutter中实现起来就很简洁,甚至监听也非常方便。比如下面的:
短短几行代码就实现了四个的各自拖动监听,这是笨重的xml所不能及的
var a = (a) { print("a:$a"); }; var b = (b) { print("b:$b"); }; var c = (c) { print("c:$c"); }; var d = (d) { print("d:$d"); }; var childs= [a,b,c,d].map((fun){ return SizedBox.fromSize(size: Size(250, 100), child: TextSlider(onChanged: fun,),); }).toList(); var scaffold = Scaffold( body: Center(child: Wrap(children:childs,),) ); 复制代码
10.小结
Flutter针对界面是非常友好的,它可以作为Android视图很好地补充。
更不用说Flutter强大的跨平台能力,它已成为一颗新星,正冉冉升起。
你还在等什么,见证一下Flutter的魅力吧,相信你会喜欢上它的。