allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例

admin 3个月前 (06-19) 科技 38 0

老孟导读:Flutter中结构组件有水平 / 垂直结构组件( RowColumn )、叠加结构组件( StackIndexedStack )、流式结构组件( Wrap )和 自界说结构组件(Flow)。

水平、垂直结构组件

Row 是将子组件以水平方式结构的组件, Column 是将子组件以垂直方式结构的组件。项目中 90% 的页面结构都可以通过 Row 和 Column 来实现。

将3个组件水平排列:

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

将3个组件垂直排列:

Column(
  mainAxisSize: MainAxisSize.min,
  children: <Widget>[
    Container(
      height: 50,
      width: 100,
      color: Colors.red,
    ),
    Container(
      height: 50,
      width: 100,
      color: Colors.green,
    ),
    Container(
      height: 50,
      width: 100,
      color: Colors.blue,
    ),
  ],
)

在 Row 和 Column 中有一个异常重要的观点:主轴( MainAxis )交织轴( CrossAxis ),主轴就是与组件结构偏向一致的轴,交织轴就是与主轴偏向垂直的轴。

详细到 Row 组件,主轴 是水平偏向,交织轴 是垂直偏向。而 Column 与 Row 正好相反,主轴 是垂直偏向,交织轴 是水平偏向。

明了了 主轴 和 交织轴 观点,我们来看下 mainAxisAlignment 属性,此属性示意主轴偏向的对齐方式,默认值为 start,示意从组件的最先处结构,此处的最先位置和 textDirection 属性有关,textDirection 示意文本的结构偏向,其值包罗 ltr(从左到右) 和 rtl(从右到左),当 textDirection = ltr 时,start 示意左侧,当 textDirection = rtl 时,start 示意右侧,

Container(
  decoration: BoxDecoration(border: Border.all(color: Colors.black)),
  child: Row(
    children: <Widget>[
      Container(
        height: 50,
        width: 100,
        color: Colors.red,
      ),
      Container(
        height: 50,
        width: 100,
        color: Colors.green,
      ),
      Container(
        height: 50,
        width: 100,
        color: Colors.blue,
      ),
    ],
  ),
)

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第1张

玄色边框是Row控件的局限,默认情况下Row铺满父组件。

主轴对齐方式有6种,效果如下图:

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第2张

spaceAround 和 spaceEvenly 区别是:

  • spaceAround :第一个子控件距最先位置和最后一个子控件距末端位置是其他子控件间距的一半。
  • spaceEvenly : 所有间距一样。

和主轴对齐方式相对应的就是交织轴对齐方式 crossAxisAlignment ,交织轴对齐方式默认是居中。Row控件的高度是依赖子控件高度,因此子控件高都一样时,Row的高和子控件高相同,此时是无法体现交织轴对齐方式,修改3个颜色块高划分为50,100,150,这样Row的高是150,代码如下:

Container(
      decoration: BoxDecoration(border: Border.all(color: Colors.black)),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          Container(
            height: 50,
            width: 100,
            color: Colors.red,
          ),
          Container(
            height: 100,
            width: 100,
            color: Colors.green,
          ),
          Container(
            height: 150,
            width: 100,
            color: Colors.blue,
          ),
        ],
      ),
    )

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第3张

主轴对齐方式效果如下图:

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第4张

mainAxisSize 示意主轴尺寸,有 min 和 max 两种方式,默认是 maxmin 示意尽可能小,max 示意尽可能大。

Container(
	decoration: BoxDecoration(border: Border.all(color: Colors.black)),
	child: Row(
		mainAxisSize: MainAxisSize.min,
		...
	)
)

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第5张

看玄色边框,正好包裹子组件,而 max 效果如下:

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第6张

textDirection 示意子组件主轴结构偏向,值包罗 ltr(从左到右) 和 rtl(从右到左)

Container(
  decoration: BoxDecoration(border: Border.all(color: Colors.black)),
  child: Row(
    textDirection: TextDirection.rtl,
    children: <Widget>[
      ...
    ],
  ),
)

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第7张

verticalDirection 示意子组件交织轴结构偏向:

  • up :从底部最先,并垂直堆叠到顶部,对齐方式的 start 在底部,end 在顶部。
  • down: 与 up 相反。
Container(
  decoration: BoxDecoration(border: Border.all(color: Colors.black)),
  child: Row(
    crossAxisAlignment: CrossAxisAlignment.start,
    verticalDirection: VerticalDirection.up,
    children: <Widget>[
      Container(
        height: 50,
        width: 100,
        color: Colors.red,
      ),
      Container(
        height: 100,
        width: 100,
        color: Colors.green,
      ),
      Container(
        height: 150,
        width: 100,
        color: Colors.blue,
      ),
    ],
  ),
)

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第8张

