Flutter入门Flutter

Flutter入门三:自动布局(Row/Column/Stack

2021-02-04  本文已影响0人  markhetao

Flutter入门 学习大纲

  1. CenterAlignment
  2. 三种布局方式(RowColumnStack
  3. Expanded自动填充和AspectRatio宽高比
  4. StatefulWidget可变组件 与 StatelessWidget不可变组件

import 'package:flutter/material.dart';
import 'layout_demo.dart';

// 入口,展示MyWidget组件
void main() => runApp(App());

// 根组件
class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Home(),
      theme: ThemeData(
        primaryColor: Colors.yellow
      ),
    );
  }
}

// Home 组件
class Home extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[100],
      appBar: AppBar(
        title: Text("Flutter Demo"),
      ),
      body: LayoutDemo(), // 展示LayoutDemo组件
    );
  }
}

1. Center和Alignment

1.1 Center

import 'package:flutter/material.dart';

// 布局Demo
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.yellow,
      // Center: 居中
      child: Center(
        child: Text( "Layout Demo" ),
      ),
    );
  }
}

1.2 Alignment

import 'package:flutter/material.dart';

// 布局Demo
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.yellow,
      //  Alignment: 按比例展示位置
      alignment: Alignment(-1,-1),
      child: Text( "Layout Demo" ),
    );
  }
}

2. 三种布局方式(Row、Column、Stack)

2.1 Row

  • Alignment(0, 0)标注child子容器Row居中对齐
    由于Row水平撑满父容器空间,所以会看到Row子元素没水平居中对齐的假象。 实际上我们Alignment是影响了Row容器水平对齐了,Row子元素的对齐方式,是Row内部处理的。

  • mainAxisAlignment:主轴方向对齐(RowColumn都有这个属性)
    Row中:
    start靠左(默认)
    end: 靠右
    center: 居中
    spaceAround: 剩下空间平均分配周围(每个部件周围等间距)
    spaceBetween:剩下空间平均分配在小部件中间(两边无间距,中间等间距)
    spaceEvenly: 完全等间距

  • crossAxisAlignment:交叉轴方向对齐(RowColumn都有这个属性)
    Row中:
    start居上
    center: 居中(默认)
    end: 居下
    baseline: 文字底部对齐(如果在Column中,必须配合textBaseline使用,后面具体分析)
    stretch: 垂直填充长条

  • Expanded填充式布局,完全等分主轴宽度,会自动换行row宽度无效,column高度无效(后面会具体分析)

import 'package:flutter/material.dart';

// 布局Demo
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.yellow,
      alignment: Alignment(0, 0), // 居中对齐
      // Row 水平(X轴水平方向)
      // Row 是在父控件`Container`的居中展示。内部元素默认是从左到右
      child: Row(
        // 主轴方向(start:靠左(默认),end: 靠右, center: 居中
        //          spaceAround:  剩下空间平均分配在周围(每个部件周围等间距)
        //          spaceBetween: 剩下空间平均分配在小部件中间(两边无间距,中间等间距),
        //          spaceEvenly:  完全等间距)
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        // 交叉轴(y轴方向) (start: 居上,center: 居中(默认), end: 居下,
        //                 baseline: 文字底部对齐,stretch: 垂直填充长条)
        crossAxisAlignment: CrossAxisAlignment.baseline,
        children: <Widget>[
          // 使用Expanded填充式布局,完全等分主轴宽度,会自动换行。row宽度无效,column高度无效
          Expanded(
            child: Container(
                child: Icon(
                  Icons.add,
                  size: 120,
                ),
                color: Colors.red),
          ),
          Expanded(
            child: Container(
                child: Icon(
                  Icons.ac_unit,
                  size: 60,
                ),
                color: Colors.blue),
          ),
          Expanded(
            child: Container(
                child: Icon(
                  Icons.access_alarm,
                  size: 30,
                ),
                color: Colors.white),
          ),
        ],
      ),
    );
  }
}

2.2 Column

  • Alignment对齐方式和mainAxisAlignment主轴对齐、crossAxisAlignment交叉轴对齐与Row类似,只是一个是水平一个是垂直方向。
  • 需要注意crossAxisAlignmenttextBaseline类型,必须配合textBaseline使用(后面具体分析)
import 'package:flutter/material.dart';

// 布局Demo
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.yellow,
      alignment: Alignment(0, 0),
      // Column 垂直(y轴垂直)
      // Column 是在父控件`Container`的居中展示。内部元素是从上到下
      child: Column(
        // 主轴方向(start:靠上(默认),end: 靠下, center: 居中,
        //          spaceAround:  剩下空间平均分配在周围(每个部件周围等间距)
        //          spaceBetween: 剩下空间平均分配在小部件中间(两边无间距,中间等间距),
        //          spaceEvenly:  完全等间距)
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        // 交叉轴(y轴方向) (start: 居左,center: 居中(默认), end: 居右,
        //                 baseline: 文字底部对齐(针对文本,需要配合textBaseLine),
        //                 stretch: 水平填充长条)
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Container(
              child: Icon(
                Icons.add,
                size: 120,
              ),
              color: Colors.red),
          Container(
              child: Icon(
                Icons.ac_unit,
                size: 60,
              ),
              color: Colors.blue),
          Container(
              child: Icon(
                Icons.access_alarm,
                size: 30,
              ),
              color: Colors.white),
        ],
      ),
    );
  }
}
2.2.1 演示baseline(文字底部对齐)

