Flutter从入门到入土【总】

Flutter的使用

Flutter


Flutter是谷歌基于Dart语言开发的一款开源、免费且跨平台的App开发框架,所以建议先学习Dart语言的基本语法,很像JavaScriptJava,学起来很快。这篇文章打算直接总结如何使用Flutter框架,所以比较长,但是讲解的东西比较简短

环境配置

安装JDK


  1. 前往Java Downloads | Oracle下载所需要的jdkJDK17版本够用了

    在这里插入图片描述

  2. 配置系统环境

    1. 新建系统变量,第一行输入JAVA_HOME,第二行输入安装jdk的目录

    在这里插入图片描述

    1. 在系统变量中找到Path双击点开,点击新建,并输入%JAVA_HOME%\bin

      在这里插入图片描述

    2. 一直点确定,然后再cmd中输入java -version,一般来说出现的就是版本号,若不行,重启下电脑,若还不行,重新安装吧!

      在这里插入图片描述

安装Android Studio


  1. 前往下载 Android Studio ,并安装

  2. 安装完毕之后,点击左边的第三项,插件,我这里汉化了,汉化教程

    在这里插入图片描述

  3. 搜索并安装这两个插件,然后重启Android Studio

    在这里插入图片描述

安装FlutterSDK


  1. 前往Flutter官网下载FlutterSDKFlutter SDK archive | Flutter

    在这里插入图片描述

  2. 将下载好的压缩包解压,然后配置环境,双击系统变量的Path,新建并输入刚刚解压到的文件夹到bin目录的路径

    在这里插入图片描述

  3. cmd中输入flutter doctor,等待一下,出现这些

    在这里插入图片描述

    前三项和倒数三项不是x一般就能用,如果第三项出现问题,先安装Android Studio

  4. 常见问题

    若第三项出现问题,先安装Android Studio
    Android-SDK的系统环境,未配置,在系统变量中新建并第一行输入ANDROID_HOME,第二行输入你安装Android Studio时,安装Android-SDK的路径,然后重启电脑,一般就行了

    在这里插入图片描述
    如果flutter doctor时出现cmdline-tools component is missing
    根据以下步骤:

    1. 打开Android Studio,关闭项目,到开始页面,点最右边的竖着的三个点,选择SDK Manager

      在这里插入图片描述

    2. 点第二项SDK Tools,在下面找到Android SDK Command-line Tools并勾选,点击确定

      在这里插入图片描述

    3. 点击确定*[我这里因为已经最新了,所以下载的东西与你们出问题的不一样]*

      在这里插入图片描述

    4. 等待这个界面下载,下载完成就点完成

      在这里插入图片描述

    如果还不行,推荐重新安装Android SDK!

demo


github
gitee

创建Flutter项目并运行

创建


  1. 一切准备好之后,会看到Android Studio右上角有个New Flutter Project的按钮,点这个按钮是为了创建Flutter项目

    在这里插入图片描述

  2. 点进去之后,右边点击Flutter,然后下一步

  3. 这里就是项目的基本信息

    • 项目名称:随便你想怎么起怎么起
    • 项目路径:看你想放哪
    • 项目描述:这一条无所谓
    • 项目类型:想做app就选Application
    • 所属组织:随你
    • 安卓系统的编译语言:一般选第二项
    • IOS系统的编译语言:一般选第二项
    • 所在平台:一般全选

    然后填好之后,点左下角创建

    在这里插入图片描述

    最后,我们需要编写的文件,就在lib

    运行

    先点击右上角的手机图标,然后选择Edge,再点击运行,如果没有这种运行,左边的项目找到项目的目录,然后点开lib,再双击**.dart文件**,再编辑器右键点击小三角运行

    在这里插入图片描述

flutter的基本要素


  1. 创建第一个flutter,需要导入一个包package:flutter/material.dart

  2. 使用main函数,让他可以运行**runApp()**方法

  3. runApp()需要填写容器,这里先使用Center

  4. Center里面需要填写child

  5. Text()即是创建一个文本到Center

  6. Text里可以有textDirection为让文字从左到右[ltr]还是从右到左[rtl]

  7. Text里的style可以改变文字样式

    import 'package:flutter/material.dart';
    //demo1-Center
    void main() {
      runApp(const Center(
        child: Text(
          "hello world!",
          textDirection: TextDirection.ltr,
          style: TextStyle(
            fontSize: 20.0,
            color: Colors.brown
          ),
        )
      ));
    }
    

MaterialApp and Scaffold

MaterialApp常用的属性


属性名用处
home主页
title标题
color颜色
theme主题
routes路由

MaterialApp-home


填写容器,一般以Scaffold为主,Scaffold是与MaterialApp的另一种组件

import 'package:flutter/material.dart';
// demo2-MaterialApp and Scaffold
void main(){
  runApp(MaterialApp(
    home: Scaffold()
  ));
}

MaterialApp-title|color


MaterialApp的标题和颜色,但是一般不用

MaterialApp-theme


整个软件的主题,填写的参数有要求

  • ThemeData.light():白色主题
  • ThemeData.dark():黑色主题
import 'package:flutter/material.dart';
// demo2-MaterialApp and Scaffold
void main(){
  runApp(MaterialApp(
    theme:ThemeData.dark()
  ));
}

MaterialApp-routes


none

Scaffold的常用属性


  • appBar - 用于显示顶部部分
  • body - 用于显示主要内容
  • drawer - 左边抽屉菜单

Scaffold-appBar


如何使用appBar

import 'package:flutter/material.dart';
// demo2-MaterialApp and Scaffold
void main(){
  runApp(MaterialApp(
    home:Scaffold(
      appBar:AppBar(
        title:Text("demo2")
      )
  ));
}

其实appBar还有一些属性

  • backgroundColor - 用于设置背景颜色
  • centerTitle - 是否居中文字
import 'package:flutter/material.dart';
// demo2-MaterialApp and Scaffold
void main(){
  runApp(MaterialApp(
    home:Scaffold(
      appBar:AppBar(
        title:Text("demo2")
      ),
      backgroundColor: Colors.lightGreen,
      centerTitle: true,
    )
  ));
}

Scaffold-body


主体部分,填写容器就行了,这里用了Center

import 'package:flutter/material.dart';
// demo2-MaterialApp and Scaffold
void main(){
  runApp(MaterialApp(
    home: Scaffold(
      body: const Center(
        child: Text(
          "Hello world!",
          textDirection: TextDirection.ltr,
          style: TextStyle(
              color: Colors.greenAccent,
              fontSize: 30.0
          ),
        ),
      ),
    )
  ));
}

Scaffold-drawer


  1. 编写一个简单的界面

    import 'package:flutter/material.dart';
    //demo4-Drawer
    void main(){
      runApp(MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: const Text(
                "demo4-Drawer",
                textDirection: TextDirection.ltr
            ),
            backgroundColor: Colors.blue,
            titleTextStyle:const TextStyle(
                color: Colors.amber
            ),
          ),
          drawer:
        ),
      ));
    }
    
  2. darwer需要填写与之相应的Darwer()

    里面常用的两个属性

    • backgroundColor - 抽屉菜单的背景颜色
    • child - 抽屉菜单的内容
    import 'package:flutter/material.dart';
    //demo4-Drawer
    void main(){
      runApp(MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: const Text(
                "demo4-Drawer",
                textDirection: TextDirection.ltr
            ),
            backgroundColor: Colors.blue,
            titleTextStyle:const TextStyle(
                color: Colors.amber
            ),
          ),
          drawer:const Drawer(
            backgroundColor: Colors.lightBlue,
            child:Text(
              "DrawerTitle",
              textDirection: TextDirection.ltr,
              style: TextStyle(
                color: Colors.greenAccent,
                fontSize: 30.0,
              ),
            ),
          ),
        ),
      ));
    }
    
  3. 总体代码

    import 'package:flutter/material.dart';
    //demo4-Drawer
    void main(){
      runApp(MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: const Text(
                "demo4-Drawer",
                textDirection: TextDirection.ltr
            ),
            backgroundColor: Colors.blue,
            titleTextStyle:const TextStyle(
                color: Colors.amber
            ),
          ),
          body: HomeComp(),
          drawer:const Drawer(
            backgroundColor: Colors.lightBlue,
            child:Text(
              "DrawerTitle",
              textDirection: TextDirection.ltr,
              style: TextStyle(
                color: Colors.greenAccent,
                fontSize: 30.0,
              ),
            ),
          ),
        ),
      ));
    }
    
    
    class HomeComp extends StatelessWidget{
      // const HomeComp ({Key ? key}) : super(key: key);
      
      Widget build(BuildContext context) {
        // TODO: implement build
        return const Center(
          child: Text(
            "Hello world",
            textDirection: TextDirection.ltr,
            style: TextStyle(
              color: Colors.green,
    
            ),
          ),
        );
      }
    
    }
    