想一想这种效果完全可以通过对齐方式实现,那么为什么还要有 textDirectionverticalDirection 这两个属性,官方API文档已经注释了这个问题

This is also used to disambiguate start and end values (e.g. [MainAxisAlignment.start] or [CrossAxisAlignment.end]).

用于消除 MainAxisAlignment.start 和 CrossAxisAlignment.end 值的歧义的。

叠加结构组件

叠加结构组件包罗 StackIndexedStack,Stack 组件将子组件叠加显示,凭据子组件的顺遂依次向上叠加,用法如下:

Stack(
  children: <Widget>[
    Container(
      height: 200,
      width: 200,
      color: Colors.red,
    ),
    Container(
      height: 170,
      width: 170,
      color: Colors.blue,
    ),
    Container(
      height: 140,
      width: 140,
      color: Colors.yellow,
    )
  ],
)

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第9张

Stack 对未定位(不被 Positioned 包裹)子组件的巨细由 fit 参数决议,默认值是 StackFit.loose ,示意子组件自己决议,StackFit.expand 示意尽可能的大,用法如下:

Stack(
  fit: StackFit.expand,
  children: <Widget>[
    Container(
      height: 200,
      width: 200,
      color: Colors.red,
    ),
    Container(
      height: 170,
      width: 170,
      color: Colors.blue,
    ),
    Container(
      height: 140,
      width: 140,
      color: Colors.yellow,
    )
  ],
)

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第10张

效果只有黄色(最后一个组件的颜色),并不是其他组件没有绘制,而是另外两个组件被黄色组件笼罩。

Stack 对未定位(不被 Positioned 包裹)子组件的对齐方式由 alignment 控制,默认左上角对齐,用法如下:

Stack(
  alignment: AlignmentDirectional.center,
  children: <Widget>[
    Container(
      height: 200,
      width: 200,
      color: Colors.red,
    ),
    Container(
      height: 170,
      width: 170,
      color: Colors.blue,
    ),
    Container(
      height: 140,
      width: 140,
      color: Colors.yellow,
    )
  ],
)

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第11张

通过 Positioned 定位的子组件:

Stack(
  alignment: AlignmentDirectional.center,
  children: <Widget>[
    Container(
      height: 200,
      width: 200,
      color: Colors.red,
    ),
    Container(
      height: 170,
      width: 170,
      color: Colors.blue,
    ),
    Positioned(
      left: 30,
      right: 40,
      bottom: 50,
      top: 60,
      child: Container(
        color: Colors.yellow,
      ),
    )
  ],
)

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第12张

topbottomleftright 四种定位属性,划分示意距离上下左右的距离。

若是子组件跨越 Stack 界限由 overflow 控制,默认是裁剪,下面设置总是显示的用法:

Stack(
  overflow: Overflow.visible,
  children: <Widget>[
    Container(
      height: 200,
      width: 200,
      color: Colors.red,
    ),
    Positioned(
      left: 100,
      top: 100,
      height: 150,
      width: 150,
      child: Container(
        color: Colors.green,
      ),
    )
  ],
)

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第13张

IndexedStack 是 Stack 的子类,Stack 是将所有的子组件叠加显示,而 IndexedStack 通过 index 只显示指定索引的子组件,用法如下:

class IndexedStackDemo extends StatefulWidget {
  @override
  _IndexedStackDemoState createState() => _IndexedStackDemoState();
}

class _IndexedStackDemoState extends State<IndexedStackDemo> {
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        SizedBox(height: 50,),
        _buildIndexedStack(),
        SizedBox(height: 30,),
        _buildRow(),
      ],
    );
  }

  _buildIndexedStack() {
    return IndexedStack(
      index: _index,
      children: <Widget>[
        Center(
          child: Container(
            height: 300,
            width: 300,
            color: Colors.red,
            alignment: Alignment.center,
            child: Icon(
              Icons.fastfood,
              size: 60,
              color: Colors.blue,
            ),
          ),
        ),
        Center(
          child: Container(
            height: 300,
            width: 300,
            color: Colors.green,
            alignment: Alignment.center,
            child: Icon(
              Icons.cake,
              size: 60,
              color: Colors.blue,
            ),
          ),
        ),
        Center(
          child: Container(
            height: 300,
            width: 300,
            color: Colors.yellow,
            alignment: Alignment.center,
            child: Icon(
              Icons.local_cafe,
              size: 60,
              color: Colors.blue,
            ),
          ),
        ),
      ],
    );
  }

  _buildRow() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        IconButton(
          icon: Icon(Icons.fastfood),
          onPressed: () {
            setState(() {
              _index = 0;
            });
          },
        ),
        IconButton(
          icon: Icon(Icons.cake),
          onPressed: () {
            setState(() {
              _index = 1;
            });
          },
        ),
        IconButton(
          icon: Icon(Icons.local_cafe),
          onPressed: () {
            setState(() {
              _index = 2;
            });
          },
        ),
      ],
    );
  }
}

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第14张

