一、认识Fluter
几乎完全还原手机app,相当于原生app。
二、环境搭建(Windows)
Windows 7以上64位系统,磁盘空间大于3个G,因为要安装模拟虚拟机
1、java环境的安装,下载地址:java下载地址--自己现在相应的版本,下载安装完成之后,在终端中输入 java,出现选项帮助即为安装成功
2、下载安装FlutterSDK,下载地址:FlutterSDK下载地址,下载安装完成之后,在Flutter安装目录的flutter
文件下找到flutter_console.bat
,双击运行并启动flutter命令行,接下来,你就可以在Flutter命令行运行flutter命令了。
2.1、配置环境变量,整个过程需要,若要在任何地方都可以使用Flutter命令。配置Flutter SDk目录配置到系统环境变量Path中D:\Flutter\flutter\bin
2.2、进行Flutter doctor -v测试,确认安装的Flutter是否正确无误,在终端中输入flutter doctor -v,结果中有X的说明安装是有问题的,点根烟,看看窗外,不要急,进行下一步,安装Android Studio
2.3、傻瓜式安装,点击下一步,整个过程需要,Android Studio下载地址:Android Studio,找到下载点击下载就好,赶紧安吧。
整个过程持续时间比较久,男人嘛,久点好。大哥,你要是真的不会装,送你一个详细教程:不会装就看他装,OK,安装成功之后,就如插件商店Plugin,搜索Flutter插件,点中间的Search in repositories
,然后点击安装。别犹豫下载他,完成之后重启。
2.4、此时我们可以在终端中再次输入 flutter doctor -v ,发现缺少证书,我们继续安装证书。在终端中输入
flutter doctor --android-licenses
见到需要选择Y/N的,一律选择Y,直到证书安装完成,先不要高兴,下面一步进行虚拟机安装
2.5、先新建一个flutter文件,然后安装虚拟机,点击Android Studio中的上方菜单tool
-AVD Manager
选项。出现新建菜单,选择Create Virtual Device.....
,如果你一个虚拟机也没建过,这个选项在对话框的中间,选择虚拟机类型,这个你随意选就好,我选择的是Nexus 5x
。选择系统,这里尽量选择最新的,我选择了Android 9.0
系统,安装好后,点击开始按钮,运行虚拟机,虚拟机运行之后,可以点击debug按钮,你可能会遇到一些小问题,不要急,看下一步
2.6、第一步:修改掉项目下的android目录下的build.gradle
文件,把google() 和 jcenter()这两行去掉。改为阿里的链接。
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
全部代码:
buildscript {
repositories {
// google()
// jcenter()
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public'}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
}
}
allprojects {
repositories {
// google()
// jcenter()
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
注意是有两个部分进行了修改,不要只修改一处。
第二步:修改Flutter SDK包下的flutter.gradle
文件,这个目录要根据你的SDK存放的位置有所变化。比如我放在了D盘Flutter目录下,那路径就是这个。
D:\Flutter\flutter\packages\flutter_tools\gradle
打开文件进行修改,修改代码如下
buildscript {
repositories {
//jcenter()
// maven {
// url 'https://dl.google.com/dl/android/maven2'
// }
maven{
url 'https://maven.aliyun.com/repository/jcenter'
}
maven{
url 'http://maven.aliyun.com/nexus/content/groups/public'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
}
}
然后再重新Debug一下,就基本可以启动起来。
2.7、在vsCode中使用flutter,只需要下载Flutter即可,为了加快我们的开发速度,我们来写一个快速启动虚拟机的批处理文件,
emulator.exe
这个程序,你可以巧妙利用windows的查找工具进行查找。新建一个xxx.bat
文件到桌面,xxx的意思是,你可以自己取名字,随意叫什么都可以。我这里叫EmulatorRun.bat
.
查找emulator.exe
文件的路径,把查找到的路径放到bat文件中, 你一般会查找到两个emulator.exe文件,一个是在tools目录下,一个是在emulator目录下,我们选择emulator
目录下的这个,复制它的路径。
C:\Users\Administrator\AppData\Local\Android\Sdk\emulator\emulator.exe
3,打开Android Studio
,并查看你的AVD虚拟机名称, 如果你觉的输入不方便和怕出错,你可以点击图片后边的笔型按钮,进入编辑模式,复制这个名称。
4.然后根据你复制的名称,把bat文件输入成如下形式。
C:\Users\Administrator\AppData\Local\Android\Sdk\emulator\emulator.exe -netdelay none -netspeed full -avd Nexus_5X_API_28
参数解释:
2.8、现在可以跑起来flutter项目了,在终端中输入 flutter run 等待启动
flutter run
三、Flutter常用组件
Text Widget 文本组件的使用
TextAlign属性
TextAlign属性就是文本的对齐方式,它的属性值有如下几个(详细请看视频中讲解):
child:Text(
'Hello JSPang ,非常喜欢前端,并且愿意为此奋斗一生。我希望可以出1000集免费教程。',
textAlign:TextAlign.left,
)
maxLines属性
设置最多显示的行数,后面直接跟数字即可
child:Text(
'Hello JSPang ,非常喜欢前端,并且愿意为此奋斗一生。我希望可以出1000集免费教程。',
textAlign:TextAlign.left,
maxLines: 1,
)
overflow属性
overflow属性是用来设置文本溢出时,如何处理,它有下面几个常用的值供我们选择。
style属性
style属性的内容比较多
child:Text(
'Hello JSPang ,非常喜欢前端,并且愿意为此奋斗一生。我希望可以出1000集免费教程。',
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,//下划线属性
),
)
更详细的属性资料可以参看这个网址:https://docs.flutter.io/flutter/painting/TextStyle-class.html
Container容器组件的使用
Container(容器控件)在Flutter是经常使用的控件,它就相当于我们HTML里的 Alignment属性 其实容器的作用就是方便我们进行布局的,这个属性针对的是Container内child的对齐方式,也就是容器子内容的对齐方式,并不是容器本身的对齐方式。 设置宽、高和颜色属性 padding的属性就是一个内边距,它和你使用的前端技术CSS里的 如何单独设置某一边的内边距使用** 那我们设置上边距为30,左边距为10,就可以用下面的代码来编写。 margin属性 decoration属性 比如你需要给背景加入一个渐变,这时候需要使用BoxDecoration这个类,代码如下(需要注意的是如果你设置了decoration,就不要再设置color属性了,因为这样会冲突) 设置边框 设置边框可以在decoration里设置border属性 Image图片组件的使用 加入图片的几种方式 fit属性的设置 fit属性可以控制图片的拉伸和挤压 BoxFit.fill:全图显示,图片会被拉伸,并充满父容器。 BoxFit.contain:全图显示,显示原比例,可能会有空隙。 BoxFit.cover:显示可能拉伸,可能裁切,充满(图片要充满整个容器,还不变形)。 BoxFit.fitWidth:宽度充满(横向充满),显示可能拉伸,可能裁切。 BoxFit.fitHeight :高度充满(竖向充满),显示可能拉伸,可能裁切。 BoxFit.scaleDown:效果和contain差不多,但是此属性不允许显示超过源图片大小,可小不可大 图片的混合模式 图片混合模式(colorBlendMode)和color属性配合使用,能让图片改变颜色,里边的模式非常的多,产生的效果也是非常丰富的 repeat图片重复 ImageRepeat.repeat : 横向和纵向都进行重复,直到铺满整个画布。 ImageRepeat.repeatX: 横向重复,纵向不重复。 ImageRepeat.repeatY:纵向重复,横向不重复。 ListView 列表组件简介 列表组件的知识其实是很多的 我们使用了ListView,然后在他的内部 图片列表的使用 上节课学习了Image Widget 已经对ListView有了清楚的认识,还是使用我们的ListView组件,只是在ListView组件里加一个 我们先是加入了Center组件,作用是让我们的横向列表可以居中到屏幕的中间位置,然后在center组件的下面加入了Container容器组件,并设置了容器组件的高是200,在容器组件里我们加入了 scrollDirection属性 ListView组件的 代码优化,组件拆分 动态列表的使用 List类型的使用 List是Dart的集合类型之一,其实你可以把它简单理解为数组(反正我是这么认为的),其他语言也都有这个类型。它的声明有几种方式: 那我们这里使用的是一个List传递,然后直接用List中的 说明:再 generate方法传递两个参数,第一个参数是生成的个数,第二个是方法。 接受参数 我们已经传递了参数,那MyApp这个类是需要接收的。 这是一个构造函数,除了Key,我们增加了一个必传参数,这里的 这样我们就可以接收一个传递过来的参数了,当然我们要事先进行声明。 动态列表 ListView.builder() 接受了值之后,就可以直接调用动态列表进行生成了。具体代码如下: GridView网格列表组件 列表组件已经学会了,那还有一种常用的列表,叫做网格列表,网格列表经常用来显示多张图片,比如我们经常使用的手机里的相册功能,大部分形式都是网格列表。 我们先不做一个相册的应用,而是使用文字,作一个最简单的网格列表,目的是先熟悉一下 我们在body属性中加入了网格组件,然后给了一些常用属性: 图片网格列表 childAspectRatio:宽高比,这个值的意思是宽是高的多少倍,如果宽是高的2倍,那我们就写2.0,如果高是宽的2倍,我们就写0.5。 四、Flutter布局 水平布局Row的使用 Flutter中的row控件就是水平控件,它可以让Row里边的子元素进行水平排列。Row控件可以分为灵活排列和非灵活排列两种,这两种模式都需要熟练掌握,等经验丰富后可根据需求进行使用。 不灵水平布局 从字面上就可以理解到,不灵活就是根据Row子元素的大小,进行布局。如果子元素不足,它会留有空隙,如果子元素超出,它会警告。 比如现在我们要制作三个按钮,并让三个按钮同时在一排。我们写下了如下代码,但你会发现效果并不理想。 这时候你会发现的页面已经有了三个按钮,但这三个按钮并没有充满一行,而是出现了空隙。这就是不灵活横向排列造成的。它根据子元素的大小来进行排列。如果我们想实现充满一行的效果,就要使用灵活水平布局了。 灵活水平布局 解决上面有空隙的问题,可以使用 灵活和不灵活的混用 如果这时候想让中间的按钮大,而两边的按钮保持真实大小,就可以不灵活和灵活模式进行混用,实现效果。代码和效果如下: 垂直布局Column组件 Column组件即垂直布局控件,能够将子组件垂直排列。 左对齐只要在column组件下加入下面的代码,就可以让文字左对齐。 主轴和副轴的辨识 在设置对齐方式的时候你会发现右mainAxisAlignment属性,意思就是主轴对齐方式,那什么是主轴,什么又是幅轴那。 main轴:如果你用column组件,那垂直就是主轴,如果你用Row组件,那水平就是主轴。 cross轴:cross轴我们称为幅轴,是和主轴垂直的方向。比如Row组件,那垂直就是幅轴,Column组件的幅轴就是水平方向的。 比如现在我们要把上面的代码,改成垂直方向居中。因为用的是Column组件,所以就是主轴方向,这时候你要用的就是主轴对齐了。 现在全部的代码如下: 水平方向相对屏幕居中 让文字相对于水平方向居中,只要加入Center组件就可以轻松解决。 Expanded属性的使用 其实在学习水平布局的时候我们对Expanded有了深刻的理解,它就是灵活布局。比如我们想让中间区域变大,而头部区域和底部根据文字所占空间进行显示。 Stack层叠布局 水平布局和垂直布局确实很好用,但是有一种情况是无法完成的,比如放入一个图片,图片上再写一些字或者放入容器,这时候Row和Column就力不从心了。Flutter为这种情况准备了Stack层叠布局 层叠布局的 alignment 属性 alignment属性是控制层叠的位置的,建议在两个内容进行层叠时使用。它有两个值X轴距离和Y轴距离,值是从0到1的,都是从上层容器的左上角开始算起的 CircleAvatar组件的使用 现在我们准备放入一个图像,然后把弧度设置成100,形成一个漂亮的圆形,代码如下: 效果代码 想布局出这个效果还是比较容易的,代码如下: Stack的Positioned属性 上节课已经学习了 Positioned组件的属性 实现图片中的布局,代码如下: 卡片组件布局 Flutter还有一种比较比较酷炫的布局方式,我称 它为卡片式布局 实例开发 比如我们现在要开发一个类似收获地址的列表,并且列表外部使用一个卡片式布局。 卡片式布局默认是撑满整个外部容器的,如果你想设置卡片的宽高,需要在外部容器就进行制定 代码中使用了一个垂直布局组件Column组件,然后利用了 五、页面导航和其他知识 一般页面导航和返回 RaisedButton按钮组件 它有两个最基本的属性: Navigator.push 和 Navigator.pop 写一个Demo 我们现在就来作一个简单的案例,我们打开一个页面,页面上只有一个简单的按钮,按钮写着“查看商品详情页面”,然后点击后进入下一个页面,页面有一个按钮,可以直接返回。 导航参数的传递和接收 声明数据结构类 Dart中可以使用类来抽象一个数据,比如我们模仿一个商品信息,有商品标题和商品描述。我们定义了一个Product类,里边有两个字符型变量,title和description。 代码如下: 构建一个商品列表 作一个商品的列表,这里我们采用动态的构造方法,在主方法里传递一个商品列表(List)到自定义的Widget中。 先来看看主方法的编写代码: 上面的代码是主路口文件,主要是在home属性中,我们使用了ProductList,这个自定义组件,而且时候会报错,因为我们缺少这个组件。这个组件我们传递了一个products参数,也就是商品的列表数据,这个数据是我们用 ProductList自定义组件的代码: 先接受了主方法传递过来的参数,接受后用 导航参数的传递 我们还是使用 子页面接受参数并显示 现在需要声明 Demo全部代码如下 为了更好的帮助大家学习,我把所有的传递参数和接受参数的代码附在了下面。 页面跳转并返回数据 这节课学一下页面跳转后,这节课学一下页面跳转后,当我们返回页面时返回结果到上一个页面(也就是父页面)。这样的场景经常用于,我们去子页面选择了一项选项,然后把选择的结果返回给父级页面。 异步请求和等待 Dart中的异步请求和等待和ES6中的方法很像,直接使用async...await就可以实现。比如下面作了一个找小姐姐的方法,然后进行跳转,注意这时候是异步的。等待结果回来之后,我们再显示出来内容。具体代码如下 SnackBar的使用 返回数据的方式 返回数据其实是特别容易的,只要在返回时带第二个参数就可以了。 找小姐姐Demo代码 静态资源和项目图片的处理 pubspec.yaml 文件 如果想配置项目资源文件,就需要使用 比如在项目根目录下新建了一个 使用项目图片资源 有了声明后,我们就可以直接在项目中引用这个文件了。这里使用最简单的代码结构,只用了一张图片。代码如下: Flutter客户端打包 到现在为止学Android客户端如何打包apk。 配置APP的图标 想配置APP的图片,你需要找到下面的目录: 项目根目录/android/app/src/main/res/ 进入之后你会看到很多mipmap-为前缀命名的文件夹,后边的是像素密度,可以看出图标的分辨率。 将对应像素密度的图片放入对应的文件夹中,图片记得用png格式,记得名字要统一,才能一次性进行配置。 AndroidManifest.xml 文件 这个文件主要用来配置APP的名称、图标和系统权限,所在的目录在: 项目根目录/android/app/src/main/AndroidManifest.xml 生成 keystore 这里的坑挺多的 使用这个命令:这时候可以用下面的命令找到keytool.exe的位置。 这时候你直接拷贝命令并进行输入,但这里也有个坑,就是如果文件夹中间带有空空,你需要用带引号扩上。 这时候就可以创建成功了。你的D盘下面就会有一个Jks的文件,记住这个文件不能共享给任何人。 有了这个key.jks文件后,可以到项目目录下的 我的文件最后是这样的: 配置key注册 key生成好后,需要在build.gradle文件中进行配置。这个过程其实很简单,就是粘贴复制一些东西,你是不需要知道这些文件的具体用处的。 第一项: 进入项目目录的/android/app/build.gradle文件,在 把如下代码进行替换 替换成的代码: 直接在终端中输入: 这时候就打包成功了 如何进行真机调试: 使用数据线连接电脑,打开开发者模式,USB调试模式,此时Android Studio会自动识别,使用flutter devices 查看是否有真机存在,有的时候可能是驱动没有安装成功,在手机上安装一个豌豆荚即可,如果正确连接真机,此时运行flutter run 等待完全运行成功之后,app就安装到手机上了。 底部导航栏制作 app必备之功能: 主入口文件的编写 首先我们先写一个主入口文件,这个文件只是简单的APP通用结构,最主要的是要引入自定义的 此时BottomNaivgationWidget组件还没编写,此时会报错 StatefulWidget 讲解 在编写 在lib目录下,新建一个 它的初始化和以前使用的 上面的代码可以清楚的看到,使用 BottomNaivgationWidget自定义 接下来我们就要创建 此时,运行flutter run 进行查看,此时在底部已经出现导航栏了,但是点击还没有效果,下面我们继续来实现 子页面的编写 子页面我们就采用最简单的编写了,只放入一个 先来写一个HomeScreen组件,新建一个pages目录,然后在目录下面新建 有了这个文件剩下的文件就可以复制粘贴,然后改少量的代码来完成了。 分别建立: 这些都是导航要用的子页面,有了这些页面,我们才能继续编写代码。 重写initState()方法 我们要重新 代码如下: 这里的 BottomNavigationBar里的响应事件 现在给出全部的 不规则底部工具栏制作 大部分的底部导航都是中规中矩的,但有些时候也需要突出个性,比如在中间部位增加一个突出的按钮 自定义主题样本 Flutter支持自定义主题,如果使用自定义主题,设置的内容项是非常多的,这可能让初学者头疼,Flutter贴心的为给我们准备了主题样本。 primarySwatch :现在支持18种主题样本了。 会了这个知识后,我们就可以先把我们的主入口文件编写一下了,具体代码如下: 这时候 floatingActionButton Widget 一般来说,它是一个圆形,中间放着图标,会优先显示在其他Widget的前面。 下面我们来看看它的常用属性: onPressed :点击相应事件,最常用的一个属性。 tooltip:长按显示的提示文字,因为一般只放一个图标在上面,防止用户不知道,当我们点击长按时就会出现一段文字性解释。非常友好,不妨碍整体布局。 child :放置子元素,一般放置Icon Widget。 我们来看一下 写完这些代码已经有了一个悬浮的按钮,但这个悬浮按钮还没有和低栏进行融合,这时候需要一个属性。 此时就和底部导航融合为一体了 BottomAppBar Widget 截止到上面的代码,基本功能都已经实现,但是还没有交互效果 StatefulWidget子页面的制作 在前两节课的实例中我们使用了子页面,但子页面继承与 新建一个 代码中设置了一个内部的 按钮交互效果的制作 这些效果都是在 新建两个变量,主要作用是控制body中的视图,也就是显示不同的子页面。 下一步是为 剩下的就是写个个按钮的交互事件,交互的动作分两种: 直接打开子导航,比如我们点击了中间的”+“按钮,我们直接开启子页面。 酷炫的路由动画 现在Flutter的路由效果已经非常不错了,能满足大部分App的需求,但是谁不希望自己的App更酷更炫那,学完这节课你就可以给自己的APP加上酷炫的路由动画了。 其实路由动画的原理很简单,就是重写并继承 主入口方法 先编写一个主入口方法,还是最简单的格式,只不过home属性,使用的 pages.dart页面的编写 主入口文件用 上面代码中有一个新知识点,需要学习一下: 写完这个页面代码后,已经可以进行最基本的导航了,但是并没有什么酷炫的动画。 自定义CustomRoute Widget 新建一个 构造方法可以简单理解为:只要一调用这个类或者说是Widget,构造方法里的所有代码就执行了。 custome_router.dart代码详情 animate :动画的样式,一般使用动画曲线组件(CurvedAnimation)。 curve: 设置动画的节奏,也就是常说的曲线,Flutter准备了很多节奏,通过改变动画取消可以做出很多不同的效果。 transitionDuration:设置动画持续的时间,建议再1和2之间。 写完代码,我们已经可以看到在切换路由时有了动画效果, 缩放路由动画 旋转+缩放路由动画 旋转+缩放的思路是在一个路由动画里的child属性里再加入一个动画,让两个动画同时执行。来看详细代码: 左右滑动路由动画 其实用的做多的还是左右滑动路由动画,其实实现起来也是非常简单,直接使用 总结:动画的使用会让我们的APP更加酷炫,也会让别人觉的你不是一个新手,再Flutter里使用动画是非常方便的,所以你可以把这些动画效果事先写好,在工作中直接使用。 毛玻璃效果制作 Flutter的Fliter Widget 也是非常强大的,它可以制作出你想要的神奇滤镜效果。这节我们就以实战的方式,制作一个毛玻璃效果,通过实例来学习Fitler 的用法 main.dart文件编写 这个和以前的写法都一样,所以就直接贴代码了。详细解释会在视频中解说,不过我相信不解释,小伙伴也一定可以看明白 BackdropFilter Widget 我们新建一个 这个代码嵌套很多,所以一定要注意你的代码层次,容易出错的地方就是嵌套错误。这个效果尽量少用,因为我测试了一下,它对系统的消耗还是比较大的。 保持页面状态 在工作中切换页面时,再切换回来,时要求页面状态不发生改变的。这能给APP浏览者最好的体验,机会所有的APP都有这个需求,属于一个大众需求。这节课我们就来看看这样的效果如何实现 With 关键字的使用 with是dart的关键字,意思是混入的意思,就是说可以将一个或者多个类的功能添加到自己的类无需继承这些类, 避免多重继承导致的问题。 需要注意的是with后边是Mixin,而不是普通的Widget,这个初学者比较爱犯错误。需要强调一下。 TabBar Widget的使用 基本页面布局 我们先把入口页面布局好,下节课我们再让他保持状态。学了上面两个知识,你其实可以做出来布局了。全部代码如下: 下面我们来实现页面保持状态 那为了能看出是保持状态的,我们作一个按钮,然后点一下加一。就跟Flutter为我们生成的例子一样。 实现功能的全部代码: 写完这个Widget,然后在入口文件中引入。 然后把Body区域改成我们刚写的MyHomePage Widget 就可以了,注意是改三个。 一个不简单的搜索条 搜索这个功能,大部分APP都会存在,这节课我们就学习一下,如何做一个有提示功能,而且交互很好的搜索条 主入口文件 这个还是继承 数据文件 asset.dart searchList : 这个相当于数据库中的数据,我们要在这里进行搜索。 recentSuggest : 目前的推荐数据,就是搜索时,自动为我们进行推荐。 整体代码如下 : AppBar的样式制作 这节课我们先把第一个搜索界面布好,下节课我们主要作搜索的交互效果。看下面的代码: 这时候就可以在虚拟机中进行预览了,但是这时候点击搜索按钮还没有任何反应。 现在我们来实现点击搜索图标后,变成搜索条的样式,并且有一定的交互效果。 在点击图标时执行 重写buildActions方法: 代码如下: buildLeading 方法重写 这个时搜索栏左侧的图标和功能的编写,这里我们才用 buildResults方法重写 buildSuggestions方法重写 这个方法主要的作用就是设置推荐,就是我们输入一个字,然后自动为我们推送相关的搜索结果,这样的体验是非常好的。 具体代码如下: 为了方便小伙伴们学习,给出所有 流式布局 模拟添加照片效果 这节已一个模拟添加多张照片的小实例,主要学习一下流式布局在Flutter里的应用。如果你作为一个前端开发者,那这节课的内容将非常容易。 mediaQuery 媒体查询 使用 Wrap流式布局 Flutter中流式布局大概有三种常用方法,这节课先学一下Wrap的流式布局。有的小伙伴会说Wrap中的流式布局可以用Flow很轻松的实现出来,但是Wrap更多的式在使用了Flex中的一些概念,某种意义上说式跟Row、Column更加相似的。 单行的Wrap跟Row表现几乎一致,单列的Wrap则跟Column表现几乎一致。但Row与Column都是单行单列的,Wrap则突破了这个限制,mainAxis上空间不足时,则向crossAxis上去扩展显示。 从效率上讲,Flow肯定会比Wrap高,但Wrap使用起来会更方便一些。 这个会在实例中用到,所以,我在实例中会讲解这个代码。 GestureDetector 手势操作 入口文件 入口文件很简单,就是引用了 warp_demo.dart 主要的文件代码我就列在下面了 展开闭合案例 手机的屏幕本身就很小,所以要合理利用空间,把主要的元素展示出来,次要或者不重要的元素等用户向看的时候再给用户展示。这类操作最常见的交互就是展开和闭合了。这节课我们主要学习一下 ExpansionTile组件 main.dart入口文件 expansion_tile.dart 扩展组件 这个文件是我们的主要学习文件,但是并不复杂,就是一个可展开组件。全部代码如下: 这时候就可以预览了,效果也应该出现了。 展开闭合列表案例 上节课学的只是一个单个的展开闭合组件,你当然可以把这个组件作为List元素,组成一个数组,形成列表。不过Flutter也很贴心的为提供了一个ExpansionPanelList Widget,它可以实现展开闭合的列表功能。 需要注意的是这个列表必须放在可滑动组件中使用,否则会报错 ExpansionPanelList 常用属性 ExpandStateBean 自定义类 为了方便管理制作了一个 expansion_panel_list.dart 这个文件我就直接上代码了 贝塞尔曲线切割 现在人们对于网站的美感要求是越来越高了,所以很多布局需要优美的曲线设计。当然最简单的办法是作一个PNG的透明图片,然后外边放一个 去掉DeBug图标 在讲正式内容之前,先回答小伙伴们的一个问题,就是如何去掉DeBug图标。在我们进行编写代码预览时,有一Debug的图标一直在屏幕上,确实不太美观,其实只要语句代码就可以去掉的。 这个代码要配置在主入口文件里,全部代码如下 ClipPath 路径裁切控件 在 CustomClipper 裁切路径 我们主要的贝塞尔曲线路径就写在 一个二阶的贝塞尔曲线是需要控制点和终点的,控制点就像一块磁铁,把直线吸引过去,形成一个完美的弧度,这个弧度就是贝塞尔曲线了。 我们先来熟悉一下裁切路径和贝塞尔曲线,作一个最简单的贝塞尔曲线出来。 全部代码如下: 波浪形式的贝塞尔曲线 上节课已经对知识点有了了解,这节课我们主要就是加大一些难度,作个更复杂的贝塞尔裁切出来。 这节课主要改造上节课的代码,作一个波浪形的贝塞尔裁切。波浪形式的只要把裁切变成两个对称的贝塞尔曲线就可以实现了。代码如下: 这两节课的主要内容就是如何裁切和贝塞尔曲线的原理,其实裁切还有圆形裁切、圆角裁切和矩形裁切,因为都比较容易,我就不再讲解了。 打开应用的闪屏动画案例 打开一个APP,经常会看到精美的启动页,这种启动页也称为闪屏动画。它是从无到有有一个透明度的渐变动画的。图像展示完事后,才跳转到用户可操作的页面。这节课主要学习一下闪屏动画的制作。 AnimationController 我们这里有了两个参数 : 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:Container(
child:new Text('Hello JSPang',style: TextStyle(fontSize: 40.0),),
alignment: Alignment.center,
),
),
),
);
}
}
bottomCenter
:下部居中对齐。botomLeft
: 下部左对齐。bottomRight
:下部右对齐。center
:纵横双向居中对齐。centerLeft
:纵向居中横向居左对齐。centerRight
:纵向居中横向居右对齐。topLeft
:顶部左侧对齐。topCenter
:顶部居中对齐。topRight
: 顶部居左对齐设置宽、高和颜色属性
child:Container(
child:new Text('Hello JSPang',style: TextStyle(fontSize: 40.0),),
alignment: Alignment.center,
width:500.0,
height:400.0,
color: Colors.lightBlue,
),
padding属性
padding
表现形式一样child:Container(
child:new Text('Hello JSPang',style: TextStyle(fontSize: 40.0),),
alignment: Alignment.topLeft,
width:500.0,
height:400.0,
color: Colors.lightBlue,
padding:const EdgeInsets.all(10.0),
),
EdgeInsets.fromLTRB(value1,value2,value3,value4)
**padding:const EdgeInsets.fromLTRB(10.0,30.0,0.0,0.0),
child:Container(
child:new Text('Hello JSPang',style: TextStyle(fontSize: 40.0),),
alignment: Alignment.topLeft,
width:500.0,
height:400.0,
color: Colors.lightBlue,
padding:const EdgeInsets.fromLTRB(10.0,30.0,0.0,0.0),
margin: const EdgeInsets.all(10.0),
),
decoration
是 container 的修饰器,主要的功能是设置背景和边框。child:Container(
child:new Text('Hello JSPang',style: TextStyle(fontSize: 40.0),),
alignment: Alignment.topLeft,
width:500.0,
height:400.0,
padding:const EdgeInsets.fromLTRB(10.0,30.0,0.0,0.0),
margin: const EdgeInsets.all(10.0),
decoration:new BoxDecoration(
gradient:const LinearGradient(
colors:[Colors.lightBlue,Colors.greenAccent,Colors.purple]
)
),
),
child:Container(
child:new Text('Hello JSPang',style: TextStyle(fontSize: 40.0),),
alignment: Alignment.topLeft,
width:500.0,
height:400.0,
padding:const EdgeInsets.fromLTRB(10.0,30.0,0.0,0.0),
margin: const EdgeInsets.all(10.0),
decoration:new BoxDecoration(
gradient:const LinearGradient(
colors:[Colors.lightBlue,Colors.greenAccent,Colors.purple]
),
border:Border.all(width:2.0,color:Colors.red)
),
),
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:Container(
child:new Image.network(
'http://jspang.com/static/myimg/blogtouxiang.jpg',
scale:1.0,
),
width:300.0,
height:200.0,
color: Colors.lightBlue,
),
),
),
);
}
}
child:new Image.network(
'http://jspang.com/static/myimg/blogtouxiang.jpg',
color: Colors.greenAccent,
colorBlendMode: BlendMode.darken,
),
child:new Image.network(
'http://jspang.com/static/myimg/blogtouxiang.jpg',
repeat: ImageRepeat.repeat,
),
body: new ListView(
children:
children
中,使用了widget
数组,因为是一个列表,所以它接受一个数组,然后有使用了listTite组件(列表瓦片),在组件中放置了图标和文字。body: new ListView(
children:
body: new ListView(
children:
横向列表的使用
ScrollDirection
属性。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:Container(
height:200.0,
child:new ListView(
scrollDirection: Axis.horizontal,
children:
ListView
组件,然后设置了组件的scrollDirection属性。然后再ListView的子组件里加入了Container容器组件,然后设置了不同颜色,scrollDirection
属性只有两个值,一个是横向滚动,一个是纵向滚动。默认的就是垂直滚动,所以如果是垂直滚动,我们一般都不进行设置。
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:Center(
child:Container(
height:200.0,
child:MyList()
),
),
),
);
}
}
class MyList extends StatelessWidget{
@override
Widget build(BuildContext context){
return ListView(
scrollDirection: Axis.horizontal,
children:
var myList = List()
: 非固定长度的声明。var myList = List(2)
: 固定长度的声明。var myList= List
:固定类型的声明方式。var myList = [1,2,3]
: 对List直接赋值。generate
方法进行生产List里的元素。最后的结果是生产了一个带值的List变量。代码如下:void main () => runApp(MyApp(
items: new List
main
函数的runApp中调用了MyApp类,再使用类的使用传递了一个items
参数,并使用generate生成器对items
进行赋值。final List
@required
意思就必传。:super
如果父类没有无名无参数的默认构造函数,则子类必须手动调用一个父类构造函数。import 'package:flutter/material.dart';
void main () => runApp(MyApp(
items: new List
GridView
的基本语法,代码如下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:
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
),
children:
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(
appBar:new AppBar(
title:new Text('水平方向布局'),
),
body:new Row(
children:
Expanded
来进行解决,也就是我们说的灵活布局。我们在按钮的外边加入Expanded就可以了,代码如下: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(
appBar:new AppBar(
title:new Text('水平方向布局'),
),
body:new Row(
children:
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(
appBar:new AppBar(
title:new Text('水平方向布局'),
),
body:new Row(
children:
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(
appBar:new AppBar(
title:new Text('垂直方向布局'),
),
body:Column(
children:
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
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(
appBar:new AppBar(
title:new Text('垂直方向布局'),
),
body:Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children:
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(
appBar:new AppBar(
title:new Text('垂直方向布局'),
),
body:Column(
mainAxisAlignment: MainAxisAlignment.center,
children:
body:Column(
mainAxisAlignment: MainAxisAlignment.center,
children:
CircleAvatar
这个经常用来作头像的,组件里边有个radius
的值可以设置图片的弧度。new CircleAvatar(
backgroundImage: new NetworkImage('http://jspang.com/static//myimg/blogtouxiang.jpg'),
radius: 100.0,
),
import 'package:flutter/material.dart';
void main () => runApp(MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context ){
var stack = new Stack(
alignment: const FractionalOffset(0.5, 0.8),
children:
Stack
组件,并且进行了两个组件的层叠布局,但是如果是超过两个组件的层叠该如何进行定位那?这就是我们加今天要学的主角Positioned
组件了。这个组件也叫做层叠定位组件。
import 'package:flutter/material.dart';
void main () => runApp(MyApp());
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context ){
var stack = new Stack(
children:
ListTile
实现内部列表,这里需要说明的是ListTile不光可以使用在ListView组件中,然后容器组件其实都可以使用。代码如下.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:
Navigator
组件。
Navigator.push
:是跳转到下一个页面,它要接受两个参数一个是上下文context
,另一个是要跳转的函数。Navigator.pop
:是返回到上一个页面,使用时传递一个context(上下文)参数,使用时要注意的是,你必须是有上级页面的,也就是说上级页面使用了Navigator.push
。import 'package:flutter/material.dart';
void main(){
runApp(MaterialApp(
title:'导航演示1',
home:new FirstScreen()
));
}
class FirstScreen extends StatelessWidget{
@override
Widget build(BuildContext context){
return new Scaffold(
appBar: AppBar(title:Text('导航页面')),
body:Center(
child:RaisedButton(
child:Text('查看商品详情页面'),
onPressed: (){
Navigator.push(context,new MaterialPageRoute(
builder:(context) =>new SecondScreen())
);
},
)
)
);
}
}
class SecondScreen extends StatelessWidget{
@override
Widget build(BuildContext context){
return Scaffold(
appBar:AppBar(title:Text('技术胖商品详情页')),
body:Center(child:RaisedButton(
child:RaisedButton(
child:Text('返回'),
onPressed: (){
Navigator.pop(context);
},
)
))
);
}
}
class Product{
final String title; //商品标题
final String description; //商品描述
Product(this.title,this.description);
}
void main(){
runApp(MaterialApp(
title:'数据传递案例',
home:ProductList(
products:List.generate(
20,
(i)=>Product('商品 $i','这是一个商品详情,编号为:$i')
),
)
));
}
List.generate
生成的。并且这个生成的List原型就是我们刚开始定义的Product这个类(抽象数据)。class ProductList extends StatelessWidget{
final List
ListView.builder
方法,作了一个根据传递参数数据形成的动态列表Navigator
组件,然后使用路由MaterialPageRoute
传递参数,具体代码如下。Navigator.push(
context,
MaterialPageRoute(
builder:(context)=>new ProductDetail(product:products[index])
)
);
ProductDetail
这个类(组件),先要作的就是接受参数,具体代码如下。class ProductDetail extends StatelessWidget {
final Product product;
ProductDetail({Key key ,@required this.product}):super(key:key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title:Text('${product.title}'),
),
body:Center(child: Text('${product.description}'),)
);
}
}
import 'package:flutter/material.dart';
//传递的数据结构,也可以理解为对商品数据的抽象
class Product{
final String title; //商品标题
final String description; //商品描述
Product(this.title,this.description);
}
void main(){
runApp(MaterialApp(
title:'数据传递案例',
home:ProductList(
products:List.generate(
20,
(i)=>Product('商品 $i','这是一个商品详情,编号为:$i')
),
)
));
}
class ProductList extends StatelessWidget{
final List
_navigateToXiaoJieJie(BuildContext context) async{ //async是启用异步方法
final result = await Navigator.push(//等待
context,
MaterialPageRoute(builder: (context)=> XiaoJieJie())
);
Scaffold.of(context).showSnackBar(SnackBar(content:Text('$result')));
}
}
SnackBar
是用户操作后,显示提示信息的一个控件,类似Tost
,会自动隐藏。SnackBar
是以Scaffold
的showSnackBar
方法来进行显示的。Scaffold.of(context).showSnackBar(SnackBar(content:Text('$result')));
Navigator.pop(context,'xxxx'); //xxx就是返回的参数
import 'package:flutter/material.dart';
void main(){
runApp(MaterialApp(
title:'页面跳转返回数据',
home:FirstPage()
));
}
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar:AppBar(title:Text("找小姐姐要电话")),
body:Center(
child: RouteButton(),
)
);
}
}
//跳转的Button
class RouteButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed:(){
_navigateToXiaoJieJie(context);
},
child: Text('去找小姐姐'),
);
}
_navigateToXiaoJieJie(BuildContext context) async{ //async是启用异步方法
final result = await Navigator.push(//等待
context,
MaterialPageRoute(builder: (context)=> XiaoJieJie())
);
Scaffold.of(context).showSnackBar(SnackBar(content:Text('$result')));
}
}
class XiaoJieJie extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar:AppBar(
title:Text('我是小姐姐')
),
body:Center(
child:Column(
children:
pubspec.yaml
文件,需要把资源文件在这里声明。images
文件夹,文件夹下面放了一个图片,图片的名称叫做blogtouxiang.jpg
,那我们在pubspec.yaml
文件里就要写如下代码进行声明。 assets:
- images/blogtouxiang.jpg
import 'package:flutter/material.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Image.asset('images/blogtouxiang.jpg'),
);
}
}
android:label="flutter_app" //配置APP的名称,支持中文
android:icon="@mipmap/ic_launcher" //APP图标的文件名称
flutter doctor -v
D:\Program\Android\'Android Studio'\jre\bin\keytool -genkey -v -keystore D:\key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key
android
文件夹下,创建一个名为key.properties的文件,并打开粘贴下面的代码storePassword=
storePassword=123123
keyPassword=123123
keyAlias=key
storeFile=D:/key.jks
android{
这一行前面,加入如下代码:def keystorePropertiesFile = rootProject.file("key.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
buildTypes {
release {
signingConfig signingConfigs.debug
}
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
生成apk
flutter build apk
20个flutter实例
BottomNavigationWidget
组件。import 'package:flutter/material.dart';
import 'bottom_navigation_widget.dart';
void main()=> runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title:'Flutter bottomNavigationBar',
theme:ThemeData.light(),
home:BottomNavigationWidget()
);
}.
}
BottomNaivgationWidget
组件前,我们需要简单了解一下什么是StatefulWidget
. StatefulWidget
具有可变状态(state)的窗口组件(widget)。使用这个要根据变化状态,调整State值。bottom_navigation_widget.dart
文件StatelessWidget
不同,我们在VSCode中直接使用快捷方式生成代码(直接在VSCode中输入stful):class name extends StatefulWidget {
_nameState createState() => _nameState();
}
class _nameState extends State
StatefulWidget
分为两个部分,第一个部分是继承与StatefullWidget
,第二个部分是继承于State
.其实State
部分才是我们的重点,主要的代码都会写在State
中。BottomNaivgationWidget
这个Widget了,只是建立一个底部导航。import 'package:flutter/material.dart';
class BottomNavigationWidget extends StatefulWidget {
_BottomNavigationWidgetState createState() => _BottomNavigationWidgetState();
}
class _BottomNavigationWidgetState extends State
AppBar
和一个Center
,然后用Text Widget表明即可home_screen.dart
文件import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar:AppBar(
title: Text('HOME'),
),
body:Center(
child: Text('HOME'),
)
);
}
}
initState()
方法,把刚才做好的页面进行初始化到一个Widget数组中。有了数组就可以根据数组的索引来切换不同的页面了。这是现在几乎所有的APP采用的方式。 List
..add()
是Dart语言的..语法,如果你学过编程模式,你一定听说过建造者模式,简单来说就是返回调用者本身。这里list后用了..add(),还会返回list,然后就一直使用..语法,能一直想list里增加widget元素。 最后我们调用了一些父类的initState()
方法。BottomNavigationBar
组件里提供了一个相应事件onTap
,这个事件自带一个索引值index
,通过索引值我们就可以和我们list里的索引值相对应了。onTap:(int index){
setState((){
_currentIndex= index;
});
},
bottom_navigation_widget.dart
的全部代码,讲解我会在视频中一行一行讲解。import 'package:flutter/material.dart';
import 'pages/home_screen.dart';
import 'pages/email_screen.dart';
import 'pages/pages_screen.dart';
import 'pages/airplay_screen.dart';
class BottomNavigationWidget extends StatefulWidget {
_BottomNavigationWidgetState createState() => _BottomNavigationWidgetState();
}
class _BottomNavigationWidgetState extends State
theme: ThemeData(
primarySwatch: Colors.lightBlue,
),
import 'package:flutter/material.dart';
import 'bottom_appBar_demo.dart';
void main()=>runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title:'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.lightBlue,
),
home:BottomAppBarDemo(),
);
}
}
bottom_appBar_demo.dart
这个文件还没有,也找不到,这个文件是我们的主要文件,我们的主要业务逻辑会写在这个文件里。floatingActionButton
工作中我们通常简称它为“FAB”,也许只是我们公司这样称呼,从字面理解可以看出,它是“可交互的浮动按钮”,其实在Flutter默认生成的代码中就有这家伙,只是我们没有正式的接触。
floatingActionButton
的主要代码:floatingActionButton: FloatingActionButton(
onPressed: (){
Navigator.of(context).push(MaterialPageRoute(builder:(BuildContext context){
return EachView('New Page');
}));
},
tooltip: 'Increment',
child: Icon(
Icons.add,
color: Colors.white,
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
BottomAppBar
是 底部工具栏的意思,这个要比BottomNavigationBar
widget灵活很多,可以放置文字和图标,当然也可以放置容器。BottomAppBar
的常用属性:
floatingActionButton
融合,所以使用的值都是CircularNotchedRectangle(),有缺口的圆形矩形。import 'package:flutter/material.dart';
class BottomAppBarDemo extends StatefulWidget {
_BottomAppBarDemoState createState() => _BottomAppBarDemoState();
}
class _BottomAppBarDemoState extends State
StatelessWidget
(不可变控件),所以很麻烦的写了4个页面,其实完全可以写一个继承于StatefulWidget
的控件,进行搞定。each_view.dart
文件,然后输入如下代码:import 'package:flutter/material.dart';
class EachView extends StatefulWidget {
String _title;
EachView(this._title);
@override
_EachViewState createState() => _EachViewState();
}
class _EachViewState extends State
_title
变量,这个变量是从主页面传递过来的,然后根据传递过来的具体值显示在APP的标题栏和屏幕中间。bottom_appBar_demo.dart
页面完成的。首先我们需要引入新作的子页面each_view.dart
。import 'each_view.dart';
List
_eachView
进行初始化赋值,我们可以直接重写初始化方法,具体代码如下: @override
void initState() {
// TODO: implement initState
super.initState();
_eachView = List();
_eachView..add(EachView('Home'))..add(EachView('Me'));
}
onPressed: (){
Navigator.of(context).push(MaterialPageRoute(builder:(BuildContext context){
return EachView('New Page');
}));
},
onPressed:(){
setState(() {
_index=0;
});
}
bottom_appBar_demo.dart
所有代码,代码如下:import 'package:flutter/material.dart';
import 'each_view.dart';
class BottomAppBarDemo extends StatefulWidget {
_BottomAppBarDemoState createState() => _BottomAppBarDemoState();
}
class _BottomAppBarDemoState extends State
PageRouterBuilder
这个类里的transitionsBuilder
方法。不过这个方法还是有很多写法的,通过写法的不同,产生的动画效果也有所不同。FirstPage
的组件是我们自定义的,需要我们再次编写。入口文件的代码如下:import 'package:flutter/material.dart';
import 'pages.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title:'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home:FirstPage()
);
}
}
import
引入了pages.dart
文件,这个文件就是生成了两个页面(Flutter里的页面也是Widget,这个你要跟网页区分开)。有了两个页面就可以实现路由跳转了。pages.dart
文件的代码如下,这里我们先用普通路由代替,看一看效果。import 'package:flutter/material.dart';
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue,
appBar:AppBar(
title:Text('FirstPage',style: TextStyle(fontSize: 36.0)),
elevation: 0.0,
),
body:Center(
child: MaterialButton(
child: Icon(
Icons.navigate_next,
color:Colors.white,
size:64.0,
),
onPressed: (){
Navigator.of(context).push(
MaterialPageRoute(
builder:(BuildContext context){
return SecondPage();
}));
},
),
)
);
}
}
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.pinkAccent,
appBar: AppBar(
title: Text('SecondPage',style:TextStyle(fontSize:36.0),),
backgroundColor: Colors.pinkAccent,
leading:Container(),
elevation: 0.0,
),
body:Center(
child: MaterialButton(
child: Icon(
Icons.navigate_before,
color:Colors.white,
size:64.0
),
onPressed: ()=>Navigator.of(context).pop(),
),
)
);
}
}
custome_router.dart
文件,这个就是要自定义的路由方法,自定义首先要继承于通用的路由的构造器类PageRouterBuilder
。继承之后重写父类的CustomRoute
构造方法。import 'package:flutter/material.dart';
class CustomRoute extends PageRouteBuilder{
final Widget widget;
CustomRoute(this.widget)
:super(
transitionDuration:const Duration(seconds:1),
pageBuilder:(
BuildContext context,
Animation
return ScaleTransition(
scale:Tween(begin:0.0,end:1.0).animate(CurvedAnimation(
parent:animation1,
curve: Curves.fastOutSlowIn
)),
child:child
);
return RotationTransition(
turns:Tween(begin:0.0,end:1.0)
.animate(CurvedAnimation(
parent: animation1,
curve: Curves.fastOutSlowIn
)),
child:ScaleTransition(
scale:Tween(begin: 0.0,end:1.0)
.animate(CurvedAnimation(
parent: animation1,
curve:Curves.fastOutSlowIn
)),
child: child,
)
);
SlideTransition
就可以了。// 幻灯片路由动画
return SlideTransition(
position: Tween
import 'package:flutter/material.dart';
import 'frosted_glass_emo.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title:'Flutter Demo',
theme:ThemeData(
primarySwatch: Colors.blue,
),
home:Scaffold(
body:FrostedGlassDemo(),
)
);
}
}
BackdropFilter
就是背景滤镜组件,使用它可以给父元素增加滤镜效果,它里边最重要的一个属性是filter
。 filter
属性中要添加一个滤镜组件,实例中我们添加了图片滤镜组件,并给了模糊效果。frosted_glass_demo.dart
的文件,然后写入下面的代码,具体的解释已经写到了代码的注释中。import 'package:flutter/material.dart';
import 'dart:ui'; //引入ui库,因为ImageFilter Widget在这个里边。
class FrostedGlassDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body:Stack( //重叠的Stack Widget,实现重贴
children:
class _KeepAliveDemoState extends State
TabBar
是切换组件,它需要设置两个属性。
TabController
组件。bottom:TabBar(
controller: _controller,
tabs:[
Tab(icon:Icon(Icons.directions_car)),
Tab(icon:Icon(Icons.directions_transit)),
Tab(icon:Icon(Icons.directions_bike)),
],
)
import 'package:flutter/material.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme:ThemeData(
primarySwatch: Colors.blue,
),
home:KeepAliveDemo()
);
}
}
class KeepAliveDemo extends StatefulWidget {
_KeepAliveDemoState createState() => _KeepAliveDemoState();
}
/*
with是dart的关键字,意思是混入的意思,
就是说可以将一个或者多个类的功能添加到自己的类无需继承这些类,
避免多重继承导致的问题。
SingleTickerProviderStateMixin 主要是我们初始化TabController时,
需要用到vsync ,垂直属性,然后传递this
*/
class _KeepAliveDemoState extends State
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
//混入AutomaticKeepAliveClientMixin,这是保持状态的关键
//然后重写wantKeppAlive 的值为true。
class _MyHomePageState extends State
import 'keep_alive_demo.dart';
StatelessWidget
,然后在home属性中加入SearchBarDemo
,这是一个自定义的Widget,主要代码都在这个文件中。main.dart
文件的代码如下:import 'package:flutter/material.dart';
import 'search_bar_demo.dart';
void main() =>runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title:'Flutter Demo',
theme: ThemeData.light(),
home: SearchBarDemo()
);
}
}
asset.dart
相当于数据文件,工作中这些数据是后台传递给我们,或者写成配置文件的,这里我们就以List的方式代替了。我们在这个文件中定义了两个List:
const searchList = [
"jiejie-大长腿",
"jiejie-水蛇腰",
"gege1-帅气欧巴",
"gege2-小鲜肉"
];
const recentSuggest = [
"推荐-1",
"推荐-2"
];
import 'package:flutter/material.dart';
import 'asset.dart';
class SearchBarDemo extends StatefulWidget {
_SearchBarDemoState createState() => _SearchBarDemoState();
}
class _SearchBarDemoState extends State
searchBarDelegate
类,这个类继承与SearchDelegate
类,继承后要重写里边的四个方法。buildActions
方法时搜索条右侧的按钮执行方法,我们在这里方法里放入一个clear图标。 当点击图片时,清空搜索的内容。 @override
List
AnimatedIcon
,然后在点击时关闭整个搜索页面,代码如下。 @override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow, progress: transitionAnimation),
onPressed: () => close(context, null));
}
buildResults
方法,是搜到到内容后的展现,因为我们的数据都是模拟的,所以我这里就使用最简单的Container
+Card
组件进行演示了,不做过多的花式修饰了。 @override
Widget buildResults(BuildContext context) {
return Container(
width: 100.0,
height: 100.0,
child: Card(
color: Colors.redAccent,
child: Center(
child: Text(query),
),
),
);
}
@override
Widget buildSuggestions(BuildContext context) {
final suggestionList = query.isEmpty
? recentSuggest
: searchList.where((input) => input.startsWith(query)).toList();
return ListView.builder(
itemCount: suggestionList.length,
itemBuilder: (context, index) => ListTile(
title: RichText(
text: TextSpan(
text: suggestionList[index].substring(0, query.length),
style: TextStyle(
color: Colors.black, fontWeight: FontWeight.bold),
children: [
TextSpan(
text: suggestionList[index].substring(query.length),
style: TextStyle(color: Colors.grey))
])),
));
}
}
search_bar_demo.dart
文件的代码:import 'package:flutter/material.dart';
import 'asset.dart';
class SearchBarDemo extends StatefulWidget {
_SearchBarDemoState createState() => _SearchBarDemoState();
}
class _SearchBarDemoState extends State
meidaQuery
可以很容易的得到屏幕的宽和高,得到宽和高的代码如下:final width = MediaQuery.of(context).size.width;
final height = MediaQuery.of(context).size.height;
GestureDetector
它式一个Widget,但没有任何的显示功能,而只是一个手势操作,用来触发事件的。虽然很多Button组件是有触发事件的,比如点击,但是也有一些组件是没有触发事件的,比如:Padding、Container、Center这时候我们想让它有触发事件就需要再它们的外层增加一个GestureDetector
,比如我们让Padding有触发事件,代码如下:Widget buildAddButton(){
return GestureDetector(
onTap:(){
if(list.length<9){
setState(() {
list.insert(list.length-1,buildPhoto());
});
}
},
child: Padding(
padding:const EdgeInsets.all(8.0),
child: Container(
width: 80.0,
height: 80.0,
color: Colors.black54,
child: Icon(Icons.add),
),
),
);
}
warp_demo.dart
文件,然后再home属性中使用了WarpDemo
,代码如下:import 'package:flutter/material.dart';
import 'warp_demo.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData.dark(),
home:WarpDemo()
);
}
}
import 'package:flutter/material.dart';
//继承与动态组件
class WarpDemo extends StatefulWidget {
_WarpDemoState createState() => _WarpDemoState();
}
class _WarpDemoState extends State
ExpansionTile
组件的使用。ExpansionTile Widget
就是一个可以展开闭合的组件,常用的属性有如下几个。
Text Widget
。import 'package:flutter/material.dart';
import 'expansion_tile.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title:'Flutter Demo',
theme: new ThemeData.dark(),
home:ExpansionTileDemo()
);
}
}
import 'package:flutter/material.dart';
class ExpansionTileDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:Text('expansion tile demo')),
body:Center(
child: ExpansionTile(
title:Text('Expansion Tile'),
leading:Icon(Icons.ac_unit),
backgroundColor: Colors.white12,
children:
ExpandStateBean
类,里边就是两个状态,一个是是否展开isOpen
,另一个索引值。代码如下:class ExpandStateBean{
var isOpen;
var index;
ExpandStateBean(this.index,this.isOpen);
}
import 'package:flutter/material.dart';
class ExpansionPanelListDemo extends StatefulWidget {
_ExpansionPanelListDemoState createState() => _ExpansionPanelListDemoState();
}
class _ExpansionPanelListDemoState extends State
Container
.但其内容如果本身就不是图片,只是容器,这种放入图片的做法会让包体变大。其实我们完全可以使用贝塞尔曲线进行切割。 debugShowCheckedModeBanner: false,
import 'package:flutter/material.dart';
import 'custom_clipper.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title:'Flutter Demo',
debugShowCheckedModeBanner: false,
theme:ThemeData(
primarySwatch: Colors.blue,
),
home:HomePage()
);
}
}
clipPath
控件可以把其内部的子控件切割,它有两个主要属性(参数):
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body:Column(
children:
Scaffold
里放置了一个列容器,然后把ClipPath
控件放到了里边,ClipPath
的子元素是一个容器控件Container
。BootomClipper
是我们自定义的一个对象,里边主要就是切割的路径。getClip
方法里,它返回一段路径。import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body:Column(
children:
class BottomClipper extends CustomClipper
AnimationController
是Animation
的一个子类,它可以控制Animation
, 也就是说它是来控制动画的,比如说控制动画的执行时间。
vsync:this
:垂直同步设置,使用this就可以了。duration
: 动画持续时间,这个可以使用seconds
秒,也可以使用milliseconds
毫秒,工作中经常使用毫秒,因为秒还是太粗糙了。