flutter 应用程序添加交互

准备工作

这里具体布局不做介绍
直接上图

image.png

实现代码

lass MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Column buildButtonColumn(IconData icon, String label){
      Color color =Theme.of(context).primaryColor;
      return new Column(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Icon(
            icon,
            color:color
          ),
          Container(
            margin: const EdgeInsets.only(top: 8.0),
            child: Text(
              label,
              style: TextStyle(
                fontSize: 12.0,
                fontWeight: FontWeight.w400,
                color: color
              ),
            ),
          )
        ],
      );
    }
    Widget titleSection =Container(
      padding: const EdgeInsets.all(32.0),
      child: Row(
        children: <Widget>[Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Container(
              padding: EdgeInsets.only(bottom: 8.0),
              child: Text(
                'Oeschinen Lake Campground',
                style:TextStyle(
                  fontWeight:FontWeight.bold,
                )
              ),
            ),
            Text(
              'Kandersteg,switzerland',
              style:TextStyle(
                color:Colors.grey[500],
              ),
            )],
          ),
        ),
        Icon(
          Icons.star,
          color: Colors.red[500],
        ),
        Text('41')],
      ),
    );

     Widget buttonScetion =Container(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: <Widget>[
          buildButtonColumn(Icons.call,"Call"),
          buildButtonColumn(Icons.near_me, "route"),
          buildButtonColumn(Icons.share, "share")

        ],
      ),
    );

    Widget textSection =Container(
      padding: const EdgeInsets.all(32.0),
      child: Text(
        '''
          Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese Alps. Situated 1,578 meters above sea level, it is one of the larger Alpine Lakes. A gondola ride from Kandersteg, followed by a half-hour walk through pastures and pine forest, leads you to the lake, which warms to 20 degrees Celsius in the summer. Activities enjoyed here include rowing, and riding the summer toboggan run.

        ''',
        softWrap: true,

      ),
    );

    Widget titleImage = Image.asset(
            'images/1.jpg',
             height: 240.0,
             fit: BoxFit.cover,
          );

    return  MaterialApp(
      title: "flutter demo",
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text("welocom to fultter"),
        ),
        body: ListView(
        children: <Widget>[
          titleImage,
          titleSection,
          buttonScetion,
          textSection
        ],
      )
      ),
      
    );
  }


}

当应用第一次启动时,这颗星形图标是实心红色,表明这个湖以前已经被收藏了。星号旁边的数字表示41个人对此湖感兴趣。 完成本教程后,点击星形图标将取消收藏状态,然后用轮廓线的星形图标代替实心的,并减少计数。再次点击会重新收藏,并增加计数。


为了实现这个,您将创建一个包含星号和计数的自定义widget,它们都是widget。 因为点击星形图标会更改这两个widget的状态,所以同一个widget应该同时管理这两个widget。

Stateful(有状态) 和 stateless(无状态) widgets

  • 有些widgets是有状态的, 有些是无状态的
  • 如果用户与widget交互,widget会发生变化,那么它就是有状态的.
  • widget的状态(state)是一些可以更改的值, 如一个slider滑动条的当前值或checkbox是否被选中.
  • widget的状态保存在一个State对象中, 它和widget的布局显示分离。
  • 当widget状态改变时, State 对象调用setState(), 告诉框架去重绘widget.

stateless widget 没有内部状态. IconIconButton, 和Text 都是无状态widget, 他们都是 StatelessWidget的子类。

stateful widget 是动态的. 用户可以和其交互 (例如输入一个表单、 或者移动一个slider滑块),或者可以随时间改变 (也许是数据改变导致的UI更新). Checkbox, Radio, Slider, InkWell, Form, and TextField 都是 stateful widgets, 他们都是 StatefulWidget的子类。

创建一个有状态的widget

  • 要创建一个自定义有状态widget,需创建两个类:StatefulWidget和State
  • 状态对象包含widget的状态和build() 方法。
  • 当widget的状态改变时,状态对象调用setState(),告诉框架重绘widget

在本节中,您将创建一个自定义有状态的widget。 您将使用一个自定义有状态widget来替换两个无状态widget - 红色实心星形图标和其旁边的数字计数 - 该widget用两个子widget管理一行:IconButton和Text。

实现一个自定义的有状态widget需要创建两个类:

  • 定义一个widget类,继承自StatefulWidget.
  • 包含该widget状态并定义该widget build()方法的类,它继承自State.

第一步: 决定哪个对象管理widget的状态

Widget的状态可以通过多种方式进行管理,但在我们的示例中,widget本身(FavoriteWidget)将管理自己的状态。 在这个例子中,切换星形图标是一个独立的操作,不会影响父窗口widget或其他用户界面,因此该widget可以在内部处理它自己的状态。

第二步: 创建StatefulWidget子类

FavoriteWidget类管理自己的状态,因此它重写createState()来创建状态对象。 框架会在构建widget时调用createState()。在这个例子中,createState()创建_FavoriteWidgetState的实例,您将在下一步中实现该实例。