流式结构组件

Wrap 为子组件举行水平或者垂直偏向结构,且当空间用完时,Wrap 会自动换行,也就是流式结构。

建立多个子控件做为 Wrap 的子控件,代码如下:

Wrap(
  children: List.generate(10, (i) {
    double w = 50.0 + 10 * i;
    return Container(
      color: Colors.primaries[i],
      height: 50,
      width: w,
      child: Text('$i'),
    );
  }),
)

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第15张

direction 属性控制结构偏向,默以为水平偏向,设置偏向为垂直代码如下:

Wrap(
  direction: Axis.vertical,
  children: List.generate(4, (i) {
    double w = 50.0 + 10 * i;
    return Container(
      color: Colors.primaries[i],
      height: 50,
      width: w,
      child: Text('$i'),
    );
  }),
)

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第16张

alignment 属性控制主轴对齐方式,crossAxisAlignment 属性控制交织轴对齐方式,对齐方式只对有剩余空间的行或者列起作用,例如水平偏向上正好填充完整,则不管设置主轴对齐方式为什么,看上去的效果都是铺满。

说明 :主轴就是与当前组件偏向一致的轴,而交织轴就是与当前组件偏向垂直的轴,若是Wrap的结构偏向为水平偏向 Axis.horizontal,那么主轴就是水平偏向,反之结构偏向为垂直偏向 Axis.vertical ,主轴就是垂直偏向。

Wrap(
	alignment: WrapAlignment.spaceBetween,
	...
)

主轴对齐方式有6种,效果如下图:

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第17张

spaceAroundspaceEvenly 区别是:

  • spaceAround:第一个子控件距最先位置和最后一个子控件距末端位置是其他子控件间距的一半。
  • spaceEvenly:所有间距一样。

设置交织轴对齐代码如下:

Wrap(
	crossAxisAlignment: WrapCrossAlignment.center,
	...
)

若是 Wrap 的主轴偏向为水平偏向,交织轴偏向则为垂直偏向,若是想要看到交织轴对齐方式的效果需要设置子控件的高不一样,代码如下:

Wrap(
  spacing: 5,
  runSpacing: 3,
  crossAxisAlignment: WrapCrossAlignment.center,
  children: List.generate(10, (i) {
    double w = 50.0 + 10 * i;
    double h = 50.0 + 5 * i;
    return Container(
      color: Colors.primaries[i],
      height: h,
      alignment: Alignment.center,
      width: w,
      child: Text('$i'),
    );
  }),
)

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第18张

runAlignment 属性控制 Wrap 的交织抽偏向上每一行的对齐方式,下面直接看 runAlignment 6中方式对应的效果图,

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第19张

runAlignmentalignment 的区别:

  • alignment :是主轴偏向上对齐方式,作用于每一行。
  • runAlignment :是交织轴偏向上将每一行看作一个整体的对齐方式。

spacingrunSpacing 属性控制Wrap主轴偏向和交织轴偏向子控件之间的间隙,代码如下:

Wrap(
	spacing: 5,
    runSpacing: 2,
	...
)

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第20张

textDirection 属性示意 Wrap 主轴偏向上子组件的偏向,取值局限是 ltr(从左到右) 和 rtl(从右到左),下面是从右到左的代码:

Wrap(
	textDirection: TextDirection.rtl,
	...
)

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第21张

verticalDirection 属性示意 Wrap 交织轴偏向上子组件的偏向,取值局限是 up(向上) 和 down(向下),设置代码如下:

Wrap(
	verticalDirection: VerticalDirection.up,
	...
)

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第22张

注重:文字为0的组件是在下面的。

自界说结构组件

大部门情况下,不会使用到 Flow ,但 Flow 可以调整子组件的位置和巨细,连系Matrix4绘制出种种酷炫的效果。

Flow 组件对使用转换矩阵操作子组件经由系统优化,性能异常高效。

基本用法如下:

