Flutter | 布局组件(下)

简介: Flutter | 布局组件(下)

Flow


我们一般情况下很少使用 Flow,因为其比较复杂,需要手动对 widget 进行布局,相当于是 android 中的 onLayout 方法。


Flow 主要用于以下需要高度自定义布局或者性能要求较高(如动画中) 的场景,


Flow 有如下优点


性能好:Flow 是一个队子组件尺寸以及位置调整非常高效的控件。Flow 用转换矩阵对子组件进行位置调整的时候进行了优化:在 Flutter 定位过后,如果子组件尺寸发生了变化,在 FlowDelegate 中的 paintChildren() 方法中调用 context.paintChild 进行重绘,而 contextPaintChild 进行重绘的时候使用了转换矩阵,并没有实际调整组件的位置


灵活:由于需要自定实现 FlowDelegate 的 parintChildren() 方法,所以我们需要手动计算每一个组件的位置,因此,可以自定义布局策略


缺点


使用复杂

不能自适应子组件大小,必须通过指定父容器大小或者实现 TestFlowDelegate 的 getSize 返回固定大小

示例


class TestFlowDelegate extends FlowDelegate {
  EdgeInsets margin = EdgeInsets.zero;
  TestFlowDelegate({this.margin});
  @override
  void paintChildren(FlowPaintingContext context) {
    var x = margin.left;
    var y = margin.top;
    //计算每一个自 widget 的位置
    for (int i = 0; i < context.childCount; i++) {
      //获取宽度
      var width = context
          .getChildSize(i)
          .width + x + margin.right;
      //是否需要换行
      print('$width ---- ${context.size.width}');
      if (width < context.size.width) {
        //绘制第一个
        context.paintChild(i, transform: Matrix4.translationValues(x, y, 0));
        x = width + margin.left;
      } else {
        //绘制后面的
        x = margin.left;
        y += context
            .getChildSize(i)
            .height + margin.top + margin.bottom;
        //绘制子widget(有优化)  
        context.paintChild(i, transform: Matrix4.translationValues(x, y, 0));
        x += context
            .getChildSize(i)
            .width + margin.left + margin.right;
      }
    }
  }
  @override
  bool shouldRepaint(covariant FlowDelegate oldDelegate) {
    return oldDelegate != this;
  }
  @override
  Size getSize(BoxConstraints constraints) {
    return Size(double.infinity, 200);
  }
}


class WrapAndFlowTest extends StatelessWidget {
  final List<String> _list = const [
    "爱是你我",
    "一壶老酒",
    "最炫民族风",
    "怒放的生命",
    "再见青春",
    "北京,北京"
  ];
  List<Widget> getMusicList() {
    return _list
        .map((e) => Container(
              width: 140,
              height: 40,
              child: RaisedButton(
                child: Text(e),
                onPressed: () => print(e),
              ),
            ))
        .toList();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("WarpAndFlow"),
        ),
        body: Padding(
          padding: EdgeInsets.all(10),
          child: Flow(
            delegate: TestFlowDelegate(margin: EdgeInsets.all(10)),
            children: getMusicList(),
          ),
        ));
  }
}


可以看到主要的任务就是实现 paintChildren,他的主要任务就是确定每个子 Widget 的位置,由于 Flow 不能自适应 Widget 的大小,所以在 getSize 中返回一个固定大小来指定 Flow 的大小


层叠布局 Stack,Positioned


层叠布局和 Android 中的 FrameLayout 布局是相似的,子组件可以通过父容器的四个角的位置来确定自身的位置。


绝对定位允许子组件堆叠起来(按照代码中声明的顺序)。Flutter 中使用 Stack 和 Positioned 这两个 组件来配合实现决定定位。


Stack 允许组件堆叠,而 Positioned 用于根据 Stack 的四个角来确定子组件的位置


Stack


Stack({
  this.alignment = AlignmentDirectional.topStart,
  this.textDirection,
  this.fit = StackFit.loose,
  this.overflow = Overflow.clip,
  List<Widget> children = const <Widget>[],
})


alignment:此参数决定如何去对齐没有定位**(没有使用 Positioned)** 或部分定位的子组件。


部分定位指的是没有在某一个轴上定位:left ,right 为横轴,top ,bottom 为纵轴,只要包含某个轴上的一个定位属性就算在该轴上有定位


