Flutter开发五、常用组件与页面布局

目录

widget

State

MaterialApp

Scaffold脚手架容器组件

基础组件

文本组件Text Widget

child里的属性

汇总

手势组件GestureDetector

按钮组件

图片组件Image

child里的属性

colorBlendMode图片混合模式能让图片改变颜色

repeat图片重复铺满

CircleAvatar圆形头像

单选框和复选框

Checkbox

单选开关

裁剪组件

透明度组件opacity

Visibility控制子组件是否可见的组件

容器组件

脚手架-文章开头

Container容器

child里的属性

设置宽、高和颜色属性

adding,margin和decoration内外边距和背景边框

SizeBox装饰类容器

DecoratedBox装饰类容器

SafeArea安全区域

可滚动组件

列表组件ListView

  ​

图片列表

 ​

ScrollDirection横向列表

保持列表项的数据状态

监听列表是否滚动到页面底部

添加/清理 controller 滚动控制器

嵌套组件分离

动态加载列表

List集合

List传递和接收

动态列表ListView.builder()

  ​

网格视图GridView

 ​

布局组件

Row横向布局

Column纵向布局

水平布局:Expanded

弹性布局Flex

流式布局Wrap、Flow

层叠布局Stack、positioned

相对定位Align

补充

AppBar导航条

底部TabBar

Drawer侧边栏抽屉

卡片布局:

宽度撑满:FractionallySizedBox

宽度不撑满:ClipRRect

布局从左到右进行排列,并且自动换行:Wrap

列表ListView

listView下进行动态卡片布局


Flutter万物皆组件(Widget)

前言

widget

StatelessWidgetStatefulWidget是flutter的基础组件,日常开发中自定义Widget都是选择继承这两者之一(并不是只有这两种)。两者的区别在于状态的改变,两种widget都是继承自Widget类。

  • StatelessWidget面向那些始终不变的UI控件,比如标题栏中的标题, 常见的有Container、ScrollView等;
  • 而StatefulWidget则是面向可能会改变UI状态的控件,比如有点击反馈的按钮,常见的有CheckBox、AppBar、TabBar等。
  • 一个StatelessWidget可以用多个不同的BuildContext构建,而一个StatefulWidget会为每个BuildContext创建一个State对象。

StatefulWidget的创建需要指定一个State,在需要更新UI的时候调用setState(VoidCallback fn),并在VoidCallback中改变一些变量数值等,组件会重新build以达到刷新状态也就是刷新UI的效果。

State

作用

    在widget构建的时候可以被同步读取
    在widget的生命周期中可能会被改变

生命周期
    created:当State对象被创建时候,State.initState方法会被调用;
    initialized:当State对象被创建,但还没有准备构建时,State.didChangeDependencies在这个时候会被调用;
    ready:State对象已经准备好了构建,State.dispose没有被调用的时候;
    defunct:State.dispose被调用后,State对象不能够被构建

   

MaterialApp

表示一个应用了 Material 界面风格的应用程序,它封装了应用程序实现 Material Design 所需要的一些widget,大多数项目的界面都应该基于 MaterialApp 进行呈现。

import 'package:flutter/material.dart';
void main () => runApp(MyApp());

class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context ){
      return MaterialApp(
        // 指定应用程序在任务栏上显示的标题
        title:'Text widget',
        // 指定应用程序的主界面
        home:Scaffold(
          body:Center(
            child:Text('Hello JSPang')
          ),
        // 配置应用程序的主题
        theme: ThemeData(primarySwatch: Colors.red),
        ),
      );
  }
}

Scaffold脚手架容器组件

该组件是页面结构的脚手架,包含了页面的基本组成单元,例如:

  • appBar【头部导航条区域】

  • body【页面主题内容区域】

  • drawer【侧边栏抽屉区域】

  • bottomNavigationBar【底部tabBar区域】

  • floatingActionButton【右下角浮动按钮区域】

Scaffold( // 脚手架
    appBar: AppBar(// 导航栏
      title: Text('页面标题'),
    ),
    body: Center(
      child: Text('主体内容'),
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: () {},
      child: Icon(Icons.add),
    ),
    drawer: Drawer(),
    bottomNavigationBar: BottomNavigationBar(// 底部导航栏
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.help_center),
          ),
        ],
        currentIndex: _selectedIndex,
        fixedColor: Colors.lightBlue,
        onTap: (index) {
          print("selected: $index");
          setState(() {
            _selectedIndex = index;
          });
        },
      ),
  ),
  // 主题颜色
  theme: ThemeData(primarySwatch: Colors.red),
)

基础组件

文本组件Text Widget

import 'package:flutter/material.dart';
void main () => runApp(MyApp());

class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context ){
      return MaterialApp(
        title:'Text widget',
        home:Scaffold(
          body:Center(
            child:Text('Hello JSPang')
          ),
        ),
      );
  }
}

child里的属性

textAlign文本的对齐方式

  • center: 文本以居中形式对齐,这个也算比较常用的了。
  • left:左对齐,经常使用,让文本居左进行对齐,效果和start一样。
  • right :右对齐,使用频率也不算高。
  • start:以开始位置进行对齐,类似于左对齐。
  • end: 以为本结尾处进行对齐,不常用。有点类似右对齐.
child:Text(
  'Hello',
  textAlign:TextAlign.left,
)