分离组件


我们在编写软件的时候,不会把所有的东西功能和界面全部写在一起,那样会让我们看起来很烦躁,因为非常的乱

  1. 先编写一个普通的界面,现在,代码是报错的,因为body还没有填写东西

    import 'package:flutter/material.dart';
    //demo3-分离组件
    void main(){
      runApp(MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: const Text("demo3-分离组件"),
            backgroundColor: Colors.blue,
            titleTextStyle:const TextStyle(
              color: Colors.amber
            ),
          ),
          body: ,
        ),
      ));
    }
    
  2. 在main方法下面,编写一个组件类,让这个类继承StatelessWidget,此时会报错,将鼠标放上去,让系统自动帮你把方法写出来

    const HomeComp({super.key});这一行一般都是这样的形式,照写就行

    class HomeComp extends StatelessWidget{
      const HomeComp({super.key});
      
      Widget build(BuildContext context) {
       
      }
    }
    
  3. 在继承的方法返回容器即可

    class HomeComp extends StatelessWidget{
      const HomeComp({super.key});
    
      
      Widget build(BuildContext context) {
        // TODO: implement build
        return const Center(
          child: Text(
            "Hello world",
            style: TextStyle(
              color: Colors.green,
            ),
          ),
        );
      }
    }
    
  4. 总体代码就是这样

    import 'package:flutter/material.dart';
    //demo3-分离组件
    void main(){
      runApp(MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: const Text("demo3-分离组件"),
            backgroundColor: Colors.blue,
            titleTextStyle:const TextStyle(
              color: Colors.amber
            ),
          ),
          body: const HomeComp(),
        ),
      ));
    }
    
    
    class HomeComp extends StatelessWidget{
      const HomeComp({super.key});
    
      
      Widget build(BuildContext context) {
        // TODO: implement build
        return const Center(
          child: Text(
            "Hello world",
            style: TextStyle(
              color: Colors.green,
            ),
          ),
        );
      }
    }
    

基础组件

Container容器组件

alignment的参数


参数说明
Alignment.topCenter顶部居中对齐
Alignment.topLeft顶部左对齐
Alignment.topRight顶部右对齐
Alignment.topRight顶部右对齐
Alignment.center水平垂直居中对齐
Alignment.centerLeft垂直居中水平居左对齐
Alignment.centerRight垂直居中水平居右对齐
Alignment.bottomCenter底部居中对齐
Alignment.bottomLeft底部居左对齐
Alignment.bottomRight底部居右对齐

decoration的属性


先使用BoxDecoration(),里面的参数为

属性说明
gradient渐变 LinearGradient()为背景线性渐变 RadialGradient()为径向渐变,这两个方法里面需要color参数,可以书数组
boxShadow阴影 需要定义一个常量数组,数组里面需要BoxShadow(),里面有color[颜色]、offset[位移,参数为Offset(x, y)]、blurRadius[虚化程度]
border边框 需要使用Border.all(),里面有color[颜色]、width[线条大小]
borderRadius盒子圆角 需要使用BorderRadius.circular(),参数就是double类型的数字

其他属性


属性说明
margin容器与外部的距离,参数使用EdgeInsets.all(double)或者EdgeInsets.fromLTRB(double,double,double,double)
padding容器边框与child的距离,同上
transform让容器可以位移旋转
`heightwidth`
child子元素

Text组件

textAlign参数


文字对齐方式

参数说明
TextAlign.center文字居中
TextAlign.left文字左对齐
TextAlign.right文字右对齐
TextAlign.justfy文字两端对齐

textDirection参数


文字方向

参数说明
TextDirection.ltr从左至右
TextDirection.rtl从右至左

overflow参数


文字超出屏幕之后的处理方式

参数说明
TextOverflow.visible强制显示
TextOverflow.clip裁剪隐藏
TextOverflow.fade渐隐
TextOverflow.ellipsis省略号

textScaler参数


textScaleFactor已弃用,用textScaler代替,用于字体显示倍率

参数说明
TextScaler.linear(double)字体显示倍率,1为正常

maxLines参数


显示最大行数

  • 填写整数即可

style属性


需要搭配TextStyle()使用,以下是TextStyle()属性

  • decoration:文字的装饰线

    参数说明
    TextDecoration.none无线条
    TextDecoration.lineThrough删除线
    TextDecoration.overline上划线
    TextDecoration.underline下划线
  • decorationColor:装饰线的颜色

  • decorationStyle:装饰线样式

    参数说明
    TextDecorationStyle.dashed横杆虚线
    TextDecorationStyle.dotted点虚线
    TextDecorationStyle.double双线
    TextDecorationStyle.solid实线
    TextDecorationStyle.wavy波浪线
  • wordSpacing单词间隙,负值会让单词变得更紧凑

  • letterSpacing字母间隙,负值,会让字母变得更紧凑

  • fontStyle:文字样式

    参数说明
    FontStyle.normal正常字体
    FontStyle.italic斜体
  • fontSize:文字大小

  • color:文字颜色

  • fontWeight:文字粗细

    参数说明
    FontWeight.normal正常粗细
    FontWeight.normal粗体
    FontWeight.wx00按照整百粗细(100-900)

图片组件


图片组件分为两种

  • Image.asset
  • Image.network

属性


两种的参数都是一样的,就是引用图片的方式不一样

alignment:图片对齐方式

参数说明
Alignment.topCenter顶部居中对齐
Alignment.topLeft顶部左对齐
Alignment.topRight顶部右对齐
Alignment.center水平垂直居中对齐
Alignment.centerLeft垂直居中水平居左对齐
Alignment.centerRight垂直居中水平居右对齐
Alignment.bottomCenter底部居中对齐
Alignment.bottomLeft底部居左对齐
Alignment.bottomRight底部居右对齐

fit:图片的填充

参数说明
BoxFit.fill拉伸填满
BoxFit.contain按原始比例,可能会有空隙
BoxFit.cover图片填满整个容器,不变形,但是会被剪裁
BoxFit.fitWidth宽度充满
BoxFit.fitHeight高度充满
BoxFit.scaleDown效果和contain差不多,但是不允许显示超过原图片的大小,可小不可大

color:图片背景颜色

colorBlendMode:图片混合颜色选项

  • 参数比较多,都是比较基本的图片颜色混合

    在这里插入图片描述

repeat:图片平铺

  • ImageRepeat.noRepeat:不重复
  • ImageRepeat.repeat:横向和总线都重复
  • ImageRepeat.repeatX:横向重复,纵向不重复
  • ImageRepeat.repeatY:纵向重复,横向不重复

width | height:图片宽高

Image.asset


  1. 在目录新建储存图片的文件夹

    在这里插入图片描述

  2. pubspec.yaml文件中声明图片文件*[注意格式和空格]*,在这个地方添加⬇

    flutter:
      # The following line ensures that the Material Icons font is
      # included with your application, so that you can use the icons in
      # the material Icons class.
      uses-material-design: true
      assets:
        - "img/head.jpg"
      #在这个地方添加,认准这个地方,下面这里的这四行注释下面也行
      # To add assets to your application, add an assets section, like this:
      # assets:
      #   - images/a_dot_burr.jpeg
      #   - images/a_dot_ham.jpeg
      
    
  3. child中使用Image.asset()

    // 这只是个组件
    class Demo6 extends StatelessWidget{
      const Demo6({super.key});
    
      
      Widget build(BuildContext context) {
        // TODO: implement build
        return Center(
          child: Image.asset(
            "img/head.jpg",
          )
        );
      }
    }
    

Image.network


这个也同理,就是不用配置什么东西

// 这只是个组件
class Demo6 extends StatelessWidget{
  const Demo6({super.key});

  
  Widget build(BuildContext context) {
    // TODO: implement build
    return Center(
      child: Image.network(
        "https://www.baidu.com/img/flexible/logo/pc/result.png",
      )
    );
  }
}

图标组件


使用Icon()来使用官方给的图标

class Demo7 extends StatelessWidget{
  const Demo7({super.key});

  
  Widget build(BuildContext context) {
    // TODO: implement build
    return const Center(
      child: Column(
        children: [
          Icon(
            Icons.add,
          ),
          Icon(
            Icons.add_box_rounded,
            color: Colors.lightBlueAccent,
          ),
          Icon(
            Icons.accessibility_rounded,
            color: Colors.green,
            size: 30,
          )
        ],
      ),
    );
  }}

