Flutter构建布局的思路

[TOC]

本文参考链接:在Flutter中构建布局,主要解释一下这里的思路和遇到的坑。

Flutter布局机制简介

Flutter布局机制的核心就是widget。在Flutter中,几乎所有东西都是一个widget - 甚至布局模型都是widget。在Flutter应用中看到的图像、图标和文本都是widget。 甚至你看不到的东西也是widget,例如行(row)、列(column)以及用来排列、约束和对齐这些可见widget的网格(grid)。

要布局的页面

按照文档所述,我们要实现的就是下图页面的效果,将整个widget分割成数值排列的四个子widget,每个widget又可以单独设计。本身少费脑细胞的思路,我们从上向下来看,分别是:一个图片资源widget,一个标题widget,一个按钮widget,一个文本widget,这里概念很清晰,每个子布局都作为一个widget来进行处理。下边就各个击破。
布局分析

Flutter中图片资源加载方式(第一部分)

为了更有针对性,这里只介绍图片资源的加载方式,关于其他资源的加载方式,请参见资源与图像

  1. 图像添加到工程目录中,在project根目录下新建文件夹images,将图片放进去 (注意,wget不能保存此二进制文件);添加图片到项目

  2. 更新 pubspec.yaml 文件以包含 assets 标签. 这样才会使您的图片在代码中可用。更新方法如下:更新位置

  3. 要注意引用位置,不要写错了,不然就会报资源引用错误这里写图片描述

  4. 引用方法:

body: new ListView(
  children: [
    new Image.asset(
      'images/lake.jpg',
      height: 240.0,
      fit: BoxFit.cover,
    ),
    // ...
  ],
)

至此,图片的问题我们就解决了。

标题栏widget设计方式(第二部分)

标题栏

首先,构建标题部分左边栏。将Column(列)放入Expanded中会拉伸该列以使用该行中的所有剩余空闲空间。 设置crossAxisAlignment属性值为CrossAxisAlignment.start,这会将将列中的子项左对齐。
将第一行文本放入Container中,然后底部添加8像素填充。列中的第二个子项(也是文本)显示为灰色。
标题行中的最后两项是一个红色的星形图标和文字“41”。将整行放在容器中,并沿着每个边缘填充32像素

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Widget titleSection = new Container(
      padding: const EdgeInsets.all(32.0),
      child: new Row(
        children: [
          new Expanded(
            child: new Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                new Container(
                  padding: const EdgeInsets.only(bottom: 8.0),
                  child: new Text(
                    'Oeschinen Lake Campground',
                    style: new TextStyle(
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                new Text(
                  'Kandersteg, Switzerland',
                  style: new TextStyle(
                    color: Colors.grey[500],
                  ),
                ),
              ],
            ),
          ),
          new Icon(
            Icons.star,
            color: Colors.red[500],
          ),
          new Text('41'),
        ],
      ),
    );
  //...
}

代码解析:

标题栏

按钮布局widget设计方式(第三部分)

按钮

按钮部分包含3个使用相同布局的列 - 上面一个图标,下面一行文本。该行中的列平均分布行空间, 文本和图标颜色为主题中的primary color,它在应用程序的build()方法中设置为蓝色:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //...

    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),

    //...
}

.>由于三个按钮布局相同,大小一致,我们这里使用嵌套函数进行处理,如buildButtonColumn,它会创建一个颜色为primary color,包含一个Icon和Text的 Widget 列。

 Column buildButtonColumn(IconData icon, String label) {
      Color color = Theme.of(context).primaryColor;
      return new Column(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          new Icon(icon, color: color),
          new Container(
            margin: const EdgeInsets.only(top: 8.0),
            child: new Text(
              label,
              style: new TextStyle(
                fontSize: 12.0,
                fontWeight: FontWeight.w400,
                color: color,
              ),
            ),
          ),
        ],
      );
    }

构建函数将图标直接添加到列(Column)中。将文本放入容器以在文本上方添加填充,将其与图标分开。
通过调用函数并传递icon和文本来构建这些列。然后在行的主轴方向通过 MainAxisAlignment.spaceEvenly 平均的分配每个列占据的行空间。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //...

    Widget buttonSection = new Container(
      child: new Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          buildButtonColumn(Icons.call, 'CALL'),
          buildButtonColumn(Icons.near_me, 'ROUTE'),
          buildButtonColumn(Icons.share, 'SHARE'),
        ],
      ),
    );
  //...
}