maxLines:设置最多显示的行数,溢出部分默认不显示,可以通过overflow设置

'Hello',  
 textAlign:TextAlign.left,
 maxLines: 1,

overflow:溢出文字显示方式

  • clip:直接切断,剩下的文字就没有了,不太友好。
  • ellipsis:在后边显示省略号,体验性较好,常用。
  • fade: 溢出的部分会进行一个渐变消失的效果,上线的渐变,不是左右。
'Hello',  
textAlign:TextAlign.left,
overflow:TextOverflow.ellipsis,
maxLines: 1,

style属性的内容比较多可参考api

TextStyle里的属性:

  • fontSize:文字大小
  • color:设置文字颜色,一般用color:color.fromARGB()
  • decoration: TextDecoration.underline,//下划线,也有其他的
  • decorationStyle: TextDecorationStyle.solid,//实线

效果:字体大小为25.0,颜色为粉红色,并且有一个下划线。

  'Hello',  
  textAlign:TextAlign.left,
  overflow:TextOverflow.ellipsis,
  maxLines: 1,
  style: TextStyle(
    fontSize:25.0,
    color:Color.fromARGB(255, 255, 150, 150),
    decoration:TextDecoration.underline,
    decorationStyle:TextDecorationStyle.solid,
  ),

汇总

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';//一般要引入这俩文件
 
void main() => runApp(MyApp());//dart语法,箭头函数,单表达式返回语句简写
 
class MyApp extends StatelessWidget {//继承这个类是固定的
  @override//覆写父类的Build方法
  Widget build(BuildContext context) {//这里下面写的东西都是组件
    return MaterialApp(//返回这个组件
      title: "Text Widget",
      home: Scaffold(//home为窗口本体,scaffold本意为脚手架,其实就是可以搭建里边的内容
        body: Center(//内容在屏幕中间,Center也是一个组件
          child: Text(//Text组件
            "Your life is up to you.",    
            textAlign: TextAlign.left,//文字对齐方式,还有center和right
            maxLines: 2,//最大行数,超出的部分见下行overflow
            overflow: TextOverflow.ellipsis,//ellipsis意思是溢出的文字会以省略号显示
            style: TextStyle(
              fontSize: 30.0,//字体大小,注意一定要加.0
              color: Color.fromARGB(255, 255, 105, 145),//第一个参数是透明度,后面就是RGB颜色
            ),
          ),
        ),
      ),
    );
  }
}

手势组件GestureDetector

GestureDetector(
      child: Text("Text"),
      onTapUp: () {
        print("onTapUp");
      },
      onTapDown: () {
        print("onTapDown");
      },
      onTapCancel: () {
        print("onTapCancel");
      },
      onTap: () {
        print("onTap");
      },
      onDoubleTap: () {
        print("onDoubleTap");
      },
      onLongPress: () {
        print("onLongPress");
      },
    )

按钮组件

Flutter开发六、MaterialButton风格按钮以及按钮自适应

图片组件Image

  • Image.asset:加载资源图片,就是加载项目资源目录中的图片,会增大打包的包体体积,用的是相对路径。
  • Image.network:网络资源图片
  • Image.file:加载本地图片,就是加载本地文件中的图片,这个是一个绝对路径,跟包体无关。
  • Image.memory: 加载Uint8List资源图片。
          child:Container(
            child: Image.network(
              'http://static/myimg/blogtouxiang.jpg',
              scale:1.0,
              // fit: BoxFit.fitWidth,//图片在容器中的填充方式
              color: Colors.pink[50],//需要混合在图片上的颜色
              colorBlendMode:BlendMode.colorBurn,//图片以何种模式与颜色混合
              repeat: ImageRepeat.repeatY,//图片重复,以中心Y轴纵向重复
            ),
            width:300.0,
            height: 700.0,
            color: Colors.pink[100],//容器的颜色,我这里把尺寸设置的超过了屏幕,皆为美观
          ),

child里的属性

fit控制图片拉伸和挤压

  • BoxFit.fill:全图显示,图片会被拉伸,并充满父容器。

  • BoxFit.contain:全图显示,显示原比例,可能会有空隙。

  • BoxFit.cover:显示可能拉伸,可能裁切,充满(图片要充满整个容器,还不变形)。

  • BoxFit.fitWidth:宽度充满(横向充满),显示可能拉伸,可能裁切。

  • BoxFit.fitHeight :高度充满(竖向充满),显示可能拉伸,可能裁切。

  • BoxFit.scaleDown:效果和contain差不多,但是此属性不允许显示超过源图片大小,可小不可大。

            child: Image.network(
              'http://static/myimg/blogtouxiang.jpg',
              scale:1.0,
              fit: BoxFit.fill,
            ),

colorBlendMode图片混合模式能让图片改变颜色

  • color:是要混合的颜色,如果你只设置color是没有意义的。
  • colorBlendMode:是混合模式,相当于我们如何混合。
            child: Image.network(
              'http://static/myimg/blogtouxiang.jpg',
              color: Colors.greenAccent,
              colorBlendMode: BlendMode.darken,
            ),

repeat图片重复铺满

  • ImageRepeat.repeat : 横向和纵向都进行重复,直到铺满整个画布。

  • ImageRepeat.repeatX: 横向重复,纵向不重复。

  • ImageRepeat.repeatY:纵向重复,横向不重复。

            child: Image.network(
              'http://static/myimg/blogtouxiang.jpg',
              repeat: ImageRepeat.repeat,
            ),