textDirection:和 Row,Column 中的 textDirection 功能一样,都用于确定 alignment 对齐的参考系,即 textDirection 值为 ltr,则 alignment 代表左,end 为右。如果 textDirecion为 rtl,start 则为 有,end 为左


fit:此参数用于确定没有定位的子组件如何使用 Stack 的大小。StackFit.loose 表示使用子组件的大小, expand 表示扩伸到 Stack 的大小


overflow:此属性决定如何显示超出 Stack 显示空间的子组件;值为 Overflow.clip 时,超出部分会被剪裁(隐藏),只为 Overflow.visible 则不会


Positioned


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


left,top,right,bottom 分别代表 tack 四个边的距离,widget 耦合 height 用于指定需要定位元素的宽度和高度。


注意,Positioned 的 widget,height 是用于配合 left,top ,right,bottom 来定位组件,举个例子,在 水平方向是,只能指定 left,right,width 三个属性的两个,如指定 left 和 widget 后,right 会自动推算出 (left+widget),如果同时指定三个属性则会报错,垂直方向同理


栗子:


class StackAndPositionedTest extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("StackAndPositioned"),
        ),
        ///通过 ConstrainedBox 来确保 Stack 占满屏幕
        body: ConstrainedBox(
          constraints: BoxConstraints.expand(),
          child: Stack(
            alignment: Alignment.center,
            children: [
              Container(
                child: Text(
                  "hello world",
                  style: TextStyle(color: Colors.white),
                ),
                color: Colors.red,
              ),
              Positioned(
                left: 18,
                child: Text("I am 345"),
              ),
              Positioned(
                top: 18,
                child: Text("your friend"),
              )
            ],
          ),
        ));
  }
}


代码中第一个组件 hellow world 没有使用 Positioned 组件,所以 会受到 Aligment.center 的约束,所以他在图中是居中显示的。


第二个子组件 I am 345 只指定了 水平方位 left,属于部分定位,即垂直没有定位,那么他在垂直方向上会按照 aligment 进行对齐,即为垂直居中


第三个 your friend 和 第二个一样,只不过是制定了 垂直 top,没有水平定位,则水平方向居中


修改代码如下:


Stack(
  alignment: Alignment.center,
  fit: StackFit.expand,
  children: [
    Positioned(
      left: 18,
      child: Text("I am 345"),
    ),
    Container(
      child: Text(
        "hello world",
        style: TextStyle(color: Colors.white),
      ),
      color: Colors.red,
    ),
    Positioned(
      top: 18,
      child: Text("your friend"),
    )
  ],
)


上面使用了 fit 属性,并且是 expand,表示没有使用定位的子组件会扩伸到 Stack 的大小


由于第二个子组件的宽高和 Stack 一样大,所以就会导致第一个组件被覆盖


第三个组件在最上层,正常显示


对齐与相对定位 Align


通过 Stack 和 Positioned 可以指定一个或多个子组件相对于父元素的各个边进行精确偏移,并且可以重叠,


但是如果只想简单调整一个子组件在父元素中的位置的话,使用 Align 组件会更简单一些


Align


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


Align 组件可以调整子组件的位置,并根据子组件的宽高来确定自身的宽高


aligment:需要一个 AlignmentGeometry 类型的值,表示子组件在父组件中的起始位置,AlignmentGeometry 是一个抽象类,常用的有两个子类 Aligment 和 FractionalOffset

widthFactor 和 heightFactor 用于确定 Align 自身的宽高属性;他们是两个缩放因子,分别会乘以子组件的宽高,最终的结果就是 Align 的宽高,如果为 null,则组件的宽高会占用尽可能多的空间


栗子


class AlignTest extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Align"),
      ),
      body: Container(
        height: 120,
        width: 120,
        color: Colors.blue[50],
        child: Align(
          alignment: Alignment.topRight,
          child: FlutterLogo(
            size: 60,
          ),
        ),
      ),
    );
  }
}


FlutterLogo 是 Fluter sdk 的一个组件,内容就是 Flutter 的商品


在 Container 中 制定了 宽高为 120,如果不指定 Container 的宽高,同时指定 widthFactor 和 heightFactor 为 2也可以达到相同的效果