文本部分

将文本放入容器中,以便沿每条边添加32像素的填充。softwrap属性表示文本是否应在软换行符(例如句点或逗号)之间断开

  Widget textSection = new Container(
      padding: const EdgeInsets.all(32.0),
      child: new 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放置到ListView中,而不是列中,因为在小设备上运行应用程序时,ListView会自动滚动。

body: new ListView(
  children: [
    new Image.asset(
      'images/lake.jpg',
      width: 600.0,
      height: 240.0,
      fit: BoxFit.cover,
    ),
    titleSection,
    buttonSection,
    textSection,
  ],
),

通过这一步步走来,我们可以看到,其实关于UI布局,我们的处理的东西其实只有一个,那就是widget,我们这里只是根据官方demo给了图片和文本的基本布局和简单的嵌套的方式,更多widget的介绍当然还是参考Widget目录

其实所有的布局全都是在一个widget中进行的,包括方法在内,dart的结构如下所示:
debugPaintSizeEnabled = true;//开启可视化调试

void main() {
  debugPaintSizeEnabled = true;  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
  //
    Widget titleSection = Container(
      padding: const EdgeInsets.all(32.0),
      child: Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
               
      ),
    );

    Column buildButtonColumn(IconData icon, String label) {
    //自定义方法,用来布局那三个按钮
        }

    Widget buttonSection = Container(
    //这里是按钮widget
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          buildButtonColumn(Icons.call, 'CALL'),
          buildButtonColumn(Icons.near_me, 'ROUTE'),
          buildButtonColumn(Icons.share, 'SHARE'),
        ],
      ),
    );

    Widget textSection = Container(
    //这里是文本widget
         );

    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Top Lakes'),
        ),
        body: ListView(
          children: [
          //1、图片
            Image.asset(
              'images/lake.jpg',
              width: 600.0,
              height: 240.0,
              fit: BoxFit.cover,
            ),
           //2、标题栏 
            titleSection,
            //3、按钮
            buttonSection,
            //4、文本
            textSection,
          ],
        ),
      ),
    );
  }
}

拥有单个子元素的布局widget

Container

一个拥有绘制、定位、调整大小的 widget。

      Container(
        child: Container(
          margin: const EdgeInsets.all(10.0),
          color: const Color(0xFF00FF00),
          width: 48.0,
          height: 48.0,
        ),

Padding

一个widget, 会给其子widget添加指定的填充

//将布局约束传递给其子级时,填充会按给定的填充缩小约束,从而导致子级以较小的大小进行布局。
// 填充然后根据其孩子的大小调整自己的尺寸,通过填充物膨胀,有效地在孩子周围创造空的空间。
      Padding(
          padding: EdgeInsets.all(8.0),
        child: const Card(
          child: Text(
            'hello world',
            style: TextStyle(
              fontSize: 18.0,
            ),
          ),
        ),
      )

Center

将其子widget居中显示在自身内部的widget

      Center(
        child: Container(
          height: 100.0,
          width: 100.0,
          color: Colors.yellow,
    可以将其子widget对齐,并可以根据子widget的大小自动调整大小。
          child: Align(
            alignment: FractionalOffset(0.2, 0.6),
            child: Container(
              height: 40.0,
              width: 40.0,
              color: Colors.red,
            ),
          ),
        ),
      )

Align

一个widget,它可以将其子widget对齐,并可以根据子widget的大小自动调整大小。

child: Align(
            alignment: FractionalOffset(0.2, 0.6),
            child: Container(
              height: 40.0,
              width: 40.0,
              color: Colors.red,
            ),
          ),

FittedBox

按自己的大小调整其子widget的大小和位置。

FittedBox(
              fit: BoxFit.contain,
              child: const FlutterLogo(),
            )),

拥有多个子元素的布局widget

Row

在水平方向上排列子widget的列表。要使子项扩展以填充可用的水平空间,请将子项包装在Expanded小部件中