CircleAvatar圆形头像

提供圆形的用户头像区域,使用起来比较简单,示例代码如下:

CircleAvatar(
  backgroundImage: NetworkImage(
      'https://images.gitee.com/uploads/91/465191_vsdeveloper.png'),
)

单选框和复选框

Checkbox

Checkbox(
      value: isSelected,
      activeColor: Colors.red,
      onChanged: (value) {
        print(value);
      },
    )

单选开关

Switch(
      value: isSelected,
      activeColor: Colors.red,
      onChanged: (value){
      print(value);
    },)

裁剪组件

Clip的相关组件:
ClipOval:圆形裁剪
ClipRRect:圆角矩形裁剪
ClipRect:矩形裁剪
ClipPath:路径裁剪

ClipOval(
child: SizeBox(
width: 100, height: 100, child: Image.network("http://xxxxx.png")),
)

透明度组件opacity

Opacity(
opacity: 0.5,
child: Text("Text"),
)

Visibility控制子组件是否可见的组件

Visibility(
visible: false,// 子组件是否可见,默认true
child: Text("Text"),// 子组件
replacement: Text("data"),// 不可见时显示的组件(当maintainState=false)
maintainState: true,//不可见时是否维持状态,默认为false
)

容器组件

脚手架-文章开头

Container容器

是一个组合类的容器组件,不参与最终的渲染

Container(容器控件)在Flutter是经常使用的控件,它就相当于我们HTML里的<div>标签,每个页面或者说每个视图都离不开它

class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context ){
      return MaterialApp(
        title:'Text widget',
        home:Scaffold(
          body:Center(
           child:Container(
             
           ),
          ),
        ),
      );
  }
}

child里的属性

alignment属性:容器子内容的对齐方式

  • bottomCenter:下部居中对齐。
  • botomLeft: 下部左对齐。
  • bottomRight:下部右对齐。
  • center:纵横双向居中对齐。
  • centerLeft:纵向居中横向居左对齐。
  • centerRight:纵向居中横向居右对齐。
  • topLeft:顶部左侧对齐。
  • topCenter:顶部居中对齐。
  • topRight: 顶部居左对齐
   child:Container(
     child: Text('Hello',style: TextStyle(fontSize: 40.0),),
     alignment: Alignment.center,
   ),

设置宽、高和颜色属性

width:设置为double.infinity可以强制在宽度上撑满
height:设置为double.infinity可以强制在高度上撑满。

child:Container(
  child: Text('Hello',style: TextStyle(fontSize: 40.0),),
  alignment: Alignment.center,
  width:500.0,
  height:400.0,
  color: Colors.lightBlue,
),

adding,margindecoration内外边距和背景边框

padding内边距为10,EdgeInsets.fromLTRB(value1,value2,value3,value4)分别代表左、上、右、下

  height:400.0,
  color: Colors.lightBlue,
  padding:const EdgeInsets.all(10.0),

margin外边距为10,分开设置也是fromLTRB

margin: const EdgeInsets.all(10.0),

decoration设置背景和边框

背景加入一个渐变(设置了decoration,不要再设置color属性了)

  decoration: BoxDecoration(
    gradient:const LinearGradient(
      colors:[Colors.lightBlue,Colors.greenAccent,Colors.purple]
    )
  ),

红色边框

decoration: BoxDecoration(
    gradient:const LinearGradient(
      colors:[Colors.lightBlue,Colors.greenAccent,Colors.purple]
    ),
    border:Border.all(width:2.0,color:Colors.red)
  ),

SizeBox装饰类容器

可以设置具体尺寸的控件,child不为null时,如果设置宽高,则强制显示宽高,如果没有设置,则自适应;如果child为null时,可当做间隔使用

SizedBox(
      height: 16,
      child: Text("Text"),
    )

DecoratedBox装饰类容器

可以在其子组件绘制前(或者绘制后)绘制一些装饰,如背景、边框、渐变等

DecoratedBox(
      decoration: BoxDecoration(
        color: Colors.white,// 背景色
        borderRadius: BorderRadius.all(Radius.circular(2.0)),// 圆角
        border: Border.all(color: Colors.pink, width: 1),// 边框
        gradient: LinearGradient(colors: [Colors.red, Colors.orange[700]]),// 渐变
        boxShadow: [// 阴影
          BoxShadow(
              color: Colors.black12, offset: Offset(5.0, 5.0), blurRadius: 4.0)
        ],
      ), 
      child: Text("Text"),
    )

SafeArea安全区域

留出刘海和底部的安全区域,很好的解决刘海屏兼容问题

SafeArea(child: Text("Text"))

可滚动组件

列表组件ListView

  • 使用ListView,然后在他的内部children中,使用了widget数组,因为是一个列表,所以它接受一个数组
  • ListView是最常用的可滚动组件之一,它可以沿一个方向线性排布所有子组件,并且它也支持基于Sliver的延迟构建模型

示例

        body: ListView(
            children:<Widget>[
              ListTile(
                  leading: Icon(Icons.access_time),
                  title: Text('hello')
              ),
              ListTile(
                  leading: Icon(Icons.access_time),
                  title: Text('hello')
              ),
            ]
        ),

  