属性


属性说明
Icons.图标名使用官方的图标
color图标颜色
size图标大小,默认20

列表组件


通过使用组件ListView()创建列表

属性


属性说明
scrollDirection设置列表类型,参数为 Axis.vertical垂直列表[默认]、Axis.horizontal 水平列表
padding内边距,与其他组件一样
resolve是否反向排序,参数:truefalse
children列表的元素,参数为数组

最简单的列表


通过使用ListTile()创建列表项,利用ListTile里的参数title可以将其他元素放进去列表项

// 垂直列表-title
class List1 extends StatelessWidget{
  const List1({super.key});
  
  Widget build(BuildContext context) {
    return ListView(
      children: const <Widget>[
        ListTile(
          title: Text("标题1"),
        ),
        ListTile(
          title: Text("标题1"),
        ),
        ListTile(
          title: Text("标题1"),
        ),
        ListTile(
          title: Text("标题1"),
        ),
        ListTile(
          title: Text("标题1"),
        ),
      ],
    );
  }
}

列表项


ListTile(),有必要说一下里面的参数

属性

属性说明
leading列表项的最左边[前导],一般放图标或者图片
trailing列表项的最右边[尾部],一般放图标或者图片
title列表项的标题
subtitle列表项的副标题
style列表项的文本样式
iconColor图标颜色
tileColor列表项背景颜色
titleTextStyle标题文本颜色
subtitleTextStyle副标题文本颜色
leadingAndTrailingTextStyle前导和尾部的文本样式
contentPadding内容的内边距
titleAlignment标题文本的对齐方式
isThreeLine额外文本行[三行列表项]

布局

Grid布局

常用属性

属性说明
scrollDirection组件滚动方向,Axis.horizontal水平,Axis.vertical垂直
reverse组件元素是否反向排序
controller滚动监听
primary内容不足时是否可以滑动
shrinkWrap内容适配,默认false
padding内边距
crossAxisCount一行或一列的元素数量
mainAxisSpacing主轴之间间距
crossAxisSpacing横轴之间间距
childAspectRatio元素的宽高比例
children组件元素
使用GridView.count创建网络布局

使用这个方法创建网络布局必须要填写一个值crossAxisCount,也就是一行限制多少个元素

class GridTest1 extends StatelessWidget{
  const GridTest1({super.key});
  
  Widget build(BuildContext context) {
    return GridView.count(
      scrollDirection: Axis.horizontal,
      crossAxisCount: 3,
      childAspectRatio: 1.0,
      children: const <Widget>[
        Icon(Icons.account_balance_wallet,color: Colors.green,),
        Icon(Icons.balance,color: Colors.brown,),
        Icon(Icons.camera,color: Colors.lightBlueAccent,),
        Icon(Icons.data_object,color: Colors.orange,),
        Icon(Icons.egg,color: Colors.cyan,),
        Icon(Icons.facebook_outlined,color: Colors.greenAccent,),
        Icon(Icons.gamepad,color: Colors.deepPurple,),
        Icon(Icons.handshake_outlined,color: Colors.lightGreen,),
        Icon(Icons.image_rounded,color: Colors.deepOrange,),
      ],
    );
  }
}
使用GridView.count创建网络布局

使用此方法创建网络布局必须要填写一个值maxCrossAxisExtent,也就是固定横轴的最大长度

class GridTest2 extends StatelessWidget{
  const GridTest2({super.key});

  
  Widget build(BuildContext context) {
    return GridView.extent(
      maxCrossAxisExtent: 100,
      childAspectRatio: 1.0,
      children: const <Widget>[
        Icon(Icons.account_balance_wallet,color: Colors.green,),
        Icon(Icons.balance,color: Colors.brown,),
        Icon(Icons.camera,color: Colors.lightBlueAccent,),
        Icon(Icons.data_object,color: Colors.orange,),
        Icon(Icons.egg,color: Colors.cyan,),
        Icon(Icons.facebook_outlined,color: Colors.greenAccent,),
        Icon(Icons.gamepad,color: Colors.deepPurple,),
        Icon(Icons.handshake_outlined,color: Colors.lightGreen,),
        Icon(Icons.image_rounded,color: Colors.deepOrange,),
      ],
    );
  }
}

线性布局

属性

属性说明参数
mainAxisAlignment主轴的排序方式`MainAxisAlignment.center
crossAxisAlignment次轴的排序方式`CrossAxisAlignment.center
children组件元素
Row布局

class RowTest extends StatelessWidget{
  const RowTest({super.key});

  
  Widget build(BuildContext context) {
    return Container(
      height: double.infinity,
      width: double.infinity,
      child: const Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.account_balance_wallet,color: Colors.green,size: 50,),
          Icon(Icons.balance,color: Colors.brown,size: 50,),
          Icon(Icons.camera,color: Colors.lightBlueAccent,size: 50,),
          Icon(Icons.data_object,color: Colors.orange,size: 50,),
          Icon(Icons.egg,color: Colors.cyan,size: 50,),
          Icon(Icons.facebook_outlined,color: Colors.greenAccent,size: 50,),
          Icon(Icons.gamepad,color: Colors.deepPurple,size: 50,),
          Icon(Icons.handshake_outlined,color: Colors.lightGreen,size: 50,),
          Icon(Icons.image_rounded,color: Colors.deepOrange,size: 50,),
        ],
      ),
    );
  }
}
Colum布局

class ColumnTest extends StatelessWidget{
  const ColumnTest({super.key});

  
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      height: double.infinity,
      child:const Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Icon(Icons.account_balance_wallet,color: Colors.green,size: 50,),
          Icon(Icons.balance,color: Colors.brown,size: 50,),
          Icon(Icons.camera,color: Colors.lightBlueAccent,size: 50,),
          Icon(Icons.data_object,color: Colors.orange,size: 50,),
          Icon(Icons.egg,color: Colors.cyan,size: 50,),
          Icon(Icons.facebook_outlined,color: Colors.greenAccent,size: 50,),
          Icon(Icons.gamepad,color: Colors.deepPurple,size: 50,),
          Icon(Icons.handshake_outlined,color: Colors.lightGreen,size: 50,),
          Icon(Icons.image_rounded,color: Colors.deepOrange,size: 50,),
        ],
      ),
    );
  }
}

Flex布局


使用flex布局一般需要搭配Expanded组件一起使用

Flex属性和Expanded属性

Flex属性说明Expanded属性说明
directionflex布局方向,Axis.horizontal水平,Axis.vertical垂直flex该组件占总flex的多少,总flex=处于该组件内Expanded组件的flex值之和
childrenflex的元素childExpanded的元素
水平布局

class FlexHor extends StatelessWidget{
  const FlexHor({super.key});

  
  Widget build(BuildContext context) {
    return Flex(
      direction: Axis.horizontal,
      children: [
        Expanded(flex: 1,child: Container(color: Colors.brown,child: Icon(Icons.egg,color: Colors.teal,size: 50,),)),
        Expanded(flex: 2,child: Container(color: Colors.orange,child: Icon(Icons.facebook_outlined,color: Colors.teal,size: 50,),)),
        Expanded(flex: 3,child: Container(color: Colors.cyan,child: Icon(Icons.data_object,color: Colors.teal,size: 50,),)),
      ],
    );
  }
}
垂直布局

class FlexVer extends StatelessWidget{
  const FlexVer({super.key});

  
  Widget build(BuildContext context) {
    return Flex(
      direction: Axis.vertical,
      children: [
        Expanded(flex: 1,child: Container(color: Colors.brown,child: Icon(Icons.egg,color: Colors.teal,size: 50,),)),
        Expanded(flex: 2,child: Container(color: Colors.orange,child: Icon(Icons.facebook_outlined,color: Colors.teal,size: 50,),)),
        Expanded(flex: 3,child: Container(color: Colors.cyan,child: Icon(Icons.data_object,color: Colors.teal,size: 50,),)),
      ],
    );
  }
}
水平与垂直布局的混合使用

在这里插入图片描述