body:Row(
          children: <Widget>[
            Expanded(child: Text('Deliver features faster',textAlign: TextAlign.center,)),
            Expanded(child: Text('Craft beautiful UIs', textAlign: TextAlign.center)),
            Expanded(child: FittedBox(
              fit: BoxFit.contain,
              child: const FlutterLogo(),
            )),
          ],
        )

Column

在垂直方向上排列子widget的列表,要使子项扩展以填充可用的垂直空间,请将子项包装在Expanded小部件中。

Column(
  children: <Widget>[
    Text('Deliver features faster'),
    Text('Craft beautiful UIs'),
    Expanded(
      child: FittedBox(
        fit: BoxFit.contain, // otherwise the logo will be tiny
        child: const FlutterLogo(),
      ),
    ),
  ],
)

Stack

可以允许其子widget简单的堆叠在一起,如果要以简单的方式重叠多个子项,此类很有用,例如,具有一些文本和图像,用渐变覆盖并且按钮附加到底部。

Stack(
  children: <Widget>[
    Container(
      width: 100,
      height: 100,
      color: Colors.red,
    ),
    Container(
      width: 90,
      height: 90,
      color: Colors.green,
    ),
    Container(
      width: 80,
      height: 80,
      color: Colors.blue,
    ),
  ],
)

IndexedStack

从一个子widget列表中显示单个孩子的Stack,显示的子项是具有给定索引的子项。堆栈总是和最大的孩子一样大。
如果value为null,则不显示任何内容。
https://docs.flutter.io/flutter/widgets/IndexedStack-class.html

Flow

一个实现流式布局算法的widget
https://docs.flutter.io/flutter/widgets/Flow-class.html

Table

为其子widget使用表格布局算法的widget
https://docs.flutter.io/flutter/widgets/Table-class.html

ListView

可滚动的列表控件。ListView是最常用的滚动widget,它在滚动方向上一个接一个地显示它的孩子。在纵轴上,孩子们被要求填充ListView。

ListView.builder(
  padding: EdgeInsets.all(8.0),
  itemExtent: 20.0,
  itemBuilder: (BuildContext context, int index) {
    return Text('entry $index');
  },
)
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

class SecondPage extends StatefulWidget {
  String title;
  String url;

  SecondPage(this.title, this.url);

//  final snackBar = new SnackBar(content: new Text(title));
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return SecondPageState();
  }
}

class SecondPageState extends State<SecondPage> {
  int a = 0;

  @override
  void initState() {
    super.initState();
    setState(() {
      a++;
      print('状态改变');
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: AppBar(
          title: Text(
            '$a',
            style: TextStyle(color: Colors.white),
          ),
          iconTheme: IconThemeData(color: Colors.white),
        ),
        body: ListView.builder(
          itemExtent: 20.0,
          padding: EdgeInsets.all(8.0),
          itemCount: data.length,
          itemBuilder: (BuildContext context, int index) =>
              new EntryItem(data[index]),
//            itemBuilder: (BuildContext context, int index) {
//              return Text('entry $index');
//            }
        ));
  }
}

class Entry {
  final String title;
  final List<Entry> children;

  Entry(this.title, [this.children = const <Entry>[]]);
}

final List<Entry> data = <Entry>[
  new Entry('Chapter A', <Entry>[
    new Entry('Section A0', [
      new Entry('Item A0.1'),
      new Entry('Item A0.2'),
      new Entry('Item A0.3'),
    ]),
    new Entry('Section A1'),
    new Entry('Section A2'),
  ]),
  new Entry('Chapter B', <Entry>[
    new Entry('Section B0'),
    new Entry('Section B1'),
  ]),
  new Entry('Chapter C', <Entry>[
    new Entry('Section C0'),
    new Entry('Section C1'),
    new Entry('Section C2', <Entry>[
      new Entry('Item C2.0'),
      new Entry('Item C2.1'),
      new Entry('Item C2.2'),
      new Entry('Item C2.3'),
    ]),
  ]),
];

class EntryItem extends StatelessWidget {
  const EntryItem(this.entry);

  final Entry entry;

  Widget _buildTiles(Entry root) {
    if (root.children.isEmpty)
      return new ListTile(
        title: new Text(root.title),
      );

    return new ExpansionTile(
      key: new PageStorageKey<Entry>(root),
      title: new Text(root.title),
      children: root.children.map(_buildTiles).toList(),
    );
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return _buildTiles(entry);
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值