图片列表

            children:<Widget>[
              new Image.network(
                  'https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3289342822,655864970&fm=26&gp=0.jpg'
              ),
              new Image.network(
                  'https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3289342822,655864970&fm=26&gp=0.jpg'
              ),
              ListTile(
                  leading: Icon(Icons.access_time),
                  title: Text('access_time')
              ),
            ]

 

ScrollDirection横向列表

  • Axis.horizontal:横向滚动或者叫水平方向滚动。
  • Axis.vertical:纵向滚动或者叫垂直方向滚动。
  • 不设置垂直滚动
        body: ListView(
            scrollDirection: Axis.horizontal,
            children:<Widget>[
              new Image.network(
                'https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3289342822,655864970&fm=26&gp=0.jpg',
                width: 200,
                height: 200,
              ),
              new Image.network(
                'https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3289342822,655864970&fm=26&gp=0.jpg',
                width: 200,
                height: 200,
              ),
            ]
        ),

 

保持列表项的数据状态

多个列表页之间的动态切换,保持每个列表项的滚动距离、数据状态等信息,可以实现 AutomaticKeepAliveClientMixin特征,来保持列表项的滚动状态

class _MovieListState extends State<MovieList>
    with AutomaticKeepAliveClientMixin {
      // 重写 wantKeepAlive 函数
      @override
      bool get wantKeepAlive => true;
    }

监听列表是否滚动到页面底部

// 定义私有变量 _scrollCtrl
ScrollController _scrollCtrl;
 
// 重写 initState 生命周期函数
@override
void initState() {
  // 此行为默认代码,不能删除
  super.initState();
  // 初始化一个 ScrollController 滚动控制器
  _scrollCtrl = new ScrollController();
  // 为 _scrollCtrl 滚动控制器添加监听事件
  _scrollCtrl.addListener(() {
    // _scrollCtrl.position.pixels 当前列表滚动的距离
    // _scrollCtrl.position.maxScrollExtent 列表的最大滚动距离
    if (_scrollCtrl.position.pixels == _scrollCtrl.position.maxScrollExtent) {
      // 调用 setState 函数,让页码值 +1
      setState(() {
        _page++;
      });
      // 获取新页面的数据
      _getMovieList();
    }
  });
}

添加/清理 controller 滚动控制器

ListView.builder(
  controller: _scrollCtrl,
  itemCount: _mlist.length,
  itemBuilder: (BuildContext ctx, int i) {}
)

...

// 重新 dispose 函数
@override
void dispose() {
  super.dispose();
  // 主动销毁滚动控制器
  _scrollCtrl.dispose();
}

嵌套组件分离

声明一个List的类

import 'package:flutter/material.dart';

class List extends StatelessWidget{
  @override
  Widget build(BuildContext context){
    return ListView(
      scrollDirection: Axis.horizontal,
      children: <Widget>[
        new Container(
          width:180.0,
          color: Colors.lightBlue,
        ), new Container(
          width:180.0,
          color: Colors.amber,
        ), new Container(
          width:180.0,
          color: Colors.deepOrange,
        ),new Container(
          width:180.0,
          color: Colors.deepPurpleAccent,
        ),
      ],
    );
  }
}

在MyAPP类里直接使用这个类

import 'package:flutter/material.dart';
import 'package:untitled/List.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context ){
    return MaterialApp(
      title:'ListView widget',
      home:Scaffold(
        body:Center(
          child:Container(
              height:200.0,
              child: List()
          ),
        ),
      ),
    );
  }
}

  

动态加载列表

List集合

List是Dart的集合类型之一

  • var myList = List(): 非固定长度的声明。
  • var myList = List(2): 固定长度的声明。
  • var myList= List<String>():固定类型的声明方式。
  • var myList = [1,2,3]: 对List直接赋值。

List传递和接收

传递

void main() {
  runApp(MyApp(
      items: List<String>.generate(1000, (i)=> "Item $i")
  ));
}

MyApp类接收

这是一个构造函数,除了Key,增加了一个必传参数,@required意思必传。:super如果父类没有无名无参数的默认构造函数,则子类必须手动调用一个父类构造函数。

  final List<String> items;
  MyApp({Key key, @required this.items}):super(key:key);

动态列表ListView.builder()

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp(
      items: List<String>.generate(1000, (i)=> "Item $i")
  ));
}

class MyApp extends StatelessWidget {
  final List<String> items;
  MyApp({Key key, @required this.items}):super(key:key);

  @override
  Widget build(BuildContext context ){
    print(items);
    return MaterialApp(
      title:'ListView widget',
      home:Scaffold(
        body: ListView.builder(
          itemCount:items.length,
          itemBuilder:(context,index){
            return ListTile(
              title: Text('${items[index]}'),
            );
          }
        )
      ),
    );
  }
}

运行main方法

  

网格视图GridView