Alignment.topRight 表示子组件的位置为 顶部右上角,具体的值为 Aligment(1,-1)


Aligment


Aligment 继承自 AligmentGemetry,表示矩形内的一个点,他有两个属性 x,y,分别代表水平和垂直的偏移,定义如下:


Alignment(this.x, this.y)


Aligment 会以矩形的中心点作为坐标的原点(Aligemtn(0.0,0.0)), x,y 的值从 -1 到 1, 分别代表矩形从左到右的距离 和 顶部 到底边的距离。因此 2 个水平/垂直 单位则等于 矩形的宽/高。


如 Aligment(-1,-1) 代表左侧顶点,1,1代表 右侧底部终点;1,-1,则是右侧顶点,即为 Aligment.topRight。 为了使用方便,矩形的原点,四个顶点都已经在 Aligment 中定义了静态常量。


Aligment 可以通过其 坐标转换公式将其坐标转为子元素的具体偏移坐标:


偏移量 = (Aligment.x * childWidth/2 + childWidth /2 , Aligment.y * chilHeight/2 + childHeight /2)


其中 childWidth 为子元素的宽度,childHeight 为子元素的高度


回过头在看一下上面的栗子,我们将 Aligment(1,-1) 带入上面的公式,可知 FlutterLogo 的偏移坐标正是 (60,0)


修改上面的栗子如下:


class AlignTest extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Align"),
      ),
      body: Container(
        // height: 120,
        // width: 120,
        color: Colors.blue[50],
        // child: Align(
        //   alignment: Alignment.topRight,
        //   child: FlutterLogo(
        //     size: 60,
        //   ),
        // ),
        child: Align(
          // 2x60/2+60/2 ,0x60/2 + 60/2
          //=90           =30
          alignment: Alignment(2, 0),
          widthFactor: 2,
          heightFactor: 2,
          child: FlutterLogo(
            size: 60,
          ),
        ),
      ),
    );
  }
}


根据代码中注释的计算,可以得出 x偏移 90,y 偏移30


FractionalOffset


FractionalOffset 继承自 Aligment , 他和 Aligment 的唯一区别就是坐标点不同


FactionalOffset 的坐标原点为矩形左侧顶点,这和系统布局一直,所以理解比较容易一点。他的坐标转换公式为:


偏移=(FractionalOffset.x*childWidth , FractionalOffset * childHeight)


小栗子:


body: Container(
  height: 120,
  width: 120,
  color: Colors.blue[50],
  child: Align(
    alignment: FractionalOffset(0.2, 0.6),
    // 0.2 *60 , 0.6 * 60
    child: FlutterLogo(
      size: 60,
    ),
  ),
)


带入公式,偏移量为 (12,60),结果如下:


Align 和 Stack 对比


Align 和 Stack/Positioned 都可以用于指定子元素相对于父元素的偏移,他们主要区别如下


定位参考系统不同

Stack/Positioned 定位参考的是父容器的四个顶点

Align 则需要先通过参数 alignment 来确定具体的坐标,最终的偏移是通过 aligment 的公式计算出的

Stack 可以有多个子元素,并且可以堆叠,而 Align 只有一个元素,不存在堆叠


Center 组件


Center 组件用来居中子元素,在之前我们已经使用过他了,下面来介绍一下他,Center 定义如下


class Center extends Align {
  const Center({ Key key, double widthFactor, double heightFactor, Widget child })
    : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
}


Center 继承子 Align,它相比 Align 只少了一个 aligment 参数;


由于 Align 中 alignment 值为 center,所以,Center 组件的对齐方式为 Aglinment.center 了


widthFactory 或者 heightFactory 的长度为 null 时表示尽可能占用更多的空间,这点需要特别注意一下


总结


Row / Column


沿水平或者垂直方向排列子组件


Flex


弹性布局,个人感觉有点类似于 Android 线性布局中的 layout_weight 属性,子组件通过 flex 表示当前组件需要占总大小的多少。


流式布局 Wrap/Flow


Wrap 自动排列,可以指定 对齐属性等,超过宽度自动折行


Flow 高度自定义的 Widget,需要手动计算折行位置,排列等,比较适用于高度的自定义


层叠布局 Stack,Positioned