crossAxisAlignment设置为baseline时,需要添加textBaseline属性(Row非必须,但Column必须),可以设置为:

  • alphabetic字母
  • ideographic中文 (好像没区别)
import 'package:flutter/material.dart';

// 布局Demo
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.yellow,
      // 演示baseline(文字底部对齐)
      alignment: Alignment(0,0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        // row不会报错,但是Column会报错,必须指定textBaseline
        crossAxisAlignment: CrossAxisAlignment.baseline,
        // 文本基线textBaseline: alphabetic:英文字符  ideographic: 中文字符
        textBaseline: TextBaseline.alphabetic,
        children: <Widget>[
          Container(
            child: Text("你好!", style: TextStyle(fontSize: 20)),
            color: Colors.red,
            height: 80
          ),
          Container(
              child: Text("我是", style: TextStyle(fontSize: 30)),
              color: Colors.green,
              height: 80
          ),
          Container(
              child: Text("哈哈哈", style: TextStyle(fontSize: 40)),
              color: Colors.blue,
              height: 80
          )],
      )
    );
  }
}
image.png

2.3 Stack

import 'package:flutter/material.dart';

// 布局Demo
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.yellow,
      alignment: Alignment(0, 0),
      //  Stack 重叠 (Z轴纵深方向)
      // Stack 是在父控件`Container`的居中展示。内部元素是从里到外
      child: Stack(
        children: <Widget>[
          Container(
              child: Icon(
                Icons.add,
                size: 120,
              ),
              color: Colors.red),
          Container(
              child: Icon(
                Icons.ac_unit,
                size: 60,
              ),
              color: Colors.blue),
          Container(
              child: Icon(
                Icons.access_alarm,
                size: 30,
              ),
              color: Colors.white),
        ],
      ),
    );
  }
}
2.3.1 positioned

第一个子控件左上角展示,第二个子控件右下角展示,第三个子控件靠右,离顶部60像素展示

import 'package:flutter/material.dart';

// 布局Demo
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
        color: Colors.yellow,
      alignment: Alignment(0, 0),
      child: Container(
        width: 200,
        height: 200,
        color: Colors.white,
        child: getStack(),
      )
    );
  }

  // 获取Stack组件
  Widget getStack() {
    return Stack(
      children: <Widget>[
        // 左上(默认)
        Positioned(
          child:
          Container(
              child: Icon(
                Icons.add,
                size: 120,
              ),
              color: Colors.red),
        ),
        // 右下
        Positioned(
          right: 0,
          bottom: 0,
          child:
          Container(
              child: Icon(
                Icons.ac_unit,
                size: 60,
              ),
              color: Colors.blue),
        ),
        // 右上(距离顶部60像素)
        Positioned(
          right: 0,
          top: 60,
          child:
          Container(
              child: Icon(
                Icons.access_alarm,
                size: 30,
              ),
              color: Colors.white),
        ),
      ],
    );
  }
}

3. Expanded自动填充和AspectRatio宽高比

3.1 Expanded自动填充

上面提到Expanded填充式布局,完全等分主轴宽度,会自动换行。(row宽度无效,column高度无效)

  1. 组件内所有子组件都是Expanded,布局情况如何?
  2. 组件内Expanded其他组件共存,布局情况如何?

1. 全Expanded

// 布局Demo
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
        color: Colors.yellow, alignment: Alignment(0, 0), child: getRow());
  }

  // 获取Row组件
  Widget getRow() {
    return Row(children: <Widget>[
      Expanded(
        child: Container(
            child: Text("你好!你好!你好!你好!你好!", style: TextStyle(fontSize: 20)),
            color: Colors.red),
      ),
      Expanded(
        child: Container(
            child: Text("我是我是我是我是我是", style: TextStyle(fontSize: 30)),
            color: Colors.green),
      ),
      Expanded(
        child: Container(
            child: Text("哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈", style: TextStyle(fontSize: 40)),
            color: Colors.blue),
      ),
    ]);
  }
}

2. Expanded与其他组件共存

import 'package:flutter/material.dart';

// 布局Demo
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
        color: Colors.yellow, alignment: Alignment(0, 0), child: getRow());
  }

  // 获取Row组件
  Widget getRow() {
    return Row(children: <Widget>[
      Expanded(
        child: Container(
            child: Text("你好!你好!你好!你好!你好!", style: TextStyle(fontSize: 20)),
            color: Colors.red),
      ),
      // 第二个子组件不是`Expanded`
      Container(
          child: Text("我是我是我是我是我是我是", style: TextStyle(fontSize: 30)),
          color: Colors.green),
      Expanded(
        child: Container(
            child: Text("哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈", style: TextStyle(fontSize: 40)),
            color: Colors.blue),
      ),
    ]);
  }
}