GridView,顾名思义就是我们用来做网格的,通常我们用来做相册,或者网格状的展示栏 

  • padding:表示内边距,这个小伙伴们应该很熟悉。
  • crossAxisSpacing:网格间的空当,相当于每个网格之间的间距。
  • crossAxisCount:网格的列数,相当于一行放置的网格数量。
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context ){

    return MaterialApp(
      title:'ListView widget',
      home:Scaffold(
        body: GridView.count(
          padding:const EdgeInsets.all(20.0),
          crossAxisSpacing: 10.0,
          crossAxisCount: 3,
          children: <Widget>[
            Image.network(
              'https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3289342822,655864970&fm=26&gp=0.jpg',
              width: 200,
              height: 200,
            ),
            Image.network(
              'https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3289342822,655864970&fm=26&gp=0.jpg',
              width: 200,
              height: 200,
            ),
            Image.network(
              'https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3289342822,655864970&fm=26&gp=0.jpg',
              width: 200,
              height: 200,
            ),
            const Text('我喜欢玩游戏'),
            const Text('我喜欢看书'),
            const Text('我喜欢吃火锅')
          ],
        )
      ),
    );
  }
}

 

  • childAspectRatio:宽高比,这个值的意思是宽是高的多少倍,如果宽是高的2倍,那我们就写2.0,如果高是宽的2倍,我们就写0.5。希望小伙伴们理解一下。
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context ){

    return MaterialApp(
      title:'ListView widget',
      home:Scaffold(
          body:GridView(
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,//一行放几幅图片
                mainAxisSpacing: 2.0,//行距
                crossAxisSpacing: 2.0,//列距
                childAspectRatio: 0.7,//图片宽高比,因为海报一般长:宽是4:3嘛所以我搞的是这样
            ),
            children: <Widget>[
              Image.network('http://img5.mtime.cn/mt/2018/10/22/104316.77318635_180X260X4.jpg',fit: BoxFit.cover),
              Image.network('http://img5.mtime.cn/mt/2018/10/10/112514.30587089_180X260X4.jpg',fit: BoxFit.cover),
              Image.network('http://img5.mtime.cn/mt/2018/11/13/093605.61422332_180X260X4.jpg',fit: BoxFit.cover),
              Image.network('http://img5.mtime.cn/mt/2018/11/07/092515.55805319_180X260X4.jpg',fit: BoxFit.cover),
              Image.network('http://img5.mtime.cn/mt/2018/11/21/090246.16772408_135X190X4.jpg',fit: BoxFit.cover),
              Image.network('http://img5.mtime.cn/mt/2018/11/17/162028.94879602_135X190X4.jpg',fit: BoxFit.cover),
              Image.network('http://img5.mtime.cn/mt/2018/11/19/165350.52237320_135X190X4.jpg',fit: BoxFit.cover),
              Image.network('http://img5.mtime.cn/mt/2018/11/16/115256.24365160_180X260X4.jpg',fit: BoxFit.cover),
              Image.network('http://img5.mtime.cn/mt/2018/11/20/141608.71613590_135X190X4.jpg',fit: BoxFit.cover),
            ],
          )
      ),
    );
  }
}

  

import 'package:flutter/material.dart';
 
void main() => runApp(MyApp());
 
class MyApp extends StatelessWidget
{
  @override
  Widget build(BuildContext context)
  {
    return MaterialApp(
      title: 'roadkiller',
      home: Scaffold(
        appBar: new AppBar(
          title: new Text('roadkiller'),
        ),
        body:GridView(
            padding: new EdgeInsets.all(3.0),//学过前端好理解吧
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount:2,//一行放几幅图片
                mainAxisSpacing: 2.0,//行距
                crossAxisSpacing: 2.0,//列距
                childAspectRatio: 0.75//图片宽高比,因为海报一般长:宽是4:3嘛所以我搞的是这样
          ),
          children: <Widget>[
            new Image.network('https://ss0.baidu.com/6ONWsjip0QIZ8tyhnq/it/u=3246509238,1077330305&fm=58&s=DFD513C6886286D432620FBC0300301F'),
            new Image.network('https://ss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=2512763846,3067531314&fm=58&s=9102AEFBD3A7E4EE4437527303008074'),
            new Image.network('https://ss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2585813656,2994285865&fm=58&s=C5649A468F378ECC266911AC03008012'),
            new Image.network('https://ss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=3351456143,3085413485&fm=58&s=0CC0F804845BBFCE320951990300C082'),
            new Image.network('https://ss0.baidu.com/6ONWsjip0QIZ8tyhnq/it/u=581555814,2183385336&fm=58&s=2740D94FDE3283DC1259A91301008092'),
            new Image.network('https://ss0.baidu.com/6ONWsjip0QIZ8tyhnq/it/u=3527165871,1016449403&fm=58&s=787B20C402B38BC456651C8D0300E088'),
            new Image.network('https://ss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=2801239214,2575519245&fm=58'),
            new Image.network('https://ss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=3895436776,1493313546&fm=58&s=C115C730589647FF5E89F0C5030070A1'),
            new Image.network('https://ss0.baidu.com/6ONWsjip0QIZ8tyhnq/it/u=3343253172,1276100043&fm=58&s=9DB5109B1C1247F56898AFCA03005037'),
            new Image.network('https://ss0.baidu.com/6ONWsjip0QIZ8tyhnq/it/u=1588364103,2036988885&fm=58&s=DFDDA844965203D4CFA4599303008099'),
          ],
        )
      ),
    );
  }
}

布局组件

上面已讲到容器组件Container,布局出来child:Container()盒子还有RowColumn

Row横向布局

水平方向排布组件,不会自动换行

children【子元素】
mainAxisAlignment【横向对其方式】
crossAxisAlignment【纵向对其方式】