Flow(
  delegate: SimpleFlowDelegate(),
  children: List.generate(5, (index) {
    return Container(
      height: 100,
      color: Colors.primaries[index % Colors.primaries.length],
    );
  }),
)

delegate 控制子组件的位置和巨细,界说如下 :

class SimpleFlowDelegate extends FlowDelegate {
  @override
  void paintChildren(FlowPaintingContext context) {
    for (int i = 0; i < context.childCount; ++i) {
      context.paintChild(i);
    }
  }

  @override
  bool shouldRepaint(SimpleFlowDelegate oldDelegate) {
    return false;
  }
}

delegate 要继续 FlowDelegate,重写 paintChildrenshouldRepaint 函数,上面直接绘制子组件,效果如下:

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第23张

只看到一种颜色并不是只绘制了这一个,而是叠加笼罩了,和 Stack 类似,下面让每一个组件有一定的偏移,SimpleFlowDelegate 修改如下:

class SimpleFlowDelegate extends FlowDelegate {
  @override
  void paintChildren(FlowPaintingContext context) {
    for (int i = 0; i < context.childCount; ++i) {
      context.paintChild(i,transform: Matrix4.translationValues(0,i*30.0,0));
    }
  }

  @override
  bool shouldRepaint(SimpleFlowDelegate oldDelegate) {
    return false;
  }
}

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第24张

每一个子组件比上一个组件向下偏移30。

仿 掘金-我的效果

效果如下:

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第25张

到拿到一个页面时,先要将其拆分,上面的效果拆分如下:

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第26张

总体分为3个部门,水平结构,红色区域圆形头像代码如下:

_buildCircleImg() {
  return Container(
    height: 60,
    width: 60,
    decoration: BoxDecoration(
        shape: BoxShape.circle,
        image: DecorationImage(image: AssetImage('assets/images/logo.png'))),
  );
}

蓝色区域代码如下:

_buildCenter() {
  return Column(
    mainAxisAlignment: MainAxisAlignment.center,
    crossAxisAlignment: CrossAxisAlignment.start,
    children: <Widget>[
      Text('老孟Flutter', style: TextStyle(fontSize: 20),),
      Text('Flutter、Android', style: TextStyle(color: Colors.grey),)
    ],
  );
}

绿色区域是一个图标,代码如下:

Icon(Icons.arrow_forward_ios,color: Colors.grey,size: 14,),

将这3部门组合在一起:

Container(
  color: Colors.grey.withOpacity(.5),
  alignment: Alignment.center,
  child: Container(
    height: 100,
    color: Colors.white,
    child: Row(
      children: <Widget>[
        SizedBox(
          width: 15,
        ),
        _buildCircleImg(),
        SizedBox(
          width: 25,
        ),
        Expanded(
          child: _buildCenter(),
        ),
        Icon(Icons.arrow_forward_ios,color: Colors.grey,size: 14,),
        SizedBox(
          width: 15,
        ),
      ],
    ),
  ),
)

最终的效果就是最先我们看到的效果图。

水平睁开/收起菜单

使用Flow实现水平睁开/收起菜单的功效,代码如下:

class DemoFlowPopMenu extends StatefulWidget {
  @override
  _DemoFlowPopMenuState createState() => _DemoFlowPopMenuState();
}

class _DemoFlowPopMenuState extends State<DemoFlowPopMenu>
    with SingleTickerProviderStateMixin {
  //动画必须要with这个类
  AnimationController _ctrlAnimationPopMenu; //界说动画的变量
  IconData lastTapped = Icons.notifications;
  final List<IconData> menuItems = <IconData>[
    //菜单的icon
    Icons.home,
    Icons.new_releases,
    Icons.notifications,
    Icons.settings,
    Icons.menu,
  ];

  void _updateMenu(IconData icon) {
    if (icon != Icons.menu) {
      setState(() => lastTapped = icon);
    } else {
      _ctrlAnimationPopMenu.status == AnimationStatus.completed
          ? _ctrlAnimationPopMenu.reverse() //睁开和收拢的效果
          : _ctrlAnimationPopMenu.forward();
    }
  }

  @override
  void initState() {
    super.initState();
    _ctrlAnimationPopMenu = AnimationController(
      //必须初始化动画变量
      duration: const Duration(milliseconds: 250), //动画时长250毫秒
      vsync: this, //SingleTickerProviderStateMixin的作用
    );
  }

//天生Popmenu数据
  Widget flowMenuItem(IconData icon) {
    final double buttonDiameter =
        MediaQuery.of(context).size.width * 2 / (menuItems.length * 3);
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8.0),
      child: RawMaterialButton(
        fillColor: lastTapped == icon ? Colors.amber[700] : Colors.blue,
        splashColor: Colors.amber[100],
        shape: CircleBorder(),
        constraints: BoxConstraints.tight(Size(buttonDiameter, buttonDiameter)),
        onPressed: () {
          _updateMenu(icon);
        },
        child: Icon(icon, color: Colors.white, size: 30.0),
      ),
    );
  }

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