如果超过屏幕宽度,我们需要对宽高设置

3.2 AspectRatio 宽高比

import 'package:flutter/material.dart';

// 布局Demo
class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
        color: Colors.yellow,
        alignment: Alignment(0, 0),
        child: Container(
          color: Colors.blue,
          width: 300,
          // AspectRatio 宽高比部件
          child: AspectRatio(
            aspectRatio: 2/1,   // 设置宽高比
            child: Icon(Icons.add),
          ),
        )
    );
  }
}

4. StatefulWidget可变部件 与 StatelessWidget不可变部件

所以实际上可变部件不可变部件的区别就是可变部件 多记录状态值,在UI上,本质都是通过创建销毁 不可变部件来进行展示。

4.1 重写计数器Demo

4.1.1 main.dart创建
import 'package:flutter/material.dart';
import 'package:hello_flutter/state_manager_demo.dart';

// 入口,展示MyWidget组件
void main() => runApp(App());

// 根组件
class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 返回`MaterialApp`
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: StateManagerDemo(), // 指定根视图为`StateManagerDemo`
      theme: ThemeData(
        primaryColor: Colors.blue
      ),
    );
  }
}
4.1.2 StateManagerDemo创建(StatelessWidget不可变部件)
import 'package:flutter/material.dart';

class StateManagerDemo extends StatelessWidget {

  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("StateManagerDemo"),
      ),
      body: Center(
        child: Chip(label: Text("$count"),), // 全圆角的组件
      ),
      // 浮动按钮(默认右下角)
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        // 点击触发: 给一个无参回调函数,可以直接给`(){}`,也可以外部创建一个函数,传入函数名
        // 每次点击,count+1,打印count值
        onPressed: (){
          count += 1;
          print("count = $count");
        },
      )
    );
  }
}
  • 当我们在StatelessWidget不可变部件中创建变量未使用final修饰,而使用var修饰时,会有提示:
    This class (or a class that this class inherits from) is marked as '@immutable', but one or more of its instance fields aren't final: StateManagerDemo.count
    image.png
    我们忽略不管(暂时不管,后面做比较),继续运行,可以看到界面运行正常:
    image.png
image.png

我们发现,不用final修饰变量count,也可以正常运行,并且count完成了计数任务,但是界面UI未更新

  • 如果我们使用final声明变量,就等同于swift中的let声明,是不可变的,不能进行count+=1的操作。

实际上,这是Flutter底层渲染原理决定的,StatelessWidget不可变部件,本身不支持记录状态,每次都是销毁重新渲染UI,所以编译器提示我们不可变部件内全部使用final修饰。这样从编码层减少错误产生

  • 如果一定需要记录状态,请使用StatefulWidget可变部件。
4.1.3 StateManagerDemo创建(StatefulWidget可变部件)
import 'package:flutter/material.dart';

class StateManagerDemo extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // 直接返回State根据最新数据build的组件
    return _StateManagerState();
  }
}

class _StateManagerState extends State<StateManagerDemo> {
  // 记录计数
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("StateManagerDemo"),
        ),
        body: Center(
          child: Chip(label: Text("$count"),), // 全圆角的组件
        ),
        // 浮动按钮(默认右下角)
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          // 点击触发: 给一个无参回调函数,可以直接给`(){}`,也可以外部创建一个函数,传入函数名
          // 每次点击,count+1,打印count值
          onPressed: (){
            count += 1; // 数据变动
            setState(() {}); // 更新状态(自动触发UI的重新渲染)
            print("count = $count");
          },
        )
    );
  }
}

总结:

  • StatelessWidget不可变部件:

    直接build返回UI部件即可,变量都使用final修饰不可更改。每次通过构造方法传入新数据,都会进行旧部件销毁新部件创建

  • StatefulWidget 可变部件:

  1. State组件管理状态,并build返回最新状态UI部件。每次状态变更,需要setState(() {})更新UI部件
    (渲染机制会自动销毁旧UI部件创建新UI部件State的生命周期与StatefulWidget一致)

  2. StatefulWidget可变部件只需要返回State对象即可
    (实际是返回Statebuild最新UI部件

【思考】

实际上,在Flutter开发中我们需要注意一些优化点

  • 我们整个页面,其实只有+号按钮点击触发中心的计数UI更新。并不需要每次数据变化,都更新整个外部部件
  • 我们可以将Chip更新为一个StatefulWidget可变部件,点击+号时,通过回调Chip部件重新渲染即可。

本节,我们主要分析自动布局几种方式,以及StatefulWidgetStatefulWidget的区别。
下一节,Flutter入门四:搭建项目、资源调用、简单开发。从项目入手,一步步理解熟悉Flutter

上一篇下一篇

猜你喜欢

热点阅读