Row(
    mainAxisAlignment: MainAxisAlignment.start,// 主轴对齐方式
    crossAxisAlignment: CrossAxisAlignment.start,// 交叉轴对齐方式
      children: [
        Text("Text1"),
        Text("Text2"),
        Text("Text3"),
        Text("Text4"),
      ],
    )

Column纵向布局

垂直方向排布组件,不会自动换行

children【子元素】
mainAxisAlignment【纵向对其方式】
crossAxisAlignment【横向对其方式】

Column(
      children: [
        Text("Text1"),
        Text("Text2"),
        Text("Text3"),
        Text("Text4"),
      ],
    )

要占满整个竖直方向的屏幕,加Expanded

import 'package:flutter/material.dart';
 
void main() => runApp(MyApp());
 
class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context)
  {
    return MaterialApp(
      title: 'Column Widget',
      home: Scaffold(
        appBar: new AppBar(
          title: new Text('垂直方向布局'),
        ),
        body: Center(
          child:new Column(
            mainAxisAlignment: MainAxisAlignment.center,//主轴是相对于你的布局方向来说的,如果你是水平布局那水平轴就是主轴,如果你是垂直布局那竖直轴就是主轴
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              new RaisedButton(
              onPressed: (){},//这个是如果按了按钮会进行的操作,我们现在还没有就先不写
              color: Colors.lightBlue,
              child: new Text('blue button'),
              ),
              
              new RaisedButton(
              onPressed: (){},
              color: Colors.pink[200],
              child: new Text('pink button'),
              ),
 
              new RaisedButton(
              onPressed: (){},
              color: Colors.grey,
              child: new Text('grey button'),
              )
            ],
          ),
        )
      ),      
    );
  }
}

水平布局:Expanded

  • 可以使Row、Column、Flex等,子组件在其主轴上展开并填充可用空间,撑开父组件。必须使用在Row、Column、Flex等
  • 主要用来控制 flex 布局的占位宽度。需要用在 Row 或 Column 子组件内部。
import 'package:flutter/material.dart';
 
void main() => runApp(MyApp());
 
class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context)
  {
    return MaterialApp(
      title: 'Row Widget',
      home: Scaffold(
        appBar: new AppBar(
          title: new Text('垂直方向布局'),
        ),
        body: new Column(
          children: <Widget>[
            Expanded(
              child:new RaisedButton(
              onPressed: (){},//这个是如果按了按钮会进行的操作,我们现在还没有就先不写
              color: Colors.lightBlue,
              child: new Text('blue button'),
              )
            ),
            Expanded(
              child:new RaisedButton(
              onPressed: (){},
              color: Colors.pink[200],
              child: new Text('pink button'),
              )
            ),
            Expanded(
              child:new RaisedButton(
              onPressed: (){},
              color: Colors.orange,
              child: new Text('orange button'),
              )
            ),
          ],
        ),
      ),      
    );
  }
}

弹性布局Flex

如h5当中的弹性盒子布局,Row和Column都继承自Flex,参数也基本一致所以可以使用Row和Column来代替Flex

Flex(
      direction: Axis.horizontal,
      children: <Widget>[
        Text("Text1"),
        Text("Text2"),
        Text("Text3"),
        Text("Text4"),
      ],
    )

流式布局Wrap、Flow

Wrap和Row类似,如果一行控件不够会自动换行
Flow和Column类似,如果一列控件不够会自动换行

Wrap(
      spacing: 8.0, // 主轴(水平)方向间距
      runSpacing: 8.0, // 纵轴(垂直)方向间距
      alignment: WrapAlignment.center, // 沿主轴方向居中
      children: <Widget>[
        Text("Text1"),
        Text("Text2"),
        Text("Text3"),
        Text("Text4"),
      ],
    )

层叠布局Stack、positioned

  • 类似绝对定位,往往与Positioned联合使用,子组件可以根据父容器四个角的位置来确定自身的位置。绝对定位允许子组件堆叠起来(按照代码中声明的顺序)。
  • 想要在图片上面层叠放一些字或者Container,就需要采用层叠布局,Stack
  • 注意alignment里的FractionalOffset的使用
import 'package:flutter/material.dart';
 
void main() => runApp(MyApp());
 
class MyApp extends StatelessWidget{
  
  @override
  Widget build(BuildContext context)
  {
    var stack = new Stack(//自己写一个Stack组件,减少嵌套,避免恶心
    alignment:const FractionalOffset(0.5, 0.9),//注意里边的参数是从0~1的,原点在左上角,所以这两个参数可以让容器固定在图片的正下方
    children: <Widget>[
      new CircleAvatar(//一个头像
        backgroundImage: NetworkImage('https://i1.hdslb.com/bfs/archive/1791fb4c554b65d4bc2d25dfbe6558bceec9066f.jpg'),//这个引入图片的语法和之前不一样注意
        radius: 100.0,
      ),
      new Container(
        decoration: BoxDecoration(  
          color: Colors.pinkAccent[200],
        ),
        child: Text('鬼舞姬——阿卡丽'),
      )
    ],
  );
    return MaterialApp(
      title: 'Stack Widget',
      home: Scaffold(
        appBar: new AppBar(
          title: new Text('层叠布局'),
        ),
        body: Center(
          child: stack,
        )
      ),      
    );
  }
}