FlowMenuDelegate 界说如下:

class FlowMenuDelegate extends FlowDelegate {
  FlowMenuDelegate({this.animation}) : super(repaint: animation);
  final Animation<double> animation;

  @override
  void paintChildren(FlowPaintingContext context) {
    double x = 50.0; //起始位置
    double y = 50.0; //横向睁开,y稳定
    for (int i = 0; i < context.childCount; ++i) {
      x = context.getChildSize(i).width * i * animation.value;
      context.paintChild(
        i,
        transform: Matrix4.translationValues(x, y, 0),
      );
    }
  }

  @override
  bool shouldRepaint(FlowMenuDelegate oldDelegate) =>
      animation != oldDelegate.animation;
}

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第27张

半圆菜单睁开/收起

代码如下:

import 'dart:math';

import 'package:flutter/material.dart';

class DemoFlowMenu extends StatefulWidget {
  @override
  _DemoFlowMenuState createState() => _DemoFlowMenuState();
}

class _DemoFlowMenuState extends State<DemoFlowMenu>
    with TickerProviderStateMixin {
  //动画需要这个类来夹杂
  //动画变量,以及初始化和销毁
  AnimationController _ctrlAnimationCircle;

  @override
  void initState() {
    super.initState();
    _ctrlAnimationCircle = AnimationController(
        //初始化动画变量
        lowerBound: 0,
        upperBound: 80,
        duration: Duration(milliseconds: 300),
        vsync: this);
    _ctrlAnimationCircle.addListener(() => setState(() {}));
  }

  @override
  void dispose() {
    _ctrlAnimationCircle.dispose(); //销毁变量,释放资源
    super.dispose();
  }

  //天生Flow的数据
  List<Widget> _buildFlowChildren() {
    return List.generate(
        5,
        (index) => Container(
              child: Icon(
                index.isEven ? Icons.timer : Icons.ac_unit,
                color: Colors.primaries[index % Colors.primaries.length],
              ),
            ));
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned.fill(
          child: Flow(
            delegate: FlowAnimatedCircle(_ctrlAnimationCircle.value),
            children: _buildFlowChildren(),
          ),
        ),
        Positioned.fill(
          child: IconButton(
            icon: Icon(Icons.menu),
            onPressed: () {
              setState(() {
                //点击后让动画可前行或回退
                _ctrlAnimationCircle.status == AnimationStatus.completed
                    ? _ctrlAnimationCircle.reverse()
                    : _ctrlAnimationCircle.forward();
              });
            },
          ),
        ),
      ],
    );
  }
}

FlowAnimatedCircle 代码如下:

class FlowAnimatedCircle extends FlowDelegate {
  final double radius; //绑定半径,让圆动起来
  FlowAnimatedCircle(this.radius);

  @override
  void paintChildren(FlowPaintingContext context) {
    if (radius == 0) {
      return;
    }
    double x = 0; //最先(0,0)在父组件的中央
    double y = 0;
    for (int i = 0; i < context.childCount; i++) {
      x = radius * cos(i * pi / (context.childCount - 1)); //凭据数学得出坐标
      y = radius * sin(i * pi / (context.childCount - 1)); //凭据数学得出坐标
      context.paintChild(i, transform: Matrix4.translationValues(x, -y, 0));
    } //使用Matrix定位每个子组件
  }

  @override
  bool shouldRepaint(FlowDelegate oldDelegate) => true;
}

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第28张

交流

老孟Flutter博客地址(330个控件用法):http://laomengit.com

迎接加入Flutter交流群(微信:laomengit)、关注民众号【老孟Flutter】:

allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第29张 allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例 第30张
,

欧博开户网址

欢迎进入欧博开户网址(Allbet Gaming):www.aLLbetgame.us,欧博网址开放会员注册、代理开户、电脑客户端下载、苹果安卓下载等业务。

AllBetGaming声明:该文看法仅代表作者自己,与本平台无关。转载请注明:allbetgmaing下载:【Flutter实战】六大结构组件及半圆菜单案例

网友评论

  • (*)

最新评论

文章归档

站点信息

  • 文章总数:779
  • 页面总数:0
  • 分类总数:8
  • 标签总数:1285
  • 评论总数:468
  • 浏览总数:31839