class RowAndColumn extends StatelessWidget{
  const RowAndColumn({super.key});

  
  Widget build(BuildContext context) {
    return Container(
      height: 300,
      margin:const EdgeInsets.all(10),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Expanded(
            flex: 1,
            child: Container(color: Colors.teal,),
          ),
          Container(height: 10,),//只是组件之间的分割线
          Expanded(
            flex: 1,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Expanded(flex: 2,child: Container(color: Colors.deepPurple,),),
                Container(width: 10,),//只是组件之间的分割线
                Expanded(flex: 1,child: Column(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    Expanded(flex: 1,child: Container(color: Colors.tealAccent,)),
                    Container(height: 10,),//只是组件之间的分割线
                    Expanded(flex: 1,child: Container(color: Colors.lightBlue,))
                  ],
                ))
              ],
            ),
          ),
        ],
      ),
    );
  }
}

Wrap布局

属性

属性说明
direction组件主轴的方向,参数为Axis.horizontal水平,Axis.vertical垂直
spacing主轴元素之间的间距
runSpacing新行元素之间的间距
alignment主轴元素的对齐方式
runAlignment新行元素的对齐方式
verticalDirection定义children摆放顺序,默认是VerticalDirection.down,参数还有VerticalDirection.up
children组件元素

在这里插入图片描述

class WrapTest extends StatelessWidget{
  const WrapTest({super.key});

  
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.topCenter,
      child: Container(
        width: 300,
        alignment: Alignment.topCenter,
        child: Wrap(
          spacing: 5,
          runSpacing: 5,
          direction: Axis.horizontal,
          alignment: WrapAlignment.start,
          runAlignment: WrapAlignment.center,
          verticalDirection: VerticalDirection.down,
          children: [
            TextButton(onPressed: (){}, child: const Text("Btn1")),
            TextButton(onPressed: (){}, child: const Text("Btn22")),
            TextButton(onPressed: (){}, child: const Text("Btn333")),
            TextButton(onPressed: (){}, child: const Text("Btn4444")),
            TextButton(onPressed: (){}, child: const Text("Btn55555")),
            TextButton(onPressed: (){}, child: const Text("Btn666666")),
            TextButton(onPressed: (){}, child: const Text("Btn7777777")),
            TextButton(onPressed: (){}, child: const Text("Btn88888888")),
            TextButton(onPressed: (){}, child: const Text("Btn999999999")),
            TextButton(onPressed: (){}, child: const Text("Btn1000000000")),
            TextButton(onPressed: (){}, child: const Text("Btn11")),
          ],
        ),
      ),
    );
  }
}

Stack布局


一般来说,会使用其他组件来与Stack布局一起使用

属性

属性说明
alignment对齐方式,参数Alignment.*
textDirection文本方向,参数TextDirection.*
children子组件

使用stack布局,会让它的子组件都堆叠在一起,如下

在这里插入图片描述

class StackTest extends StatelessWidget{
  const StackTest({super.key});

  
  Widget build(BuildContext context) {
    return Container(
      child: const Stack(
        alignment: Alignment.topCenter,
        children: [
          Text("test1"),
          Text("test2"),
          Text("test3"),
          Text("test4"),
          Text("test5"),
          Text("test6"),
          Text("test7"),
        ],
      ),
    );
  }
}
结合Align使用

Align组件一般来说,只用通过alignment属性来控制组件的位置

Align属性

属性说明
alignment对齐方式,参数Alignment.*
child子组件

如果位置不够,Stack布局里的组件依然会堆叠

在这里插入图片描述

class StackAndAlign extends StatelessWidget{
  const StackAndAlign({super.key});

  
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      child: const Stack(
        textDirection: TextDirection.ltr,
        alignment: Alignment.topCenter,
        children: [
          Align(
            alignment: Alignment.topLeft,
            child: Text("test1"),
          ),
          Align(
            alignment: Alignment.topCenter,
            child: Text("test2"),
          ),
          Align(
            alignment: Alignment.topRight,
            child: Text("test3"),
          ),
          Align(
            alignment: Alignment.centerLeft,
            child: Text("test4"),
          ),
          Align(
            alignment: Alignment.center,
            child: Text("test5"),
          ),
          Align(
            alignment: Alignment.centerRight,
            child: Text("test6"),
          ),
          Align(
            alignment: Alignment.bottomLeft,
            child: Text("test7"),
          ),
          Align(
            alignment: Alignment.bottomCenter,
            child: Text("test8"),
          ),
          Align(
            alignment: Alignment.bottomRight,
            child: Text("test9"),
          ),
        ],
      ),
    );
  }
}
创建一个底部导航栏

通过使用Align的对齐方式定位,将导航栏固定在底部

在这里插入图片描述

class TabBarSelf extends StatelessWidget{
  const TabBarSelf({super.key});
  List<Widget> getLists(){
    List<Widget> lists = [];
    for(int i = 0;i<100;i++){
      lists.add(
          ListTile(
            leading: Text("[${i+1}]"),
            title: Text("标题${i+1}"),
            subtitle: Text("数据${i+1}"),
            trailing: Text("|${i+1}|"),
            tileColor: Color.fromARGB(Random().nextInt(255), Random().nextInt(255), Random().nextInt(255), 1),
          )
      );
    }
    return lists;
  }
  
  Widget build(BuildContext context) {
    return Stack(
      children: [
        ListView(
          children: getLists(),
        ),
        Align(
          alignment: Alignment.bottomRight,
          child: Container(
            width: double.infinity,
            height: 40,
            color: Colors.lightBlue,
            child: const Center(
              child: Text("我是个导航栏"),
            ),
          ),
        ),
      ],
    );
  }
}

结合Positioned使用

使用Positioned组件,可以让组件位于哪个地方,就位于哪个地方,相当于绝对定位

Positioned属性