相对定位Align

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

Container(
      height: 120.0,
      width: 120.0,
      color: Colors.blue,
      child: Align(
        alignment: Alignment.topRight,
        child: Text("Text"),
      ),
    )

补充

AppBar导航条

页面的导航条区域,示例代码如下:

appBar: AppBar(
  title: Text('页面标题'),
  // 标题是否居中显示
  centerTitle: true,
  // 右侧的按钮
  actions: <Widget>[
    Padding( // 为 IconButton 添加右 padding 为 10px
      padding: EdgeInsets.only(right: 10),
      child: IconButton( // 右侧的搜索按钮
        icon: Icon(Icons.search),
        onPressed: () {},
      ),
    )
  ],
)

底部TabBar

通常配合 Scaffold 组件的 bottomNavigationBar 属性一起使用,用来渲染底部的 TabBar 效果。

  • TabBar 最好和有状态页面配合使用
  • TabBar 必须指定 TabController 控制器,用来控制 TabBar 的切换
  • 如若不指定 TabController 控制器,也可以使用 DefaultTabController 组件,把 TabBar 组件包裹起来,同时提供需要切换的页面个数即可
DefaultTabController(
  // 指定需要切换的页面个数
  length: tablist.length,
  child: Scaffold(
    appBar: AppBar(),
    // 被 tabbar 控制切换的页面
    body: TabBarView(),
    // 指定 tab 项
    bottomNavigationBar: TabBar(
      tabs: tablist
    )
  ),
)
  • TabController 必须用在拥有 TickerProviderStateMixin 或 SingleTickerProviderStateMixin 特征的类中,因此,必须让 TabController 所在的类实现此特征,
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin { }

Drawer侧边栏抽屉

drawer: Drawer(
  // 抽屉可能在高度上超出屏幕,所以使用 ListView 组件包裹起来,实现纵向滚动效果
  child: ListView(
    // 干掉顶部灰色区域
    padding: EdgeInsets.all(0),
    // 所有抽屉中的子组件都定义到这里:
    children: <Widget>[],
  ))

DrawerHeader头部

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
 
import 'package:flutter_swiper/flutter_swiper.dart';
 
void main() => runApp(new MyApp());
 
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
 
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
 
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
 
  final String title;
 
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}
 
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar:AppBar(title:Text("抽屉")),
      body:Center(child:Text("页面")),
       drawer: Drawer(
            child: Column(
              children: <Widget>[
                Row(
                  children: <Widget>[
                    Expanded(
                      child: DrawerHeader(
                        child: Text('这是头部图片'),
                        decoration: BoxDecoration(
                          image:DecorationImage(
                            image: NetworkImage("https://pic2.zhimg.com/80/v2-40c024ce464642fcab3bbf1b0a233174_hd.jpg"),
                            fit:BoxFit.fill
                          )
                        ),
                    ),)
                  ],
                ),
                ListTile(
                  leading: CircleAvatar(
                    child: Icon(Icons.home),
                  ),
                  title: Text("主页"),
                  onTap: (){
                    //跳转路由代码
                  },
                ),
                Divider(),
                ListTile(
                    leading:CircleAvatar(
                      child: Icon(Icons.folder),
                    ),
                    title:Text("文件")
                ),
                Divider(),
                ListTile(
                  leading: CircleAvatar(
                    child: Icon(Icons.help),
                  ),
                  title: Text("帮助"),
                )
              ],
            )
        ),
      endDrawer:Drawer(
        child: Text("右侧"),
      )
    );
  }
}

卡片布局:

import 'package:flutter/material.dart';
 
void main() => runApp(MyApp());
 
class MyApp extends StatelessWidget{
  
  @override
  Widget build(BuildContext context)
  {
    var card = new Card(
      child: Column(
        children: <Widget>[
          ListTile(
            leading: Icon(Icons.person_add,color: Colors.lightBlue,),
            title: Text('某大学西校区学生公寓六号楼'),
            subtitle: Text('roadkiller:15550313333'),
          ),
          new Divider(),
          ListTile(
            leading: Icon(Icons.person_add,color: Colors.lightBlue,),
            title: Text('某大学西校区学生公寓六号楼'),
            subtitle: Text('路小哥:15550313333'),
          ),
          new Divider(),
          ListTile(
            leading: Icon(Icons.person_add,color: Colors.lightBlue,),
            title: Text('某大学西校区学生公寓六号楼'),
            subtitle: Text('陈小哥:15550313333'),
          ),
        ],
      ),
    );
    return MaterialApp(
      title: 'Stack Widget',
      home: Scaffold(
        appBar: new AppBar(
          title: new Text('卡片布局'),
        ),
        body: Center(
          child: card,
        )
      ),      
    );
  }
}

宽度撑满:FractionallySizedBox

class ListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        FractionallySizedBox(
          widthFactor: 1,//宽度要100%,即撑满
          child: Container(
            decoration: BoxDecoration(
              color: Colors.greenAccent
            ),
            child: Center(
              child: Text('宽度撑满'),
            ),
          ),
        ),
      ],
    );
  }
}

宽度不撑满:ClipRRect

Padding(
    padding: EdgeInsets.all(20),
    child: ClipRRect(
        borderRadius: BorderRadius.all(Radius.circular(40)),
        child: Opacity(
            opacity: 0.6,
            child: Image.network('http://www.devio.org/img/avatar.png'),
        ),
    ),
)

