[Flutter必备]-StatefulWidget的打开方式

1,796 阅读5分钟
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的魅力吧,相信你会喜欢上它的。


结语

本文到此接近尾声了,如果想快速尝鲜Flutter,《Flutter七日》会是你的必备佳品;如果想细细探究它,那就跟随我的脚步,完成一次Flutter之旅。
另外本人有一个Flutter微信交流群,欢迎小伙伴加入,共同探讨Flutter的问题,本人微信号:zdl1994328,期待与你的交流与切磋。