属性说明
`lefttop
`widthheight`
child子组件

在这里插入图片描述

class StackAndPositioned extends StatelessWidget{
  const StackAndPositioned({super.key});

  
  Widget build(BuildContext context) {
    return Container(
      child: const Stack(
        alignment: Alignment.topCenter,
        children: [
          Positioned(
            child: Text("top0,left0"),
            top: 0,
            left: 0,
          ),
          Positioned(
            child: Text("top100,left0"),
            top: 100,
            left: 0,
          ),
          Positioned(
            child: Text("top0,left100"),
            top: 0,
            left: 100,
          ),
          Positioned(
            child: Text("top100,left100"),
            top: 100,
            left: 100,
          ),
          Positioned(
            child: Text("top0,right0"),
            top: 0,
            right: 0,
          ),
          Positioned(
            child: Text("bottom0,left0"),
            bottom: 0,
            left: 0,
          ),
          Positioned(
            child: Text("bottom0,right0"),
            bottom: 0,
            right: 0,
          ),
        ],
      ),
    );
  }
}
创建一个悬浮按钮

可以使用Positioned组件的绝对定位的特点,让按钮随时固定在窗口的一个位置

在这里插入图片描述

class SpecialBtn extends StatelessWidget{
  const SpecialBtn({super.key});
  List<Widget> getLists(){
    List<Widget> lists = [];
    for(int i = 0;i<100;i++){
      lists.add(
          ListTile(
            leading: Text("[${i+1}]"),
            title: Text("标题${i+1}"),
            subtitle: Text("数据${i+1}"),
            trailing: Text("|${i+1}|"),
            tileColor: Color.fromARGB(Random().nextInt(255), Random().nextInt(255), Random().nextInt(255), 1),
          )
      );
    }
    return lists;
  }
  
  Widget build(BuildContext context) {
    return Stack(
      children: [
        ListView(
          children: getLists(),
        ),
        Positioned(
          right: 10,
          bottom: 10,
          child: Container(
            width: 40,
            height: 40,
            alignment: Alignment.center,
            child: ElevatedButton(
              style: ButtonStyle(
                backgroundColor: MaterialStateColor.resolveWith((states) => Colors.limeAccent),
                padding: MaterialStateProperty.all(const EdgeInsets.all(0)),
              ),
              onPressed: () {
                print("你点击了按钮");
              },
              child: const Text("Btn"),
            ),
          )
        ),
      ],
    );
  }
}

AspectRatio布局


此布局的理解比较的复杂,布局的宽高由aspectRatio参数去决定,即宽高比,它会在布局限制条件允许的范围内尽可能的扩展,按照固定比率去尽量占满区域

属性

属性说明
aspectRatio宽高比,可能会忽略这个数值
child子组件
class AspectRatioTest extends StatelessWidget{
  const AspectRatioTest({super.key});

  
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: 3/2,
      child: Container(
        color: Colors.limeAccent,
      ),
    );
  }

按钮


flutter内置了一些比较好的按钮,虽然样式设置起来有些许麻烦

属性


属性说明
onPressed点击事件 [必填]
style按钮样式,需要使用ButtonStyle来调整样式
child子组件 [必填]

Flutter内置的按钮


按钮说明
ElevatedButton最普通的按钮,有背景颜色,有少许阴影,点击有颜色变化,无边框
TextButton文本按钮,无背景颜色,无阴影,点击有颜色变化,无边框
OutlinedButton边框按钮,无背景颜色,无阴影,点击有颜色变化,有边框
IconButton图标按钮,无背景颜色,无阴影,点击有颜色变化,无边框
ElevatedButton.icon带图标的普通按钮,样式如上,icon必填
TextButton.icon带图标的文本按钮,样式如上,icon必填
OutlinedButton.icon带图标的边框按钮,样式如上,icon必填

在这里插入图片描述

class BtnTest extends StatelessWidget{
  const BtnTest({super.key});

  
  Widget build(BuildContext context) {
    return GridView.count(
      crossAxisCount: 4,
      children: [
        ElevatedButton(onPressed: (){}, child: const Text("普通按钮")),
        TextButton(onPressed: (){}, child: const Text("文本按钮")),
        OutlinedButton(onPressed: (){}, child: const Text("边框按钮")),
        IconButton(onPressed: (){}, icon: const Icon(Icons.gamepad_outlined)),
        ElevatedButton.icon(onPressed: (){}, icon: const Icon(Icons.tag_faces), label: const Text("普通按钮[有图标]")),
        TextButton.icon(onPressed: (){}, icon: const Icon(Icons.tag_faces), label: const Text("文本按钮[有图标]")),
        OutlinedButton.icon(onPressed: (){}, icon: const Icon(Icons.tag_faces), label: const Text("边框按钮[有图标]")),
      ],
    );
  }
}

修改按钮宽高


通过使用SizeBox来调整按钮,SizeBox中的width和height调整盒子的大小,进而调整按钮宽高

在这里插入图片描述

class BtnTest1 extends StatelessWidget{
  const BtnTest1({super.key});

  
  Widget build(BuildContext context) {
    return SizedBox(
      width: 150,
      height: 50,
      child: ElevatedButton(
        onPressed: (){},
        child: const Text("修改宽高"),
      ),
    );
  }
}

修改按钮样式


想要修改按钮的样式,需要通过ButtonStyle去调整,而里面的参数比较的复杂

ButtonStyle的属性
属性说明参数
backgroundColor背景颜色MaterialStateProperty.all(Colors.*)
foregroundColor组件内容颜色MaterialStateProperty.all(Colors.*)
shadowColor阴影颜色MaterialStateProperty.all(Colors.*)
shape按钮形状MaterialStateProperty.all(*) 这个比较特殊
padding内边距MaterialStateProperty.all(const EdgeInsets.*)
elevation阴影范围MaterialStateProperty.all(num)
side边框MaterialStateProperty.all(const BorderSide(width: num,color: Colors.*))
alignment组件对齐方式Alignment.*
设置按钮圆角

shape这个属性的参数比较多变

若要设置圆角,参数为:MaterialStateProperty.*all*(RoundedRectangleBorder(borderRadius: BorderRadius.circular(num)))

在这里插入图片描述

class BtnTest2 extends StatelessWidget{
  const BtnTest2({super.key});

  
  Widget build(BuildContext context) {
    return SizedBox(
      width: 150,
      height: 50,
      child: ElevatedButton(
        onPressed: (){},
        style: ButtonStyle(
          backgroundColor: MaterialStateProperty.all(Colors.lightBlueAccent),
          foregroundColor: MaterialStateProperty.all(Colors.purple),
          elevation: MaterialStateProperty.all(10),
          alignment:Alignment.center,
          shadowColor:MaterialStateProperty.all(Colors.black12),
          padding:MaterialStateProperty.all(const EdgeInsets.all(10)),
          shape: MaterialStateProperty.all(RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10)
          )),
        ),
        child: const Text("修改各种属性"),
      ),
    );
  }
}

若要设置圆形,参数为:MaterialStateProperty.all(const CircleBorder(side: BorderSide(color: Color.fromARGB(0, 0, 0, 0))))

在这里插入图片描述

class BtnTest3 extends StatelessWidget{
  const BtnTest3({super.key});

  
  Widget build(BuildContext context) {
    return SizedBox(
      width: 120,
      height: 120,
      child: ElevatedButton(
        onPressed: (){},
        style: ButtonStyle(
          shape: MaterialStateProperty.all(
            const CircleBorder(side: BorderSide(color: Color.fromARGB(0, 0, 0, 0))
          )),
        ),
        child: const Text("圆形按钮"),
      ),
    );
  }
}
设置按钮边框

在这里插入图片描述

class BtnTest4 extends StatelessWidget{
  const BtnTest4({super.key});

  
  Widget build(BuildContext context) {
    return SizedBox(
      width: 120,
      height: 120,
      child: OutlinedButton(
        onPressed: (){},
        style: ButtonStyle(
          side: MaterialStateProperty.all(
            const BorderSide(width: 1,color: Colors.brown)
          ),
        ),
        child: const Text("修改边框"),
      ),
    );
  }
}

事件

PointerEvents


最简单的事件监听,通过Listener()去监听子组件的行为 tips:以下的eevent都是event,只是个变量而已

属性
属性说明
onPointerDown按下组件触发,参数:(e)=>{}
onPointerMove按下并在组件内移动触发,参数:(e)=>{}
onPointerHover移动到组件触发,参数:(e)=>{}
onPointerUp按下后松开才触发,参数:(e)=>{}

在这里插入图片描述

class Test1 extends StatelessWidget{
  const Test1({super.key});

  
  Widget build(BuildContext context) {
    return ListView(
      children: [
        ListTile(
          title: Listener(
            onPointerDown: (event) => print("按下组件触发:$event"),
            child: const Text("按下组件触发"),
          ),
        ),
        Container(height: 1,color: Colors.black87,),
        ListTile(
          title: Listener(
            onPointerMove: (event) => print("按下并在组件内移动触发:$event"),
            child: const Text("按下并在组件内移动触发"),
          ),
        ),
        Container(height: 1,color: Colors.black87,),
        ListTile(
          title: Listener(
            onPointerHover: (event) => print("移动到组件触发:$event"),
            child: const Text("移动到组件触发"),
          ),
        ),
        Container(height: 1,color: Colors.black87,),
        ListTile(
          title: Listener(
            onPointerUp: (event) => print("按下后松开才触发:$event"),
            child: const Text("按下后松开才触发"),
          ),
        ),
      ],
    );
  }
}

GestureDetector


GestureDetector封装了非常多的触发类型,所以直接使用GestureDetector()组件可以直接使用这些监听类型

Tap单击 属性

属性说明
onTapDown按下触发,参数(e)=>{}
onTapUp抬起触发,参数(e)=>{}
onTap按下后抬起[与抬起触发不同,这个两个条件都得有],参数()=>{}
onTapCancel按下后取消[可移出组件触发],参数()=>{}

在这里插入图片描述

class Test2 extends StatelessWidget{
  const Test2({super.key});

  
  Widget build(BuildContext context) {
    return ListView(
      children: [
         ListTile(
          title:GestureDetector(
            onTapDown: (e) => print("按下触发:$e"),
            child: const Text("按下触发"),
          ),
        ),
        Container(height: 1,color: Colors.black87,),
        ListTile(
          title:GestureDetector(
            onTapUp: (e) => print("抬起触发:$e"),
            child: const Text("抬起触发"),
          ),
        ),
        Container(height: 1,color: Colors.black87,),
        ListTile(
          title:GestureDetector(
            onTap: () => print("按下后抬起[与抬起触发不同,这个两个条件都得有]:======="),
            child: const Text("按下后抬起[与抬起触发不同,这个两个条件都得有]"),
          ),
        ),
        Container(height: 1,color: Colors.black87,),
        ListTile(
          title:GestureDetector(
            onTapCancel: () => print("按下后取消[可移出组件触发]:======="),
            child: const Text("按下后取消[可移出组件触发]"),
          ),
        ),
      ],
    );
  }
}
DoubleTap双击 属性

属性说明
onDoubleTap双击触发[必须按下抬起各两次],参数()=>{}
onDoubleTapCancel双击时取消[可移出组件触发],参数()=>{}
onDoubleTapDown双击时第二次点击触发,参数(e)=>{}

在这里插入图片描述

class Test3 extends StatelessWidget{
  const Test3({super.key});

  
  Widget build(BuildContext context) {
    return ListView(
      children: [
        ListTile(
          title:GestureDetector(
            onDoubleTap: () => print("双击触发:======="),
            child: const Text("双击触发[必须按下抬起各两次]"),
          ),
        ),
        Container(height: 1,color: Colors.black87,),
        ListTile(
          title:GestureDetector(
            onDoubleTapCancel: () => print("双击时取消[可移出组件触发]:======="),
            child: const Text("双击时取消[可移出组件触发]"),
          ),
        ),
        Container(height: 1,color: Colors.black87,),
        ListTile(
          title:GestureDetector(
            onDoubleTapDown: (e) => print("双击时第二次点击触发:$e"),
            child: const Text("双击时第二次点击触发"),
          ),
        ),
      ],
    );
  }
}
LongPress长按 属性

属性说明
onLongPress长按触发,参数()=>{}
onLongPressStart长按开始时[长按触发时]触发,参数(e)=>{}
onLongPressEnd长按结束时[长按抬起]触发,参数(e)=>{}
onLongPressDown长按按下时[单点也可]触发,参数(e)=>{}
onLongPressUp长按抬起时触发,参数()=>{}
onLongPressMoveUpdate长按后移动更新时触发,参数(e)=>{}
onLongPressCancel长按取消时[也可移出组件]触发,参数()=>{}

在这里插入图片描述

class Test4 extends StatelessWidget{
  const Test4({super.key});

  
  Widget build(BuildContext context) {
    return ListView(
      children: [
        ListTile(
          title: GestureDetector(
            onLongPress: ()=>print("长按触发:====="),
            child: const Text("长按触发"),
          ),
        ),
        Container(height: 1,color: Colors.black87,),
        ListTile(
          title: GestureDetector(
            onLongPressStart: (e)=>print("长按开始时[长按触发时]触发:$e"),
            child: const Text("长按开始时触发"),
          ),
        ),
        Container(height: 1,color: Colors.black87,),
        ListTile(
          title: GestureDetector(
            onLongPressEnd: (e)=>print("长按结束时[长按抬起]触发:$e"),
            child: const Text("长按结束时触发"),
          ),
        ),
        Container(height: 1,color: Colors.black87,),
        ListTile(
          title: GestureDetector(
            onLongPressDown: (e)=>print("长按按下时[单点也可]触发:$e"),
            child: const Text("长按按下时触发"),
          ),
        ),
        Container(height: 1,color: Colors.black87,),
        ListTile(
          title: GestureDetector(
            onLongPressUp: ()=>print("长按抬起时触发:===="),
            child: const Text("长按抬起时触发"),
          ),
        ),
        Container(height: 1,color: Colors.black87,),
        ListTile(
          title: GestureDetector(
            onLongPressMoveUpdate: (e)=>print("长按后移动更新时触发:$e"),
            child: const Text("长按后移动更新时触发"),
          ),
        ),
        Container(height: 1,color: Colors.black87,),
        ListTile(
          title: GestureDetector(
            onLongPressCancel: ()=>print("长按取消时触发:===="),
            child: const Text("长按取消时触发"),
          ),
        ),
      ],
    );
  }
}
Drag拖动 属性

属性说明
onVerticalDragStart开始垂直移动,参数(e)=>{}
onVerticalDragUpdate持续垂直移动,参数(e)=>{}
onVerticalDragEnd结束垂直移动,参数(e)=>{}
onHorizontalDragStart开始水平移动,参数(e)=>{}
onHorizontalDragUpdate持续水平移动,参数(e)=>{}
onHorizontalDragEnd结束水平移动,参数(e)=>{}

在这里插入图片描述

class Test5 extends StatelessWidget{
  const Test5({super.key});

  
  Widget build(BuildContext context) {
    return Center(
      child: GestureDetector(
          onVerticalDragStart: (e)=>print("开始垂直移动,$e"),
          onVerticalDragUpdate: (e)=>print("持续垂直移动,$e"),
          onVerticalDragEnd: (e)=>print("结束垂直移动,$e"),
          onHorizontalDragStart: (e)=>print("开始水平移动,$e"),
          onHorizontalDragUpdate: (e)=>print("持续水平移动,$e"),
          onHorizontalDragEnd: (e)=>print("结束水平移动,$e"),
          child: Container(
            width: 150,
            height: 50,
            color: Colors.green,
            child: const Center(
              child: Text("按住我移动一下"),
            ),
          )
      ),
    );
  }
}
事件隔离

因为通过Listener()可以拿到子组件Widget的信息,使用AbsorbPointer()组件和IgnorePointer()组件可以使得这些信息不允许打印出来,而两者的用法一样,只是属性不一样

组件属性说明
AbsorbPointer()absorbingtrue :不会打印,false :会打印
IgnorePointer()ignoringtrue :不会打印,false :会打印

在这里插入图片描述

class Test6 extends StatelessWidget{
  const Test6({super.key});


  
  Widget build(BuildContext context) {
    return Flex(
      direction: Axis.horizontal,
      children: [
        Container(width: 5),
        Expanded(
          flex: 1,
          child: IgnorePointer(
            ignoring: true,
            child: Listener(
              onPointerDown: (e)=>print("点了第一个ignoring: true,$e"),
              child: Container(
                height: 50,
                color: Colors.green,
                child: const Text("ignoring: true"),
              ),
            ),
          )),
        Container(width: 5),
        Expanded(
          flex: 1,
          child: AbsorbPointer(
            absorbing: false,
            child: Listener(
              onPointerDown: (e)=>print("点了第二个,absorbing: false,$e"),
              child: Container(
                height: 50,
                color: Colors.lightBlueAccent,
                child: const Text("absorbing: false"),
              ),
            ),
          )),
        Container(width: 5),
      ],
    );
  }
}

输入框


使用TextField()可以创建出一个输入框,Flutter为输入框提供了非常多的属性,供开发人员自定义输入框

属性


属性说明参数
controller控制器,文本的交互需要它TextEditingController()
decoration输入框的装饰选项InputDecoration()
textInputAction键盘左下角的按钮类型TextInputAction.*
textCapitalization键盘默认大小写TextCapitalization.*
autofocus是否自动聚焦到输入框bool,默认false
obscureText是否将文本变成点,即隐藏文本bool,默认false
autocorrect是否自动更正bool,默认true
maxLength最大输入字符数int
cursorWidth光标宽度int,默认2.0
cursorColor光标颜色color
cursorRadius光标圆角Radius.circular(*)
cursorHeight光标高度int
cursorOpacityAnimates光标是否开启闪烁动画bool
inputFormatters限制输入文字/数字[]
onTap点击输入框事件(){事件}
onTapOutside点击输入框外事件(){事件}
onChanged输入框变更事件(e){事件}
onEditingComplete输入框完成输入事件(){事件}
onSubmitted输入框提交事件(){事件}
enabled是否启用输入框bool

Controller


一般来说,使用控制器直接使用该对象就好了

TextEditingController controller = TextEditingController();
方法用途
clear()清除使用该控制器的输入框内容
text()设置使用该控制器的输入框内容

但是,使用TextEditingController.fromValue(*)可以自定义输入框的一些属性,*为TextPosition()

TextPosition()有以下两个属性

属性说明
text设置使用该控制器的输入框内容,参数为String
selection设置输入框内边距,参数请继续看↓

selection的参数为TextSelection.fromPosition(*),而里面又需要TextPosition(),里面只有两个属性

属性说明
affinity一般填TextAffinity.downstream
offset输入框的光标距离最左边的距离,参数为int

decoration


可以让输入框自定义成想要的样式,参数为InputDecoration(),以下为InputDecoration()的属性

属性说明参数
icon最左侧的图标,图标下无横线Icon()
labelText悬浮文字String
labelStyle悬浮文字样式TextStyle()
helperText帮助文字String
helperStyle帮助文字样式TextStyle()
hintText提示文字String
hintStyle提示文字样式TextStyle()
errorText错误文字String
errorStyle错误文字样式TextStyle()
contentPadding内容内边距EdgeInsets.all(*)
prefixIcon最左边的图标,包含在横线内Icon()
prefix最左边自定义WidgetWidget
prefixText最左边文字,只有当输入框聚焦才显示String
prefixStyle最左边文字样式TextStyle()
suffixIcon最右边的图标,包含在横线内Icon()
suffix最右边自定义WidgetWidget
suffixText最右边文字,只有当输入框聚焦才显示String
suffixStyle最右边文字样式TextStyle()
counter自定义计数器WidgetWidget
counterText计数器文本String
counterStyle计数器文本样式TextStyle()
filled是否填充颜色bool,默认false
fillColor填充颜色Color
border输入框整体边框,看下面教程InputBorder.noneOutlineInputBorder()
enabledBorder输入框可用时边框,看下面教程InputBorder.none或不填OutlineInputBorder()
disabledBorder输入框禁用时边框,看下面教程InputBorder.none或不填OutlineInputBorder()
errorBorder输入框错误时边框,看下面教程InputBorder.none或不填OutlineInputBorder()
focusedBorder输入框聚焦时边框,看下面教程InputBorder.none或不填OutlineInputBorder()

border参数详解


参数为InputBorder.none时,输入框不会显示下横线

参数为OutlineInputBorder(),需要通过属性borderRadius设置圆角,borderRadius的参数为BorderRadius.all(Radius.circular(*))

参数为UnderlineInputBorder(),输入框只会在下面显示一条线

最后两个参数的属性borderSide可以设置线条颜色,参数为BorderSide(color: Colors.*)

放demo


在这里插入图片描述

class AllInput extends StatelessWidget{
  AllInput({super.key});

  TextEditingController controller = TextEditingController();

  void clearInput(){
    controller.clear();
  }
  TextEditingController userInputController = TextEditingController();
  void clearUserInput(){
    userInputController.clear();
  }

  
  Widget build(BuildContext context) {
    controller = TextEditingController.fromValue(
      TextEditingValue(
        text: "初始化文本",
        selection: TextSelection.fromPosition(
          const TextPosition(
            affinity: TextAffinity.downstream,
            offset: 5
          )
        )
      )
    );
    return ListView(
      children: [
        const ListTile(
          title: TextField(),
        ),
        ListTile(
          title: TextField(
            controller: controller,
            textInputAction: TextInputAction.next,
            textCapitalization: TextCapitalization.characters,
            autofocus: true,
            obscureText: true,
            autocorrect: true,
            onTap: ()=>print("你点击了输入框"),
            onChanged: (e){
              print("你输入了${e.characters}/共${e.length}字");
            },
            cursorWidth: 10,
            cursorColor: Colors.green,
            cursorRadius: const Radius.circular(5),
            cursorHeight: 50,
            cursorOpacityAnimates: true,
            inputFormatters: [],

          ),
        ),
        ListTile(
          title: TextField(
            controller: controller,
            enabled: false,
          ),
        ),
        const ListTile(
          title: TextField(
            decoration: InputDecoration(
              hintText: "灰色提示文字",
              icon: Icon(Icons.account_box)
            ),
          ),
        ),
        const ListTile(
          title: TextField(
            decoration: InputDecoration(
              hintText: "灰色提示文字",
              prefixIcon: Icon(Icons.facebook_outlined),
              suffixIcon: Icon(Icons.clear),
            ),
          ),
        ),
        const ListTile(
          title: TextField(
            decoration: InputDecoration(
              hintText: "灰色提示文字",
              prefix: Text("user"),
              suffix: Text("后缀"),
            ),
          ),
        ),
        const ListTile(
          title: TextField(
            decoration: InputDecoration(
              hintText: "灰色提示文字",
              label: Text("label"),
              helperText: "帮助文字",
              errorText: "错误提示",
              contentPadding: EdgeInsets.all(10),
              counterText: "计数器",
              filled: true,
              fillColor: Colors.green
            ),
          ),
        ),
        ListTile(
          title: TextField(
            controller: userInputController,
            textInputAction: TextInputAction.next,
            decoration: const InputDecoration(
              hintText: "请输入用户名",
              labelText: "用户名",
              prefixIcon: Icon(Icons.account_box),
              filled: true,
              fillColor: Colors.white70,
              focusedBorder: OutlineInputBorder(
                borderSide: BorderSide(
                  color: Colors.lightBlue
                ),
                borderRadius: BorderRadius.all(Radius.circular(10))
              )
            ),

          ),
        )
      ],
    );
  }
}

Dialog

简单的Dialog


通过AlertDialog(),我们可以很快的创建出一个确认窗口

但是在使用AlertDialog()时,需要使用showDialog(),才能显示

  1. 新建一个方法,并携带context变量,定义一个result变量和打印result

    void alertDialog(context){
    	var result;
    	print("返回值:$result");
    }
    
  2. 使用showDialog(),以及将方法变为异步方法,这里来看一下showDialog的属性

    属性说明
    context不理,就填context
    builder返回的Dialog类型
    barrierDismissible是否点击蒙版关闭Dialog
    useSafeArea是否预留安全区域
    void alertDialog(context)async{
    	var result = await showDialog(
    		barrierDismissible: true,
        	useSafeArea: true,
      		context: context,
            builder: builder
    	);
    	print("返回值:$result");
    }
    
  3. builder返回AlertDialog()

    void alertDialog(context)async{
        var result = await showDialog(
            barrierDismissible: true,
            useSafeArea: true,
            context: context,
            builder: (context)=>AlertDialog()
        );
        print("返回值:$result");
    }
    
  4. 编辑AlertDialog()title,content,action

    void alertDialog(context)async{
        var result = await showDialog(
            barrierDismissible: true,
            useSafeArea: true,
            context: context,
            builder: (context)=>AlertDialog(
              title: const Text("标题"),
              content: const Text("内容"),
              actions: [
                TextButton(
                    onPressed: (){
                      print("确定");
                      Navigator.pop(context,"confirm");
                    },
                    child: const Text("确定")
                ),
                TextButton(
                    onPressed: (){
                      print("取消");
                      Navigator.pop(context,"cancel");
                    },
                    child: const Text("取消")
                ),
              ],
            )
        );
        print("返回值:$result");
      }
    
  5. 最后创建一个按钮使用这个方法

    onPressed: (){
    	selectDialog(context);
    },
    
  6. 效果

    在这里插入图片描述

多选项Dialog


使用showDialog()可以创建出多选项Dialog

与普通的Dialog创建方法相同,只是里面的children用了SimpleDialogOption(),相当于按钮

void selectDialog(context)async{
    var result = await showDialog(
        barrierDismissible: false,
        context: context,
        builder: (context)=>SimpleDialog(
          title: const Text("标题"),
          children: [
            SimpleDialogOption(
              onPressed: (){
                print("选项1");
                Navigator.pop(context,"选项1");
              },
              child: const Text("选项1"),
            ),
            const Divider(),
            SimpleDialogOption(
              onPressed: (){
                print("选项2");
                Navigator.pop(context,"选项2");
              },
              child: const Text("选项2"),
            ),
            const Divider(),
            SimpleDialogOption(
              onPressed: (){
                print("选项3");
                Navigator.pop(context,"选项3");
              },
              child: const Text("选项3"),
            ),
          ],
        )
    );
    print("返回值:$result");
  }

在这里插入图片描述

自定义Dialog


通过继承Dialog这个类,可以创建出高自定义的Dialog

在这里插入图片描述

class CustomDialog extends Dialog{
  String title;
  TextStyle titleTextStyle;
  String content;
  TextStyle contentTextStyle;
  Color barrierColor;
  Color backgroundColor;
  double height;
  double width;
  Function()? onFinish;
  CustomDialog({
    super.key,
    required this.title,
    required this.onFinish,
    this.content = "",
    this.barrierColor = Colors.black12,
    this.backgroundColor = Colors.brown,
    this.width = 300,
    this.height = 300,
    this.titleTextStyle = const TextStyle(
      fontSize: 30,
      fontWeight: FontWeight.bold,
      color: Colors.white
    ),
    this.contentTextStyle = const TextStyle(
        fontSize: 12,
        fontWeight: FontWeight.normal,
        color: Colors.white
    ),
  });

  
  Widget build(BuildContext context) {
    return Material(
      type: MaterialType.transparency,
      child: Stack(
        children: [
          Container(height: double.infinity,width: double.infinity,color: barrierColor,),
          Center(
            child: Container(
              width: width,
              height: height,
              padding: const EdgeInsets.all(10),
              decoration: BoxDecoration(
                color: backgroundColor,
                borderRadius: BorderRadius.circular(20)
              ),
              child: Stack(
                children: [
                  Align(
                    alignment: Alignment.topCenter,
                    child: Text(
                        title,
                        style: titleTextStyle
                    ),
                  ),
                  Align(
                    alignment: Alignment.centerLeft,
                    child: Text(
                      content,
                      style: contentTextStyle,
                    ),
                  ),
                  Align(
                    alignment: Alignment.bottomCenter,
                    child: Container(
                      height: 50,
                      child: Column(
                        children: [
                          Container(height: 2,color: Colors.white,),
                          Container(
                            height: 48,
                            child: Flex(
                              direction: Axis.horizontal,
                              children: [
                                Expanded(
                                  flex: 1,
                                    child: TextButton(
                                        onPressed: (){
                                          Navigator.pop(context,"confirm");
                                          onFinish;
                                        },
                                        child: const Text(
                                          "确定",
                                          style: TextStyle(
                                              color: Colors.white
                                          ),
                                        )
                                    )
                                ),
                                Container(width: 2,color: Colors.white,),
                                Expanded(
                                  flex: 1,
                                    child: TextButton(
                                        onPressed: (){
                                          Navigator.pop(context,"cancel");
                                        },
                                        child: const Text(
                                          "取消",
                                          style: TextStyle(
                                              color: Colors.white
                                          ),
                                        )
                                    )
                                )
                              ],
                            ),
                          )
                        ],
                      ),
                    )
                  )
                ],
              ),
            ),
          )
        ],
      ),
    );
  }
}

路由


在使用MaterialApp()时,可以使用路由,来实现页面之间的切换,而这里面的路由实现方式是通过将页面加入与移出的思想

最简单的页面切换


通过使用Navigator.push来将新页面加入

Navigator.push(context, MaterialPageRoute(
	builder: (context){
		return const NextPage();
	}
));

传参数入页面


  1. 新建一个页面,和一个实体类,并在页面入口方法中添加属性

    class Person{
      String name;
      int age;
      Person(this.name,this.age);
      
      String toString() {
        return "{name:$name,age:$age}";
      }
    }
    class AttrPage extends StatelessWidget{
      final String text;
      final Person person;
      const AttrPage({
        super.key,
        required this.text,
        required this.person
    });
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text("AttrPage"),
            backgroundColor: Colors.cyanAccent,
          ),
          body: Center(
            child: Column(
              children: [
                Text("text:$text"),
                Text("person:${person.toString()}")
              ],
            ),
          ),
        );
      }
    }
    
  2. 在主页面的按钮添加点击事件,用Navigator.push将页面加入栈,并在返回页面的对象里写入属性的参数

    onPressed: (){
    	Navigator.push(context, MaterialPageRoute(
    		builder: (context){
    			return AttrPage(text: "TestText", person: Person("jack",10));
    		}
    	));
    }
    
  3. 效果

在这里插入图片描述

退出页面返回值


通过Navigator.push加入的页面,左上角都会有一个返回的按钮,但是这个按钮返回值null,所以需要Navigator.pop在退出页面时,返回一个值

  1. 新建一个页面,并添加一个按钮,添加点击事件,使用Navigator.pop返回页面

    class BackAttrPage extends StatelessWidget{
      const BackAttrPage({super.key});
      
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text("BackAttrPage"),
            backgroundColor: Colors.green,
          ),
          body: Center(
            child: ElevatedButton(
              onPressed: (){
                Navigator.pop(context,"hello world");
              },
              child: const Text("返回"),
            ),
          ),
        );
      }
    }
    
  2. 在主页添加按钮进入这个界面,但是这里的方法需要在()后加async表示这个方法为异步方法,并在Navigator.push前加await表示等待这个方法运行完,在进行下一步,然后用一个变量给Navigator.push赋值,为了接收页面的返回值

    onPressed: ()async{
    	var result = await Navigator.push(context, MaterialPageRoute(
    		builder: (context){
    			return const BackAttrPage();
    		}
    	));
    	 print("返回值:$result");
    }
    
  3. 点击默认的返回按钮和点击自己的返回按钮的效果

在这里插入图片描述

使用MaterialApp()的routes


MaterialApp()有个routes属性,这个属性是为了方便添加页面,形式为"路由名":(context) => const 页面()

routes: {
	"route1":(context) => const Router1Page(),
	"route2":(context) => const Router2Page()
}

然后我们就可以通过Navigator.pushNamed(context,"路由名")去进入页面

传参问题


因为通过routes命名路由进入页面的方式无法直接传输参数,但是Navigator.pushNamed里的arguments可以将参数传输到页面里

  1. 在按钮处添加事件,并在Navigator.pushNamed里添加arguments属性,将要传的参数写在{}里,以,隔开

    onPressed: (){
    	Navigator.pushNamed(
    		context,
    		"route1",
    		arguments: [
    			"TestText",
    			123
    		]
    	);
    }
    
  2. 在返回页面的方法里,通过ModalRoute.of(context)?.settings.arguments获取传入的数据,如果只传一个参数,那就直接将变量转成String类型即可,但是如果是其他的形式,例如:我传的是一个List,我需要将变量转成字符串,然后再将它转换成List才能拿出来使用

    class Router1Page extends StatelessWidget{
      const Router1Page({super.key});
    
      
      Widget build(BuildContext context) {
        var args = ModalRoute.of(context)?.settings.arguments;
        List<String> argList = args.toString().substring(1,args.toString().length-1).split(",");
        return Scaffold(
          appBar: AppBar(
            title: const Text("Page1111"),
            backgroundColor: Colors.cyan,
          ),
          body: Center(
            child: Column(
              children: [
                Text("text:${argList[0]}"),
                Text("num:${argList[1]}"),
              ],
            ),
          ),
        );
      }
    }
    
  3. 效果

在这里插入图片描述

返回值[与普通的一样]


返回值的方法与普通进入页面的方法一样,没有问题

效果:

在这里插入图片描述

route的钩子函数


顾名思义就是访问routes时,执行的函数

函数说明
onGenerateRoute当页面没有Route命名但依旧使用Navigator.pushNamed会执行*[需要单独出来讲]*
onUnknownRoute当页面没有Route命名但依旧使用Navigator.pushNamed会执行,防止访问未知页面用
navigatorObservers监控页面的入栈出栈

onGenerateRoute与onUnknownRoute


onGenerateRoute

这个函数的使用方法是为了:

  • 使用路由命名方式传参的问题
  • 防止没有权限的用户访问页面
  • 访问空页面[不适用]

在使用这个函数的时候,需要一个参数RouteSettings settings,通过settings.name可以获取当前使用Navigator.pushNamed时,访问的路由名

onGenerateRoute: (RouteSettings settings){
      var routeName = settings.name;
      return null;
}

可以通过给访问特定的路由名加个权限,或者传参

//MaterialApp里的onGenerateRoute属性
onGenerateRoute: (RouteSettings settings){
      var routeName = settings.name;
      if(routeName == "noReg"){
        print("如果没有注册在route里,但是依旧要Navigator.pushNamed访问,会显示这一行,route name:$routeName");
        return MaterialPageRoute(builder: (context){
          return const Route2Page(text: "testText",);
        }
        );
      }
      return null;
}
//页面
class Route2Page extends StatelessWidget{
  const Route2Page({super.key,required this.text});
  final String text;
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("route2"),
        backgroundColor: Colors.amberAccent,
      ),
      body: Center(
        child: Text("没在routes注册,也能通过Navigator.pushNamed访问,传入值:$text"),
      ),
    );
  }
}
onUnknownRoute

这个函数的执行时机在于当页面没有Route命名但依旧使用Navigator.pushNamedonGenerateRoute没有返回页面

也就是当页面不存在的时候,这个函数会执行,且可以自定义不存在页面

//MaterialApp里的onUnknownRoute属性
onUnknownRoute: (RouteSettings settings){
      print("访问了不存在的页面,");
      return MaterialPageRoute(builder: (context){
        return const NoExitPage();
      });
    }
//不存在页面提示页面
class NoExitPage extends StatelessWidget{
  const NoExitPage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("页面不存在"),
        backgroundColor: Colors.amberAccent,
      ),
      body: const Center(
        child: Text("访问了不存在的页面!"),
      ),
    );
  }
}

navigatorObservers


这个钩子函数,会全程监控页面的入栈和出栈,只要栈堆有变化,就会执行

import 'package:flutter/material.dart';
// demo21 监控路由
void main(){
  runApp(MaterialApp(
    navigatorObservers: [DemoNO()],
    home: const HomePage(),
    routes: {
      "test1": (context) => const Test1Page(),
      "test2": (context) => const Test2Page(),
    },
  ));
}

class DemoNO extends NavigatorObserver{
  
  void didPush(Route route, Route? previousRoute) {
    var name = route.settings.name;
    print("$name页面被加入栈");
    super.didPush(route, previousRoute);
  }
  
  void didPop(Route route, Route? previousRoute) {
    var name = route.settings.name;
    print("$name页面被移出栈");
    super.didPop(route, previousRoute);
  }
}

在这里插入图片描述

demo


Turing158/flutter-demo (github.com)

Turing_ICE/flutter-demo (gitee.com)


待更新 …

  • 21
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Turing158

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

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

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

打赏作者

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

抵扣说明:

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

余额充值