布局从左到右进行排列,并且自动换行:Wrap

Wrap(//Wrap布局会从左到右进行排列,并且自动换行
          spacing: 15,//水平间距
          runSpacing: 10,//竖直间距
          children: <Widget>[
            _chip('C语言'),
            _chip('C++'),
            _chip('Java'),
            _chip('Python'),
            _chip('HTML'),
            _chip('CSS'),
            _chip('JavaScript'),
            _chip('Dart'),
            _chip('Flutter'),
          ],
)
 
 
_chip(String s) {
    return Chip(
      label: Text(s),
      avatar: CircleAvatar(
        backgroundColor: Colors.lightBlueAccent[200],
        child: Text(
          s.substring(0,1),
          style:TextStyle(fontSize: 10),
        ),
      ),
    );
  }

列表ListView

import 'package:flutter/material.dart';
 
void main() => runApp(MyApp());
 
class MyApp extends StatelessWidget
{
  @override
  Widget build(BuildContext context)
  {
    return MaterialApp(
      title: '追风者从不认输',
      home: Scaffold(
        appBar: new AppBar(
          title: new Text('roadkiller'),
        ),
        body: new ListView(//这个东西后面贼有用
          children: <Widget>[//这是一个数组,因为列表就是可以装很多组件啊,注意是children:
            new ListTile(
              leading: new Icon(Icons.add_shopping_cart),
              title: new Text('add_shopping_cart'),
            ),
            new ListTile(
              leading: new Icon(Icons.camera_enhance),
              title: new Text('camera_enhance'),
            ),
            new ListTile(
              leading: new Icon(Icons.restore),
              title: new Text('restore'),
            ),
          ],
        )
      ),
    );
  }
}

继承来减少嵌套

import 'package:flutter/material.dart';
 
void main() => runApp(MyApp());
 
class MyApp extends StatelessWidget
{
  @override
  Widget build(BuildContext context)
  {
    return MaterialApp(
      title: '追风者从不认输',
      home: Scaffold(
        appBar: new AppBar(
          title: new Text('roadkiller'),
        ),
        body:Center(
          child: Container(
            width: 200.0,
            child: MyList(),
          ),
        ), 
      ),
    );
  }
}//嵌套少一些了
 
class MyList extends StatelessWidget{//可以单独把ListView组件写一个类,其实以后无论什么组件都可以的
  @override
  Widget build(BuildContext context)
  {
    return ListView(//返回类型写对
      scrollDirection: Axis.vertical,//这句话好好理解
      children: <Widget>[
        new Container(
          height: 180.0,
          color: Colors.lightBlueAccent,
        ),
        new Container(
          height: 180.0,
          color: Colors.amber,
        ),
        new Container(
          height: 180.0,
          color: Colors.deepOrangeAccent,
        ),
        new Container(
          height: 180.0,
          color: Colors.purpleAccent,
        ),
      ],
    );
  }
}

listView下进行动态卡片布局

List listData=[
  {
    "title":'first',
    "imageUrl":'http://img1.izaoxing.com/allimg/c190122/154Q51262OG0-c029.jpg',
    "description": 'the beautiful'
  },
  {
    "title":'second',
    "imageUrl":'https://pic2.zhimg.com/80/v2-40c024ce464642fcab3bbf1b0a233174_hd.jpg',
    "description": 'the beautiful'
  },
  {
    "title":'third',
    "imageUrl":'https://pic2.zhimg.com/v2-848ed6d4e1c845b128d2ec719a39b275_b.jpg',
    "description": 'the beautiful'
  },
  {
    "title":'fourth',
    "imageUrl":'https://pic1.zhimg.com/50/v2-88b5da103cb64fe9e3f0d7b9d33fcfa4_hd.webp',
    "description": 'the beautiful'
  }
];
import 'package:flutter/material.dart';
import 'package:flutter_app/res/listData.dart';
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
      home: Scaffold(
          appBar: AppBar(
            title: Text("flutterCard"),
          ),
          body: LayoutDemo()),
    );
  }
}

class LayoutDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //TODO: implement build
    return ListView(
        children: listData.map((value){
          return Card(
            margin: EdgeInsets.all(10),
            child: Column(
              children: <Widget>[
                AspectRatio(
                  aspectRatio: 16/9,
                  child: Image.network(value["imageUrl"],fit: BoxFit.fill,),
                ),
                ListTile(
                  leading: CircleAvatar(
                    backgroundImage: NetworkImage(value["imageUrl"]),
                  ),
                  title:Text(value["title"]),
                  subtitle: Text(value["description"])
                )
             ],
          )
          );
        }).toList(),//注意这里要转换成列表,因为listView只接受列表
    );
  }
}

其他:跨平台应用Flutter框架开发:https://blog.csdn.net/qq_44695727/category_11093329.html

Flutter学习路线-按次路线学习顺畅无比:https://jspang.com/detailed?id=58

Flutter实战视频-移动电商 (第69节更新):https://jspang.com/detailed?id=53#toc28

大佬统计的三百多个组件:http://laomengit.com/flutter/widgets/Form.html

在Flutter中构建布局 - Flutter中文网 :https://flutterchina.club/tutorials/layout/#common-layout-widgets

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑶山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值