第三步: 创建State子类

自定义State类存储可变信息 - 可以在widget的生命周期内改变逻辑和内部状态。 当应用第一次启动时,用户界面显示一个红色实心的星星形图标,表明该湖已经被收藏,并有41个“喜欢”。状态对象存储这些信息在_isFavorited和_favoriteCount变量

状态对象也定义了build方法。此build方法创建一个包含红色IconButton和Text的行。 该widget使用IconButton(而不是Icon), 因为它具有一个onPressed属性,该属性定义了处理点击的回调方法。IconButton也有一个icon的属性,持有Icon。

按下IconButton时会调用_toggleFavorite()方法,然后它会调用setState()。 调用setState()是至关重要的,因为这告诉框架,widget的状态已经改变,应该重绘。 _toggleFavorite在: 1)实心的星形图标和数字“41” 和 2)虚心的星形图标和数字“40”之间切换UI。

class _FavoriteWidgetState extends State<FavoriteWidget> {
  bool _isFavorited = true;
  int _favoriteCount = 41;

  void _toggleFavorite() {
    setState(() {
      // If the lake is currently favorited, unfavorite it.
      if (_isFavorited) {
        _favoriteCount -= 1;
        _isFavorited = false;
        // Otherwise, favorite it.
      } else {
        _favoriteCount += 1;
        _isFavorited = true;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        new Container(
          padding: new EdgeInsets.all(0.0),
          child: new IconButton(
            icon: (_isFavorited
                ? new Icon(Icons.star)
                : new Icon(Icons.star_border)),
            color: Colors.red[500],
            onPressed: _toggleFavorite,
          ),
        ),
        new SizedBox(
          width: 18.0,
          child: new Container(
            child: new Text('$_favoriteCount'),
          ),
        ),
      ],
    );
  }
}

第四步: 将有stateful widget插入widget树中

将您自定义stateful widget在build方法中添加到widget树中。首先,找到创建图标和文本的代码,并删除它:

删除

在相同的位置创建stateful widget:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Widget titleSection = new Container(
      // ...
      child: new Row(
        children: [
          new Expanded(
            child: new Column(
              // ...
          ),
          new FavoriteWidget(),
        ],
      ),
    );

    return new MaterialApp(
      // ...
    );
  }
}

管理状态

谁管理着stateful widget的状态?widget本身?父widget?都会?另一个对象?答案是……这取决于实际情况。 有几种有效的方法可以给你的widget添加互动。作为小部件设计师。以下是管理状态的最常见的方法:

如何决定使用哪种管理方法?以下原则可以帮助您决定:

  • 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父widget管理
  • 如果所讨论的状态是有关界面外观效果的,例如动画,那么状态最好由widget本身来管理.

我们将通过创建三个简单示例来举例说明管理状态的不同方式:TapboxA、TapboxB和TapboxC。 这些例子功能是相似的 - 每创建一个容器,当点击时,在绿色或灰色框之间切换。 _active确定颜色:绿色为true,灰色为false。


widget管理自己的状态

有时,widget在内部管理其状态是最好的。例如, 当ListView的内容超过渲染框时, ListView自动滚动。大多数使用ListView的开发人员不想管理ListView的滚动行为,因此ListView本身管理其滚动偏移量。

_TapboxAState 类:

  • 管理TapboxA的状态.
  • 定义_active:确定盒子的当前颜色的布尔值.
  • 定义_handleTap()函数,该函数在点击该盒子时更新_active,并调用setState()更新UI.
  • 实现widget的所有交互式行为.

实现完整代码


void main() => runApp(new ManagerSelfState());

class  ManagerSelfState extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return  MaterialApp(
      title:'Flutter demo',
      home:Scaffold(
        appBar: AppBar(
          title: Text('Flutter demo'),
        ),
        body: Center(
          child: TapboxA(),
        ),
      )
    );
  }
}

class TapboxA extends StatefulWidget {
  TapboxA({Key key}) :super(key:key);
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _tapboxAState();
  }
}

class _tapboxAState extends State<TapboxA> {
  bool _active = false;
void _handleTap(){
  setState(() {
    _active = !_active;
  });
}

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        child: Center(
          child: Text(
            _active ?"Active":"Inactive",
            style:TextStyle(
              fontSize:32.0,
              color:Colors.white
            )
          ),
        ),
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: _active?Colors.lightGreen[700]:Colors.grey[600],
        ),
      ),

    );
  }
}

父widget管理widget的state

对于父widget来说,管理状态并告诉其子widget何时更新通常是最有意义的。 例如,IconButton允许您将图标视为可点按的按钮。 IconButton是一个无状态的小部件,因为我们认为父widget需要知道该按钮是否被点击来采取相应的处理。

在以下示例中,TapboxB通过回调将其状态导出到其父项。由于TapboxB不管理任何状态,因此它的父类为StatelessWidget。