Stack 层叠布局,可以有多个子组件,子组件 Postioned 用于可根据 Stack 的四个角来确定当前组件的位置,没有使用 Positioned ,则会按照 aligment 进行排列


Align


只能有一个子组件,通过 Aligment / FractionalOffset 进行定位


Aligment / FractionalOffset


两者都代表这偏移量 ,FractionalOffset 继承自 Aligment


Aligment 的原点为 Widget 的中心,即中心点为 Aligment(0,0),具体的偏移可根据公式计算


FractionalOffset 的原点为Widget 的左上角顶点,即FractionalOffset(0,0),和系统布局一样。具体偏移需要公式计算


Center


继承自 Align,相比与 Align 少了 aligment 参数,该参数默认为居中


相关文章
|
10天前
|
前端开发 安全 开发工具
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
137 90
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
25天前
|
Dart 前端开发
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
116 75
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
3月前
|
缓存 监控 前端开发
优化 Flutter 应用启动速度的策略,涵盖理解启动过程、资源加载优化、减少初始化工作、界面布局优化、异步初始化、预加载关键数据、性能监控与分析等方面
本文探讨了优化 Flutter 应用启动速度的策略,涵盖理解启动过程、资源加载优化、减少初始化工作、界面布局优化、异步初始化、预加载关键数据、性能监控与分析等方面,并通过案例分析展示了具体措施和效果,强调了持续优化的重要性及未来优化方向。
114 10
|
23天前
|
Dart 前端开发 容器
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
73 18
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
2月前
|
存储 容器
Flutter 构建自适应布局
Flutter 构建自适应布局
Flutter 构建自适应布局
|
3月前
|
开发框架 数据安全/隐私保护 开发者
Flutter 是一款强大的跨平台移动应用开发框架,本文深入探讨了其布局与样式设计
Flutter 是一款强大的跨平台移动应用开发框架,本文深入探讨了其布局与样式设计,涵盖布局基础、常用组件、样式设计、实战应用、响应式布局及性能优化等方面,助力开发者打造精美用户界面。
70 7
|
3月前
|
开发者 容器
Flutter&鸿蒙next 布局架构原理详解
本文详细介绍了 Flutter 中的主要布局方式,包括 Row、Column、Stack、Container、ListView 和 GridView 等布局组件的架构原理及使用场景。通过了解这些布局 Widget 的基本概念、关键属性和布局原理,开发者可以更高效地构建复杂的用户界面。此外,文章还提供了布局优化技巧,帮助提升应用性能。
134 4
|
3月前
|
容器
深入理解 Flutter 鸿蒙版的 Stack 布局:适配屏幕与层叠样式布局
Flutter 的 Stack 布局组件允许你将多个子组件层叠在一起,实现复杂的界面效果。本文介绍了 Stack 的基本用法、核心概念(如子组件层叠、Positioned 组件和对齐属性),以及如何使用 MediaQuery 和 LayoutBuilder 实现响应式设计。通过示例展示了照片展示与文字描述、动态调整层叠布局等高级用法,帮助你构建更加精美和实用的 Flutter 应用。
186 2
|
3月前
Flutter 自定义组件继承与调用的高级使用方式
本文深入探讨了 Flutter 中自定义组件的高级使用方式,包括创建基本自定义组件、继承现有组件、使用 Mixins 和组合模式等。通过这些方法,您可以构建灵活、可重用且易于维护的 UI 组件,从而提升开发效率和代码质量。
160 1
|
3月前
|
开发工具 UED
Flutter&鸿蒙next中封装一个输入框组件
本文介绍了如何创建一个简单的Flutter播客应用。首先,通过`flutter create`命令创建项目;接着,在`lib`目录下封装一个自定义输入框组件`CustomInput`;然后,在主应用文件`main.dart`中使用该输入框组件,实现简单的UI布局和功能;最后,通过`flutter run`启动应用。本文还提供了后续扩展建议,如状态管理、网络请求和UI优化。
121 1

热门文章

最新文章

  • 1
    【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 2
    【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 3
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 4
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
  • 5
    【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 6
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
  • 7
    零基础构建即时通讯开源项目OpenIM移动端-Flutter篇
  • 8
    flutter3-dart3-dymall原创仿抖音(直播+短视频+聊天)商城app系统模板
  • 9
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 10
    【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