Flutter基本控件和页面布局

Flutter开发环境搭建好后,就可以开始开发了。

有状态组件与无状态组件

在Flutter 中,一切皆组件:Widget。组件Widget又分为两种:

1.StatefulWidget(有状态组件):如果一个控件,它需要随着用户交互或者内部的值、状态需要根据不同的外部业务变化而改变的话,那么这个组件就需要被设计为有状态的。比如TextField输入随着输入变化而变化。

2.StatelessWidget(无状态组件):对比有状态的组件,无状态的组件是不变的,不需要随着业务变化而变化,比如一个固定的Text文本展示控件,他是不需要随着用户交互而改变的,我们把它定为无状态的。

在Flutter中,没有Controller和Activity的控制器的概念,所有的page也都是一个Widget. 一个页面基本上都是有交互的,所以,一般我们新建一个page都是继承:StatefulWidget,示例代码:

class CommonWebPage extends StatefulWidget {
  final String url;
  //添加构造函数
  CommonWebPage({this.url});

  @override
  State<StatefulWidget> createState() {
    return _CommonWebPageState();
  }
  
}

上面例子中,我们新建了一个名为CommonWebPage的页面,由于它是由状态的,所以必须要复写他的状态方法:createState(),这个状态方法返回的是专门管理这个page的状态类:_CommonWebPageState。
那么这个状态类又是什么呢?下面代码就是这个状态类:

class _CommonWebPageState extends State<CommonWebPage> {
  @override
  Widget build(BuildContext context) {
    return new WebviewScaffold(
      url: widget.url,
      appBar: _navigationBar(),
    );
  }
}

上面代码中,这个状态类继承State,由于这个状态类是为CommonWebPage页面服务的,所以泛型为这个页面。这些是dart语法,前期可以先不用关注dart的语法,先记住这个固定的写法:

先创建一个继承StatefulWidget的页面类,然后在创建一个继承State<page类>,然后复写build()方法。

这个build()其实就是这个页面里面的布局组件了。bulid里面布局的控件即渲染出来的页面样式。

页面状态变化更新机制

前面讲了有状态组件,你肯定会想知道,既然是有状态交互变化,那么状态值变化怎么让页面更新的呢?这里就要说一下setState()。

setState()

Flutter通过setState来重新渲染页面,也就是说:如果代码执行了setState()方法,那么build()会重新执行一遍,build()里面会根据最新的页面显示重新渲染新的页面。【其实这套机制和RN是一样,如果你以前了解过ReactNative的话】。

现在我们举个例子说明:
下图是要给webpage页面加载h5的过程,当页面在加载过程中,中间标题为空,当加载结束后,导航栏上显示标题。

加载中
加载完成

我们首先需要定义一个变量title:

String title; //默认null

我们页面build()代码如下:

@override
  Widget build(BuildContext context) {
    return new WebviewScaffold(
      url: widget.url,
      appBar: _navigationBar(),
    );
  }

PreferredSizeWidget _navigationBar () {
    return PreferredSize(
      child: new Stack(
        children: <Widget>[
          new CupertinoNavigationBar(
            middle: Text(this.title==null?"":this.title),
            border: Border.all(width: 0, color: CupertinoColors.darkBackgroundGray),
          ),
          new Positioned(
            child: _progressBar(),
            left: 0,
            bottom: 0,
            width: window.physicalSize.width/2,
            height: loading ? 2 : 0,
          )
        ],
      ),
      preferredSize: Size(window.physicalSize.width/2,44),
    );
  }

我们关注中间设置标题的代码:

middle: Text(this.title==null?"":this.title),

这句话很好解释,当title为空的时候,标题显示空字符(即不显示),有内容就显示标题内容。当页面加载中的时候,title为空,所以显示成上图一的效果。那么如果在加载完成后,title获取了值后,让页面重新显示成图二呢?

那么我们再页面加载完成,获取title的代码:

//获取h5页面标题
  Future<String> getWebTitle() async {
    String script = 'window.document.title';
    var title = await
    flutterWebViewPlugin.evalJavascript(script);
    setState(() {
      this.title = title;
      print('####################   $title');
    });
    return title;
  }

其中关键代码:

setState(() {
this.title = title;
});

这只title的代码放在了setState()里面,那么该代码执行完成后,会自动触发build()方法重新执行,重新执行到middle: Text(this.title==null?"":this.title),这时候title就已经有值了,页面标题就显示出来了。

页面布局

Flutter页面布局采用的是Flex布局原理,和ReactNative是一样的。如果你之前熟悉Flex布局,那么掌握Flutter页面布局将非常容易上手。

以下面页面为例:

页面布局截图

上图展现的是一个商品列表的布局样式:整体:左右模式(Row); 右边里面:上下模式(Column)。

下面贴出代码:

new Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: [
                  new Container(
                    margin: new EdgeInsets.fromLTRB(0, 0, 12, 10),
                    color: new Color(0xfff5f5f5),
                    width: 100,
                    height: 100,
                    child: new Image(image: NetworkImage(item.imageUrl),fit: BoxFit.cover,)
                  ),
                  Expanded(
                    child: new Container(
                      height: 100,
                      child: rightColomnWidget(index)
                    ),
                  )
                ],
              ),

//右边的列布局
  Widget rightColomnWidget(int index) {
    GoodsModel item = dataList[index];
    return new Column(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      new Text(
                        item.goodsName,
                        style: new TextStyle(
                          color: CupertinoColors.darkBackgroundGray,
                          fontSize: 18,
                          fontWeight: FontWeight.w600,
                        )
                      ),
                      new Text(
                        item.goodsDesc,
                        softWrap: true,
                        maxLines: 2,
                        overflow: TextOverflow.ellipsis,
                        style: new TextStyle(
                          color: CupertinoColors.darkBackgroundGray,
                          fontSize: 15,
                        )
                      ),
                      new Text(
                        item.goodsPrice + '元',
                        style: new TextStyle(
                          color: CupertinoColors.destructiveRed,
                          fontSize: 18,
                          fontWeight: FontWeight.w500,
                        )
                      )
                    ],
                  );
  }

mainAxisAlignment: 主轴方向子元素如何排列。
crossAxisAlignment:次轴方向子元素如何排列。

Container布局里可以设置margin和padding。

只有Row和Column的子元素可以是多个(children);
其他的布局组件(Container, Center,Padding,Expanded)子元素只能一个:child.

Expanded组件表示:父容器剩余的空间应该如何利用:

flex: 0 自己尽量不扩展自己的大小。
flex: 1 占满父容器剩余的空间。

一般我们都是设置flex:1 (默认是1,可以不写)。

列表组件:ListView

ListView是Flutter内置的组件,相当于iOS 中的UITableview.

ListView用法:

new ListView.builder(
        itemCount: dataList.length,
        itemBuilder: (context, index){
          return new Center(
            child: new Container(
              margin: new EdgeInsets.fromLTRB(12, 5, 12, 5),
              child: new Column(
                children: <Widget>[
                  rowWidget(index),
                  //分隔线
                  new Divider(height: 0.5,)
                ],
              )
            ),
          );
        },

itemCount表示列表元素个数;
itemBuilder:迭代的每一行里面的cell布局。

这期暂时讲到这里,下期讲网络请求。

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

推荐阅读更多精彩内容