ParentWidgetState 类:

  • 为TapboxB 管理_active状态.
  • 实现_handleTapboxChanged(),当盒子被点击时调用的方法.
  • 当状态改变时,调用setState()更新UI.

TapboxB 类:

  • 继承StatelessWidget类,因为所有状态都由其父widget处理.
    当检测到点击时,它会通知父widget.

这里发现个小技巧,直接打st 会出现提示,直接生产state 和 stateFulWidget

实现代码

void main() => runApp(new ManagerSuperState());

class ManagerSuperState extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "flutter demo",
      home: Scaffold(
        appBar: AppBar(
          title: Text("super"),
        ),
        body: ParentWidget(),
      ),
    );
  }
}


class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _active =false;
  void _handleTaboxChange(bool newValue){
    setState(() {
      _active =newValue;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Container(
      child: TapBoxB(
        active:_active,
        onChanged:_handleTaboxChange,
      ),
    );
  }
}

class TapBoxB extends StatelessWidget {
  final bool active ;
  final ValueChanged<bool> onChanged;
  TapBoxB({Key key,this.active:false,@required this.onChanged}):super(key:key);
  
  void _handleTap(){
    onChanged(!active);
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleTap,
      child: Container(
        width: 200.0,
        height: 200.0,
        decoration: BoxDecoration(
          color: active?Colors.lightGreen[700]:Colors.red[600],
        ),
        child: Center(
          child: Text(
            active?'Active':"Inactive",
            style:TextStyle(
              fontSize: 32.0,
              color: Colors.white
            )
          ),
        ),
      ),
    );
  }

}

混合管理

对于一些widget来说,混搭管理的方法最有意义的。在这种情况下,有状态widget管理一些状态,并且父widget管理其他状态。

在TapboxC示例中,点击时,盒子的周围会出现一个深绿色的边框。点击时,边框消失,盒子的颜色改变。 TapboxC将其_active状态导出到其父widget中,但在内部管理其_highlight状态。这个例子有两个状态对象_ParentWidgetState和_TapboxCState。

_ParentWidgetState 对象:

  • 管理_active 状态.
  • 实现 _handleTapboxChanged(), 当盒子被点击时调用.
  • 当点击盒子并且_active状态改变时调用setState()更新UI

_TapboxCState 对象:

  • 管理_highlight state.
  • GestureDetector监听所有tap事件。当用户点下时,它添加高亮(深绿色边框);当用户释放时,会移除高亮。
  • 当按下、抬起、或者取消点击时更新_highlight状态,调用setState()更新UI。
  • 当点击时,将状态的改变传递给父widget.
void main() => runApp(new ParentSelfManager());

class ParentSelfManager extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "test",
      home: Scaffold(
        appBar: AppBar(
          title: Text("nihao"),
        ),
        body: ParentAWidget(),
      ),
    );
  }
}

class ParentAWidget extends StatefulWidget {
  @override
  _ParentAWidgetState createState() => _ParentAWidgetState();
}

class _ParentAWidgetState extends State<ParentAWidget> {
  bool _active = false;

void _handleTapboxChanged(bool newValue){
  setState(() {
    _active =newValue;
  });
}

  @override
  Widget build(BuildContext context) {
    return Container(
        child: TabBoxC(
          active: _active,
          onChanged: _handleTapboxChanged,
        ),
    );
  }
}

class TabBoxC extends StatefulWidget {
  final bool active ;
  final ValueChanged<bool> onChanged;
  TabBoxC({Key key,this.active:false,@required this.onChanged}):super(key:key);
  @override
  _TabBoxCState createState() => _TabBoxCState();
}

class _TabBoxCState extends State<TabBoxC> {
  bool _highlight =false;
  void _handleTapDown(TapDownDetails details){
    setState(() {
      _highlight =true;
    });
  }

void _handleTapUp(TapUpDetails details){
  setState(() {
    _highlight =false;
  });
}

void _handleTapCancel(){
  setState(() {
    _highlight =false;
  });
}

void _hanleTap(){
  widget.onChanged(!widget.active);
}


  @override
  Widget build(BuildContext context) {
    return Container(
      child: GestureDetector(
        onTapDown: _handleTapDown,
        onTapUp: _handleTapUp,
        onTap: _hanleTap,
        onTapCancel: _handleTapCancel,
        child: Container(
          width: 200.0,
          height: 200.0,
          decoration: BoxDecoration(
            color: widget.active?Colors.lightBlue[600]:Colors.grey[600],
            border: _highlight?Border.all(
              color: Colors.teal[700],
              width: 10.0,
            ):null
          ),
          child:Center(
            child: Text(
              widget.active?"active":"Inactive",
              style:TextStyle(
                fontSize:32.0,
                color:Colors.white
              )
            ),
          )
        ),
      ),
    );
  }
}

image.png

借鉴文章

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341

推荐阅读更多精彩内容