Flutter学习第三课-布局组件 Wrap和Flow

流式布局
  • 我们在学习 Row 时默认只有一行,如果超出屏幕不会换行。我们把超出屏幕显示范围会自动折行的布局称为流式布局。Flutter中通过Wrap和Flow来支持流式布局,将上例中的Row换成Wrap后溢出部分则会自动换行
Wrap
  Wrap({
    Key key,
    this.direction = Axis.horizontal,
    this.alignment = WrapAlignment.start,
    this.spacing = 0.0,
    this.runAlignment = WrapAlignment.start,
    this.runSpacing = 0.0,
    this.crossAxisAlignment = WrapCrossAlignment.start,
    this.textDirection,
    this.verticalDirection = VerticalDirection.down,
    List<Widget> children = const <Widget>[],
  })
  • direction:主轴(mainAxis)的方向,默认为水平
  • alignment:主轴方向上的对齐方式,默认为start
  • spacing:主轴方向上的间距
  • runAlignment:纵轴方向的对齐方式,默认为start
  • runSpacing:纵轴方向的间距
  • crossAxisAlignment:交叉轴(crossAxis)方向上的对齐方式
  • textDirection:文本方向
  • verticalDirection:定义了children摆放顺序,默认是down
在需要让子控件自动换行的布局场景 Wrap基本可以满足,特别说一点的是Wrap能做到的Flow一定可以实现,只不过会复杂很多。

在这里插入图片描述

/*
  Wrap({
    Key key,
    this.direction = Axis.horizontal, //主轴(mainAxis)的方向,默认为水平
    this.alignment = WrapAlignment.start,//主轴方向上的对齐方式,默认为start
    this.spacing = 0.0,//主轴方向上的间距
    this.runAlignment = WrapAlignment.start,//纵轴方向的对齐方式,默认为start
    this.runSpacing = 0.0,//纵轴方向的间距
    this.crossAxisAlignment = WrapCrossAlignment.start,//交叉轴(crossAxis)方向上的对齐方式
    this.textDirection,//文本方向
    this.verticalDirection = VerticalDirection.down,//定义了children摆放顺序,默认是down
    List<Widget> children = const <Widget>[],
  })
*/
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
        title: "Flutter Demo",
        debugShowCheckedModeBanner: false,
        home: Scaffold(
            appBar: AppBar(
              title: Text("Row Sample"),
            ),
            body: Container(
                alignment: Alignment.topCenter,
                height: 300,
                child: Wrap(
                  spacing: 8.0, // 主轴(水平)方向间距
                  runSpacing: 4.0, // 纵轴(垂直)方向间距
                  alignment: WrapAlignment.center, //沿主轴方向居中
                  children: <Widget>[
                    new Chip(
                      avatar: new CircleAvatar(
                          backgroundColor: Colors.blue, child: Text('A')),
                      label: new Text('Hamilton'),
                    ),
                    new Chip(
                      avatar: new CircleAvatar(
                          backgroundColor: Colors.blue, child: Text('M')),
                      label: new Text('Lafayette'),
                    ),
                    new Chip(
                      avatar: new CircleAvatar(
                          backgroundColor: Colors.blue, child: Text('H')),
                      label: new Text('Mulligan'),
                    ),
                    new Chip(
                      avatar: new CircleAvatar(
                          backgroundColor: Colors.blue, child: Text('J')),
                      label: new Text('Laurens'),
                    ),
                  ],
                ))));
  }
}

Flow
  • Flow用起来远比Wrap麻烦,它的子控件所有的放置位置都需要你自己计算,但是它可以实现更加个性化的需求,我们可以通过delegate属性自己设置子控件排列规则和移动轨迹动画效果。
  Flow({
    Key key,
    @required this.delegate,
    List<Widget> children = const <Widget>[],
  }) 

在这里插入图片描述

/*
  Flow({
    Key key,
    @required this.delegate,//通过矩阵的方式,设置每个子元素的坐标位置。并包含一个动画控制器,
    List<Widget> children = const <Widget>[],
  })
*/
import 'package:flutter/material.dart';
import 'dart:math' as math;

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Flow Example'),
        ),
        body: FlowMenu(),
      ),
    );
  }
}

class FlowMenu extends StatefulWidget {
  @override
  _FlowMenuState createState() => _FlowMenuState();
}

class _FlowMenuState extends State<FlowMenu>
    with SingleTickerProviderStateMixin {
  AnimationController menuAnimation;
  IconData lastTapped = Icons.notifications;
  final List<IconData> menuItems = <IconData>[
    Icons.home,
    Icons.new_releases,
    Icons.notifications,
    Icons.settings,
    Icons.launch,
    Icons.access_alarm,
    Icons.account_circle,
    Icons.cloud_download,
    Icons.menu,
  ];

  void _updateMenu(IconData icon) {
    if (icon != Icons.menu) setState(() => lastTapped = icon);
  }

  @override
  void initState() {
    super.initState();
    menuAnimation = AnimationController(
      duration: const Duration(milliseconds: 250),
      vsync: this,
    );
  }

  Widget flowMenuItem(IconData icon) {
    return RawMaterialButton(
      fillColor: lastTapped == icon ? Colors.amber[700] : Colors.blue,
      splashColor: Colors.amber[100],
      shape: CircleBorder(),
      onPressed: () {
        _updateMenu(icon);
        menuAnimation.status == AnimationStatus.completed
            ? menuAnimation.reverse()
            : menuAnimation.forward();
      },
      child: Icon(
        icon,
        color: Colors.white,
        size: 45.0,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Flow(
        delegate: FlowMenuDelegate(menuAnimation: menuAnimation),
        children: menuItems
            .map<Widget>((IconData icon) => flowMenuItem(icon))
            .toList(),
      ),
    );
  }
}

class FlowMenuDelegate extends FlowDelegate {
  FlowMenuDelegate({this.menuAnimation}) : super(repaint: menuAnimation);

  final Animation<double> menuAnimation;

  @override
  bool shouldRepaint(FlowMenuDelegate oldDelegate) {
    return menuAnimation != oldDelegate.menuAnimation;
  }

  @override
  void paintChildren(FlowPaintingContext context) {
    double dx = 0.0;
    double dy = 0.0;
    double offestX = 0.0;
    double offestY = 0.0;
    double angle = 360 / (context.childCount - 1);
    for (int i = 0; i < context.childCount; ++i) {
      double radius = context.getChildSize(i).width / 2;
      dx = context.size.width / 2 - radius;
      dy = context.size.height / 2 - radius;
      offestX = 2 * radius * math.cos(angle * i * math.pi / 180) * menuAnimation.value;
      offestY = 2 * radius * math.sin(angle * i * math.pi / 180) * menuAnimation.value;
      if (i == context.childCount - 1) {
        context.paintChild(i, transform: Matrix4.translationValues(dx, dy, 0,),
        );
      } else {
        context.paintChild(i, transform: Matrix4.translationValues(dx + offestX, dy + offestY, 0,),
        );
      }
    }
  }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值