flutter3.0学习笔记

层叠布局(Stack、Positioned)

Preview
  • 层叠布局(Stack、Positioned)
  • Stack
  • IndexedStack
  • Positioned(绝对定位)
  • 对齐与相对定位(Align)
  • Align组件
  • Alignment
  • FractionalOffset

层叠布局(Stack、Positioned)

层叠布局允许子组件按照代码中声明的顺序堆叠起来。Flutter中使用StackPositioned这两个组件来配合实现绝对定位。Stack允许子组件堆叠,而Positioned用于根据Stack的四个角来确定子组件的位置。

Stack

Stack({
    super.key,
    this.alignment = AlignmentDirectional.topStart,//决定如何去对齐没有定位(没有使用Positioned)或部分定位的子组件
    this.textDirection,//都用于确定alignment对齐的参考系
    this.fit = StackFit.loose,//用于确定没有定位的子组件如何去适应Stack的大小。StackFit.loose表示使用子组件的大小,StackFit.expand表示扩伸到Stack的大小。
    this.clipBehavior = Clip.hardEdge,//决定对超出Stack显示空间的部分如何剪裁,Clip枚举类中定义了剪裁的方式,Clip.hardEdge 表示直接剪裁,不应用抗锯齿
    super.children,
  })

实例:

import 'package:flutter/material.dart';
class StackPage extends StatelessWidget {
  const StackPage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Stack'),),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Center(
          child: Stack(
            alignment: AlignmentDirectional.center,
            children: [
              Container(
                width: 250,
                height: 250,
                color: Colors.amber,
              ),
              Container(
                width: 100,
                height: 400,
                color: Colors.purple,
              ),
              Container(
                width: 400,
                height: 100,
                color: Colors.red,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

image.png

IndexedStack

IndexedStackStack 的子类。与 Stack 不同,IndexedStack 一次最多只显示一个小部件,并隐藏其他小部件。可以通过 index 属性指定要显示的子小部件。如果索引为null,则不会显示任何子小部件。其构造函数:

 IndexedStack({
    super.key,
    super.alignment,
    super.textDirection,
    StackFit sizing = StackFit.loose,
    this.index = 0,//显示子widget的索引,默认为0。如果index为null,则不显示子widget
    super.children,
  })

实例:

class StackPage extends StatefulWidget {
  const StackPage({Key? key}) : super(key: key);

  @override
  State<StackPage> createState() => _StackPageState();
}

class _StackPageState extends State<StackPage> {
  int index = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Stack'),),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Center(
          child: IndexedStack(
            index: index,
            children: [
              // index = 0
              Container(
                width: 400,
                height: 400,
                color: Colors.amber,
                alignment: Alignment.center,
                child: const Text('0',style: TextStyle(fontSize: 50),),
              ),
              // index = 1
              Container(
                width: 400,
                height: 400,
                color: Colors.purple,
                alignment: Alignment.center,
                child: const Text('1',style: TextStyle(fontSize: 50),),
              ),
              // index = 2
              Container(
                width: 400,
                height: 400,
                color: Colors.red,
                alignment: Alignment.center,
                child: const Text('2',style: TextStyle(fontSize: 50),),
              )

            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.refresh_rounded),
        onPressed: (){
          setState(() {
            if(index == 2){
              index = 0;
            }else{
              index = index + 1;
            }
          });
        },
      ),
    );
  }
}

theme (1).gif

Positioned(绝对定位)

 const Positioned({
    super.key,
    this.left,
    this.top,
    this.right,
    this.bottom,
    this.width,
    this.height,
    required super.child,
  })

left、top 、right、 bottom分别代表离Stack左、上、右、底四边的距离。width和height用于指定需要定位元素的宽度和高度。注意,Positioned的width、height 和其他地方的意义稍微有点区别,此处用于配合left、top 、right、 bottom来定位组件。

实例:

import 'package:flutter/material.dart';

class PositionedPage extends StatelessWidget {
  const PositionedPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Positioned'),),
      body: Center(
        child: Container(
          width: 400,
          height: 400,
          decoration: const BoxDecoration(
            shape: BoxShape.circle,
            color: Colors.amber
          ),
          child: Stack(
            children: [
              Positioned(
                top: 0,
                left: 0,
                child: Container(
                  width: 100,
                  height: 100,
                  decoration: const BoxDecoration(
                    shape: BoxShape.circle,
                    color: Colors.purple
                  ),
                )
              ),
              Positioned(
                top: 150,
                left: 150,
                child: Container(
                  width: 200,
                  height: 200,
                  decoration: const BoxDecoration(
                    shape: BoxShape.circle,
                    color: Colors.red
                  ),
                )
              ),
              Positioned(
                  top: -50,
                  left: 100,
                  child: Container(
                    width: 200,
                    height: 200,
                    decoration: const BoxDecoration(
                        shape: BoxShape.circle,
                        color: Colors.green
                    ),
                  )
              )
            ],
          ),
        ),
      ),
    );
  }
}

image.png

对齐与相对定位(Align)

调整一个子元素在父元素中的位置的话,使用Align组件会更简单一些。

Align组件

const Align({
    super.key,
    this.alignment = Alignment.center,
    this.widthFactor,
    this.heightFactor,
    super.child,
  }) 

widthFactorheightFactor是用于确定Align 组件本身宽高的属性;它们是两个缩放因子,会分别乘以子元素的宽、高,最终的结果就是Align 组件的宽高。如果值为null,则组件的宽高将会占用尽可能多的空间。

body: Container(
    color: Colors.green.shade50,
    child: const Align(
      alignment: Alignment.topCenter,
      child: FlutterLogo(
        size: 50.0,
      ),
    ),
  ),

image.png

body: Container(
    color: Colors.green.shade50,
    child: const Align(
      alignment: Alignment.topCenter,
      widthFactor: 2,
      heightFactor: 2,
      child: FlutterLogo(
        size: 50.0,
      ),
    ),
 ),

因为FlutterLogo的宽高为 50,则Align的最终宽高都为2*50=100。

image.png

Alignment

Alignment继承自AlignmentGeometry,表示矩形内的一个点,他有两个属性x、y,分别表示在水平和垂直方向的偏移。

Alignment(this.x, this.y)

坐标转换公式:

(Alignment.x*childWidth/2+childWidth/2, Alignment.y*childHeight/2+childHeight/2)

FractionalOffset

FractionalOffset 继承自 Alignment,它和 Alignment唯一的区别就是坐标原点不同!FractionalOffset 的坐标原点为矩形的左侧顶点,这和布局系统的一致。

实际偏移 = (FractionalOffse.x * childWidth, FractionalOffse.y * childHeight)
body: Container(
    color: Colors.green.shade50,
    height: 120.0,
    width: 120.0,
    child: const Align(
      alignment: FractionalOffset(0.2, 0.6),
      child: FlutterLogo(
        size: 50.0,
      ),
    ),
 ),

FractionalOffset(0.2, 0.6)带入坐标转换公式得FlutterLogo实际偏移为(12,36)。