[TOC]
实例 : 计数器
import 'package:flutter/material.dart';
//此行代码作用是导入了Material UI组件库。Material是一种标准的移动端和web端的视觉设计语言, Flutter默认提供了一套丰富的Material风格的UI组件。
//定义一个Main方法。Main方法中用简单RunApp就可以执行我们定义的Widget
void main() => runApp(new MyApp());
/*
Flutter写成的都是UI主键。主要分 StatelessWidget 和 StatefulWidget
整体的入口可以写成 StatelessWidget
*/
class MyApp extends StatelessWidget {
//这个Widget就是我们App的最基层的Widget了。
//传入BuildContext给我们使用。
@override
Widget build(BuildContext context) {
//这样就可以直接使用Flutter为我们封装好的MaterialApp这个主题的了.从源码可以看到这个是个StatefulWidget
//[home], [routes], [onGenerateRoute], or [builder] 这个主题下的这些方法不能都为空!!
return new MaterialApp(
debugShowCheckedModeBanner:false , //去掉debug图标
title: 'Flutter Demo',
//定义主题
theme: new ThemeData(
//主题颜色
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: '首页面'),
);
}
}
//---------------------------------------------------------------------------------
//statefulWidget ,因为Widget都是无状态的,所以如果需要有状态的话,`state`这个类来进行维持
class MyHomePage extends StatefulWidget {
// 无论你接收不接收数据,MyHomePage({Key key}) : super(key : key);固定死的
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
//---------------------------------------------------------------------------------
//flutter中状态的持有类
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
//这点和小程序很类似。调用setState进行状态同步和刷新。如果不调用这个方法,只是改变值,界面不会发生变化
setState(() {
_counter++;
});
}
//state中的`build`方法,会自动在`setState`方法后调用。
@override
Widget build(BuildContext context) {
//Scaffold可以理解成相当于一个html 它的body就是主要的内容。
//同时它其实是满足MD的。所以它还能提供对应的组件
//比如 AppBar .Drawer floatingButton等等
return new Scaffold(
appBar: new AppBar(
//这里,我们从由App.build方法创建的my主页对象中获取值,并使用它设置appbar标题
title: new Text(widget.title),
centerTitle: true, //标题居中
),
//这里可以初步的看到,如果想要布局居中显示,可以先包裹一层`Center`
body: new Center(
//因为是竖直的布局,所以再次包裹一层Column
child: new Column(
//使用这个属性,让元素在垂直线上居中竖直排列
mainAxisAlignment: MainAxisAlignment.center,
//children中传入其他组件
children: <Widget>[
new Text(
'点了一次又一次:',
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
//右下角的一个小加号圆形按钮
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
一、前置知识
1、StatelessWidget类
不需要可变状态的小部件。
无状态小部件是一个小部件,它通过构建一系列其他小部件来更加具体地描述用户界面,从而描述用户界面的一部分。构建过程以递归方式继续进行,直到用户界面的描述完全具体(例如,完全由 RenderObjectWidget 组成,它描述具体的 RenderObject。
当您描述的用户界面部分不依赖于对象本身中的配置信息和其中构件被夸大的 BuildContext 时,无状态小部件很有用。对于可以动态改变的组合,例如由于具有内部时钟驱动状态,或取决于某些系统状态,请考虑使用 StatefulWidget 。
无状态小部件的构建方法通常只在以下三种情况下调用:第一次将小部件插入树中,第一次在小部件的父级更改其配置时以及第二次使用 InheritedWidget 时,它依赖于更改。
2、StatefulWidget类
具有可变状态( state)的Widget(窗口小部件).
状态( state) 是可以在构建Widget时同步读取时 和 在Widget的生命周期期间可能改变的信息
Widget实现者的责任就是 在状态改变时通过 State.setState. 立即通知状态
当您描述的用户界面部分不依赖于对象本身中的配置信息和其中构件被夸大的 BuildContext 时,无状态小部件很有用。对于可以动态改变的组合,例如由于具有内部时钟驱动状态,或取决于某些系统状态,请考虑使用StatefulWidget 。
StatefulWidget 实例本身是不可变的,并将其可变状态存储在由 createState 方法创建的独立状态对象中 ,或者存储在该状态订阅的对象中,例如 Stream 或 ChangeNotifier 对象,其引用存储在 StatefulWidget 的最终字段中本身。
该框架只要调用一个 StatefulWidget 就 调用 createState,这意味着如果该小部件已经插入到多个位置的树中,那么多个 State 对象可能与同一个 StatefulWidget 关联。同样,如果 StatefulWidget 从树中移除,后来在树再次插入时,框架将调用 createState 再创建一个新的目标,简化的生命周期状态的对象。
Stateful widget至少由两个类组成:
- 一个
StatefulWidget
类。 - 一个
State
类;StatefulWidget
类本身是不变的,但是State
类中持有的状态在widget生命周期中可能会发生变化。如果需要主动改变State的状态,需要通过setState()方法进行触发,单纯改变数据是不会引发UI改变的。
_MyHomePageState
类是MyHomePage
类对应的状态类。看到这里,读者可能已经发现:和MyApp
类不同, MyHomePage
类中并没有build
方法,取而代之的是,build
方法被挪到了_MyHomePageState
方法中。
3、widget
- 在Flutter中,大多数东西都是widget(后同“组件”或“部件”),包括对齐(alignment)、填充(padding)和布局(layout)等,它们都是以widget的形式提供,使用时,需要new ,但也可以省略
- Flutter在构建页面时,会调用组件的
build
方法,widget的主要工作是提供一个build()方法来描述如何构建UI界面(通常是通过组合、拼装其它基础widget)。
4、简单快捷键
stl
: StatelessWidget
class extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
);
}
}
stf
:StatefulWidget
class extends StatefulWidget {
@override
_State createState() => _State();
}
class _State extends State<> {
@override
Widget build(BuildContext context) {
return Container(
);
}
}
5、小知识点
**1、InkWell **
可以让一个不能点击的组件变成可以点击的组件
例:
children: <Widget>[
InkWell(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("2019年09月09日"),
Icon(Icons.arrow_drop_down)
],
),
onTap: _showDataPicker,
)
],
2、Divider 水平分割线
//写在组件与组件之间
Divider(height:10,indent:0,color:Colors.red),
//height:分割线Widget的高,不是分割线本身效果的高,可以达到两个Widget 之间margin的效果
//indent:分割线左边缩进长度
//color:分割线的颜色
**3、CircularProgressIndicator **
Material Design风格的循环进度条,旋转以指示应用程序正忙。比如Http异步请求的时候代表正在获取的状态显示。
/*
* 圆形进度条
* 可以在外面包一层SizedBox,间接改变进度条的大小
*const CircularProgressIndicator({
Key key,
double value,//0~1的浮点数,用来表示进度多少;如果 value 为 null 或空,则显示一个动画,否则显示一个定值
Color backgroundColor,//背景颜色
Animation<Color> valueColor,//animation类型的参数,用来设定进度值的颜色,默认为主题色
this.strokeWidth = 4.0,//进度条宽度
String semanticsLabel,
String semanticsValue,
})
*/
二、项目结构and一些语法
创建项目:flutter create myapp
-
android android平台相关的代码 ios ios平台相关的代码 lib flutter相关的代码,我们写的代码主要放在lib下面,类似于vue项目中的src文件夹 test 测试 vue项目里面也有test文件夹
pubspec.yaml 配置文件 一般放一些第三方依赖 类似于vue项目中的paceage.json文件 -
在lib下面有一个main.dart,是入口文件,类似于vue中的main.js文件。
-
runApp后面要跟上一个组件。vue中也有一个render(h) h(App)
-
在flutter中,一切都是组件,在vue中,一切也是组件,vue中的组件中.vue文件。在flutter中一个组件就是一个类,需要一个组件就是需要new这个类,通常可以把new省略不写。
-
在flutter中,结构和样式写在一起,当然它不像vue中分结构和样式,它是把样式也定义成了组件。
-
如果一个组件中包含了另一个组件,那么这个组件内部需要写一个child
1、future
简单来说,future就是一个Future对象,当执行return await......的时候,实际上返回的是一个延迟计算的Future对象,这个Future对象是Dart内置的,有自己的队列策略,它将要操作的事件放入EventQueue中,在队列中的事件按照 先进先出 的原则去逐一处理事件,当事件处理完成后,将结果返回给Future对象。
在这个过程中涉及到了异步和等待:
- 异步:就是不用阻塞当前线程,来等待该线程任务处理完成再去执行其他任务。
- 等待:await,声明运算为延迟执行
简单示例:
void testFuture(){
Future future = new Future(() => null);
future.then((_){
print("then");
}).then((){
print("whenComplete");
}).catchError((_){
print("catchError");
});
} //执行结果为 then whenComplete
future里面有几个函数:
then
:异步操作逻辑在这里写。
whenComplete
:异步完成时的回调。
catchError
:捕获异常或者异步出错时的回调。
在我们平时开发中我们是这样用的,首先给我们的函数后面加上
async
关键字,表示异步操作,然后函数返回值写成Future
,然后我们可以new一个Future
,逻辑前面加上一个await
关键字,然后可以使用future.then
等操作。下面是一个示例操作,为了方便演示,这里的返回值的null。平时开发中如果请求网络返回的是json,我们可以把泛型写成String;泛型也可能是实体类(entity/domain),不过要做json转实体类相关操作。
创建多个Future,执行顺序和和创建Future的先后顺序有关,如果只是单独的调用then,没有嵌套使用的话,和调用then的先后顺序无关。如果有多个then嵌套执行,先执行外面的then,然后执行里面的then。
三、组件
一些常用的组件:
Text
- 用于简单地在屏幕上显示文本的小部件。Image
- 用于显示图像。Icon
- 用于显示Flutter的内置Material和Cupertino图标。Container
- 在Flutter中,相当于div
。允许在其中进行添加填充,对齐,背景,力大小以及其他东西的加载。空的时候也会占用0px的空间,这很方便。- TextInput - 处理用户反馈。
Row
,Column
- 这些小部件显示水平或垂直方向的子项列表。Stack
- 堆栈显示一个孩子的列表。这个功能很像CSS中的'position'属性。Scaffold
- 为应用提供基本的布局结构。它可以轻松实现底部导航,appBars,后退按钮等。
## 1、Scaffold(脚手架)
Scaffold
是 Material组件库中提供的一个组件,它提供了默认的导航栏、标题和包含主屏幕widget树(后同“组件树”或“部件树”)的body
属性。组件树可以很复杂。
-
AppBar,通常显示在使用appBar属性的应用顶部。
-
BottomNavigationBar,通常使用bottomNavigationBar 属性在应用程序底部显示,此功能向图中所示一样,无法做定制,只能以图片和文本形式存在。
-
BottomAppBar,通常使用bottomNavigationBar属性显示在应用程序的底部,用来构建定制化的底部导航栏或者布局。
-
FloatingActionButton,圆形按钮,通常使用floatingActionButton属性显示在应用程序的右下角。
-
SnackBar,通常显示在应用程序底部附近的临时通知。
## 2、MaterialApp
MaterialApp 是Material库中提供的Flutter APP框架,通过它可以设置应用的名称、主题、语言、首页及路由列表等。MaterialApp也是一个widget。一般写在最顶层
3、Center
Center
可以将其子组件树对齐到屏幕中心
此例中, Center
子组件是一个Column
组件,Column
的作用是将其所有子组件沿屏幕垂直方向依次排列;
## 4、floatingActionButton
floatingActionButton
是页面右下角的悬浮按钮,它的onPressed
属性接受一个回调函数,代表它被点击后的处理器。
5、Text
显示单一静态(不可编辑)的文本,类似Android中的TextView,IOS中的label。Flutter中Text提供了两个构造函数,首先介绍下上面代码中的new Center。
- 文本对齐方式:TextAlign
- center:居中
- left:左对齐
- right:右对齐
- 设置最多显示行数:maxLines
- 文本溢出处理方式:overflow
- clip:直接切断
- ellipsis:省略号
- fade:渐变消失的效果,上下渐变
new Center:将子组件放置在自己的中心位置。
body:Center(
child:Text(
"123djasidhasuidsahyfgyugfdifgisadsadasd",
textAlign:TextAlign.center,//对齐方式,还有 left、right
maxLines:1, //最多行数设置
overflow:TextOverflow.ellipsis,//多于的内容如何处理,ellipsis是用省略号代替,fade是表示如果内容多余会从上到下颜色渐渐变淡(灰)
style:TextStyle(
fontSize:25,//或许写为25.0
fontWeight: FontWeight.bold, //字体粗细,也可以用w600等
color:Color.fromRGBO(r,g,b,o), //o代表透明度
decoration:TextDecoration.underline, //下划线
decorationStyle:TextDecorationStyle.solid, //下划线的样式设置
letterSpacing:5, //字体之间的间距
),
),
),
6、Icon
new Icon(Icons.add) 是一个小加号图标
在代码中我们会使用到很多的系统图标,这些图标的类型都是IconData,通过Icons即可获取。
7、Image
Flutter中获取图片的方式提供了以下几种:
加入图片的方式
Image.asset: 加载项目资源目录中的图片,加入图片后会增大打包的体积,使用相对路径。
Image.network: 加载网络资源图片。
Image.file: 加载本地文件中的图片,使用绝对路径。
Image.memory: 加载Uint8List资源图片。
fit属性
fill 全图显示,充满整个容器
contain 显示图片原比例
cover 保持图片不变形的前提下,充满整个容器
fitWidth 以宽度为基准充满容器
fitHeight 以高度为基准充满容器
scaleDown 效果和contain差不多,但是此属性不允许显示超过源图片大小,可小不可大
图片与color的混合模式(colorBlendMode)
color:要混合的颜色,单设置color的值无效
colorBlendMode:混合模式,设置混合的样式
图片的重复 (ImageRepeat)
repeat 铺满整个画布
repeatX 横向重复
repeatY 纵向重复
目前 Flutter的Image支持以下图像格式:JPEG,PNG,GIF,动画GIF,WebP,动画WebP,BMP和WBMP。
color: Color.fromRGBO(2, 0, 200, 1), //设置图片颜色,为了设置蒙版
colorBlendMode: BlendMode.xxx, //设置蒙版,与上面设置的颜色相关
height:50,
width:100, //设置图片的高度,宽度
repeat: ImageRepeat.repeatX , //设置当图片小于空间时图片的重叠方式
fit: BoxFit.fill, //设置图片的填充方式
使用本地图片步骤
1、在project文件夹中新建一个images文件夹
2、在将要使用的图片放入images文件夹
3、在pubspec.yaml
文件中找到assets设置语句,添加如下内容
assets:
- images/mao.jpg //mao.jpg是添加图片名
4、在页面中使用
child: Image.asset('images/mao.jpg'),
8、Row 和 Column
Row:横向布局,Column:纵向布局。这里和Android中的LinearLayout(线性布局)的 horizontal(横向)与 vertical(纵向)是一样的效果。
水平布局Row
- 非灵活排列
根据Row子元素的大小进行布局,若子元素不足,它会留有空隙;若子元素超出,它会警告。
例如:
RaisedButton
- 灵活排列 使用Expanded解决有空隙的问题,在按钮外加入Expanded就可以了。
垂直布局column
Column属性与Row基本相同
- 主轴和副轴的辨识
- 主轴(main轴):column组件主轴是垂直 row组件主轴是水平
- 副轴(cross轴):column组件副轴是水平 row组件副轴是垂直
-
mainAxisAlignment
(主轴对齐方式) -
crossAxisAlignment
(副轴对齐方式) -
水平方向相对于屏幕居中 在child外层嵌套Center组件
处理富余空间
MainAxisAlignment
和CrossAxisAlignment
简介
MainAxisAlignment
(主轴)和CrossAxisAlignment
(交叉轴)常用于Row和Column控件中,主要是用来控制子控件排列的位置,并可以配合textDirection
和verticalDirection
属性来控制子控件排列的方向及改变MainAxisAlignment
和CrossAxisAlignment
的起始位置。
MainAxisAlignment
(主轴)就是与当前控件方向一致的轴,而CrossAxisAlignment
(交叉轴)就是与当前控件方向垂直的轴
mainAxisAlignment: MainAxisAlignment.xxx,
MainAxisAlignment {
start, //将子控件放在主轴的开始位置
end, //将子控件放在主轴的结束位置
center, //将子控件放在主轴的中间位置
spaceBetween, //将主轴空白位置进行均分,排列子元素,手尾没有空隙
spaceAround, //将主轴空白区域均分,使中间各个子控件间距相等,首尾子控件间距为中间子控件间距的一半
spaceEvenly, //将主轴空白区域均分,使各个子控件间距相等
}
CrossAxisAlignment {
start, //将子控件放在交叉轴的起始位置
end, //将子控件放在交叉轴的结束位置
center, //将子控件放在交叉轴的中间位置
stretch, //使子控件填满交叉轴
baseline, //将子控件放在交叉轴的上,并且与基线相匹配(不常用)
}
示例:
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Icon(Icons.search,color:Colors.red),
new Icon(Icons.home,color:Colors.blue),
new Icon(Icons.select_all,color:Colors.lightBlue),
new Icon(Icons.send,color:Colors.red),
],
),
9、container
Container:可以添加padding、margin、border、background color、通常用于装饰其他Widget
- child的对齐方式: Alignment
- 主要通过 left 、right、top、bottom、center、等进行组合,拼接位置,
- 例如:bottomCenter、botomLeft、center、centerRight、topRight、topCenter、
- height:高度 width:宽度 color:颜色
- margin: 内边距
EdgeInsets.all()
EdgeInsets.fromLTRB()
- padding: 外边距
- decoration: ( container 的修饰器,主要的功能是设置背景和边框)
decoration: BoxDecoration()
border: container的边框border:Border.all(width:2.0,color:Colors.red)
body:Center(
child:Container(
child:new Text("hello world",style:TextStyle(fontSize:40.0)),
alignment:Alignment.center, //例如bottomLeft,topRight,centerLeft等等
width:500,
height:400,
//color:Color.lightBlue, //背景颜色设置
///padding:const EdgeInsets.all(10),//all表示四边都是10
padding:const EdgeInsets.fromLTRB(10,20,30,40),//这是分别设置四边
margin:const EdgeInsets.all(10),
//margin:EdgeInsets.only(top:10)
decoration:new BoxDecoration( //设置渐变色,但与color冲突,不能同时设置
gradient:const LineraGradient(
colors:[Colors.lightBlue,Colors.greenAccent,Colors.purple]
),
border:Border.all(width:2.0,color:Colors.red), //边框设置
borderRadius:BorderRadius.all(Radius.circular(100)), //圆角设置
)
)
)
10、ClipOval
实现圆形组件
ClipOval例子(默认全圆角):
new ClipOval(
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
11、ListView
ListView
内部的children使用了widget数组,因为是一个列表,所以它接受一个数组。
listTite
列表项
- leading 列表前的icon图标
- title 列表标题
- 设置
ListView
的滚动方向(scrollDirection
)
Axis.horizontal
:横向滚动或者叫水平方向滚动Axis.vertical
:纵向滚动或者叫垂直方向滚动
1、ListView.builder
循环显示
scrollDirection: Axis.vertical, //设置滑动方向 Axis.horizontal 水平 默认 Axis.vertical 垂直
padding: EdgeInsets.all(10.0), //内间距
reverse: false, //是否倒序显示 默认正序 false 倒序true
primary: true, //false,如果内容不足,则用户无法滚动 而如果[primary]为true,它们总是可以尝试滚动。
itemExtent: 50.0, //确定每一个item的高度 会让item加载更加高效
shrinkWrap: true, //内容适配
itemCount: list.length, //item 数量,必选
physics: new ClampingScrollPhysics(), //滑动类型设置
cacheExtent: 30.0, //cacheExtent 设置预加载的区域
controller , //滑动监听
//要显示的数据
itemBuilder: (BuildContext context,int i){
return Text('${_list[i]["data"]["description"]}');
},
2、ListTile
- 利用了
ListTile
实现内部列表,任何容器组件其实都可以使用ListTile
- Divider( ) 用来生成一条分割线
this.leading, // item 前置图标
this.title, // item 标题
this.subtitle, // item 副标题
this.trailing, // item 后置图标
this.isThreeLine = false, // item 是否三行显示
this.dense, // item 直观感受是整体大小
this.contentPadding, // item 内容内边距
this.enabled = true,
this.onTap, // item onTap 点击事件
this.onLongPress, // item onLongPress 长按事件
this.selected = false, // item 是否选中状态
12、GridView
GridView
默认构造函数可以类比于ListView
默认构造函数,适用于有限个数子元素的场景,因为GridView
组件会一次性全部渲染children
中的子元素组件;GridView.builder
构造函数可以类比于ListView.builder
构造函数,适用于长列表的场景,因为GridView
组件会根据子元素是否出现在屏幕内而动态创建销毁,减少内存消耗,更高效渲染;GridView.count
构造函数是GrdiView
使用SliverGridDelegateWithFixedCrossAxisCount
的简写(语法糖),效果完全一致;GridView.extent
构造函数式GridView
使用SliverGridDelegateWithMaxCrossAxisExtent
的简写(语法糖),效果完全一致。
return GridView.count(
crossAxisCount: 2, // 一行多少个
crossAxisSpacing: 10, // 列与列的间隔
mainAxisSpacing: 10, // 行与行的间隔
padding: EdgeInsets.all(10),
childAspectRatio: 1, // 设置宽高比
children: this._getData(),
);
13、bottomNavigationBar 底部导航栏
一个完整的底部导航页面:点击底部导航,简单的页面跳转
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import '../views/Home.dart';
import '../views/search.dart';
import '../views/Setting.dart';
class Tabss extends StatefulWidget {
@override
_TabssState createState() => _TabssState();
}
class _TabssState extends State<Tabss> {
int _currentIndex = 0;
List _pageList = <Widget>[ //底部跳转的三个页面
Home(),
Search(),
Setting()
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: this._pageList[this._currentIndex],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed, //默认只能写三个,加上这句话可以配置多个
fixedColor: Colors.red, //选中时的颜色设置
iconSize: 20, //icon图标大小
currentIndex: this._currentIndex, //设置显示导航的索引
onTap: (int index){ //onTap点击事件
setState(() {
this._currentIndex = index;
});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home), //这里icon和title缺一不可。。。
title: Text("首页")
),
BottomNavigationBarItem(
icon: Icon(Icons.category),
title: Text("分类")
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
title: Text("搜索")
)
],
),
);
}
}
14、按钮
一般常用的 Button 是 MaterialButton、IconButton、FloatingActionButton。
注意:写按钮时都要加上onpress事件,否则可能会出现一些问题
1、MaterialButton
MaterialButton 是一个 Materia 风格的按钮。
new MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: new Text('点我'),
onPressed: () {
// ...
},
)
2、RaisedButton
RaisedButton 与 MaterialButton 类似。
new RaisedButton(
child: new Text('点我'),
onPressed: () {},
)
3、FlatButton
FlatButton 与 MaterialButton 类似,不同的是它是透明背景的。如果一个 Container 想要点击事件时,可以使用 FlatButton 包裹,而不是 MaterialButton。因为 MaterialButton 默认带背景,而 FlatButton 默认不带背景。
4、IconButton
IconButton 顾名思义就是 Icon + Button 的复合体,当某个 Icon 需要点击事件时,使用 IconButton 最好不过。
new IconButton(
icon: new Icon(Icons.volume_up),
tooltip: 'Increase volume by 10%',
onPressed: () {
// ...
},
)
其外,还有已经定义好的 Icon Button:CloseButton、BackButton。他们都有导航返回的能力。
5、FloatingActionButton
FloatingActionButton 是一个浮动在页面右下角的浮动按钮。
new Scaffold(
// ...
floatingActionButton: new FloatingActionButton(
onPressed: () {},
child: new Icon(Icons.add_a_photo),
elevation: 10, //阴影设置
highlightElevation: 2.0,
backgroundColor: Colors.red, // 红色
),
)
在 Scaffold 里使用的时候,它是一个浮动状态的按钮,在其他地方使用,就不会浮动了。
6、ButtonBar
ButtonBar 是一个布局组件,可以让 Button 排列在一行。
new ButtonBar(
children: <Widget>[
new CloseButton(),
new BackButton(),
],
)
7、样式
/*-------------------------------------按钮内部属性----------------------------------*/
child: Text("阴影按钮"), //按钮文字
color: Colors.blue, //按钮的背景颜色
textColor: Colors.white, //按钮文字颜色
elevation: 20, //阴影距离
//按钮的点击事件
onPressed: () {
print("颜色按钮");
},
//设置按钮的圆角
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)
),
//将按钮变为圆形,并设置一个red颜色的边
shape: CircleBorder(
side: BorderSide(color: Colors.red)
),
/*-----------------------------其他位置的按钮设置----------------------------------*/
//带有图标的按钮
RaisedButton.icon(
icon: Icon(Icons.search),
label: Text("图标按钮"),
onPressed: () {
print("图标按钮");
},
),
SizedBox(height: 5), //这个可用来设置按钮之间的距离 (写在按钮与按钮之间)
//设置按钮的宽高需要在按钮外设置
Container(
width: 200,
height: 50,
child: RaisedButton(
child: Text("自定义按钮"),
onPressed: () {},
),
),
/*-----------------------------------------------------------------------------------*/
//靠左的按钮
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: (){},
),
//靠右的按钮
actions: <Widget>[
IconButton(
icon: Icon(Icons.settings),
onPressed: (){},
)
示例:
有点多余的代码,不要在意
class main11 extends StatefulWidget {
@override
_main11State createState() => _main11State();
}
class _main11State extends State<main11> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton( //蓝底白字的按钮
child: Text("变一下"),
color: Colors.blue,
textColor: Colors.white,
textTheme: ButtonTextTheme.primary,
onPressed: (){
print("11111");
},
),
],
),
),
);
}
}
15、表单 TextField
child: new TextField(
autocorrect: false, // 是否自动校正
autofocus: false, //自动获取焦点
enabled: true, // 是否启用
inputFormatters: [], //对输入的文字进行限制和校验
keyboardType: TextInputType.text, //获取焦点时,启用的键盘类型
maxLines: 2, // 输入框最大的显示行数
maxLength: 3, //允许输入的字符长度
maxLengthEnforced: false, //是否允许输入的字符长度超过限定的字符长度
obscureText: true, // 是否隐藏输入的内容
//onChanged事件,在输入内容发生变化的时候触发
onChanged: (newValue) {
// print(newValue); // 当输入内容变更时,如何处理
},
//onSubmitted事件,则是在输入结束,点击完成的时候触发。
onSubmitted: (value) {
// print("whar"); // 当用户确定已经完成编辑时触发
},
style: new TextStyle(
color: new Color(Colors.amberAccent.green) // 设置字体样式
),
textAlign: TextAlign.center, //输入的内容在水平方向如何显示
decoration: new InputDecoration(
labelText: "城市", //在输入框上的左上角的文字,默认显示在输入框中,点击后移到左上角
icon: new Icon(Icons.location_city), //在输入框左侧的图标
border: new OutlineInputBorder(), // 边框样式
helperText: 'required', //必须输入
hintText: '请选择你要投保的城市', //默认文字,类似于placeholder
prefixIcon: new Icon(Icons.android), //输入框内左侧的图标
prefixText: 'Hello' //输入框左侧内部的文字,默认显示,但不会消失
),
//取消输入框的下划线
TextField(
decoration: InputDecoration(
border: InputBorder.none,
),
)
在TextFormField
中没有这onChange,onSubmitted两个事件,取而代之的是validator
,onSaved
,onFieldSubmitted
他们都接受三个函数,并且将其值作为参数传递到函数里面
- validator,如果开启
autovalidate: true
,那么将会自动检验输入的值,如果没有则会在表单提交的时候检验 该函数只允许返回验证失败的错误信息以及验证通过时返回null。 - onSaved, 当调用
FormState.save
方法的时候调用。 - onFieldSubmitted, 与
onSubmitted
一样,则是在输入结束,点击完成的时候触发。
获取输入框的值:
- 给每个输入框声明一个变量
String _text1 = "";
String _text2 = "";
- 给每个输入框加上一个onChange事件,
onChanged: (value){
setState(() {
this._text2=value;
});
- 给登录按钮一个onPress点击事件
onPressed: (){
print(this._text1);
print(this._text2);
},
输入框的初始值
//首先声明变量
var _text2 = new TextEditingController(); //初始化的时候给表单赋值
@override //这里的@override时多加的一个,原来的下面还要写
void initState() {
super.initState();
_text2.text = '初始值';
}
//在输入框设置onChanged
TextField(
onChanged: (value) {
setState(() {
this._text2.text = value;
});
},
),
16、GestureDetector 手势控制
GestureDetector({
Key key,
this.child,
this.onTapDown,//可能导致点击的指针已联系到屏幕的特定位置
this.onTapUp,//触发点的指针已停止在特定位置与屏幕联系
this.onTap,//发生了点击。
this.onTapCancel,//触发onTapDown的指针取消触发
this.onDoubleTap,//双击
this.onLongPress,//长按
this.onLongPressUp,//长按结束
this.onVerticalDragDown,//
this.onVerticalDragStart,//指针已经接触到屏幕,而且可能开始垂直移动。
this.onVerticalDragUpdate,//与屏幕接触并垂直移动的指针沿垂直方向移动
this.onVerticalDragEnd,//以前与屏幕接触并垂直移动的指针不再与屏幕接触,并且当其停止接触屏幕时以特定速度移动。
this.onVerticalDragCancel,//
this.onHorizontalDragDown,//
this.onHorizontalDragStart,//
this.onHorizontalDragUpdate,//
this.onHorizontalDragEnd,//
this.onHorizontalDragCancel,//
// onPan可以取代onVerticalDrag或者onHorizontalDrag,三者不能并存
this.onPanDown,//指针已经接触屏幕并开始移动
this.onPanStart,//与屏幕接触并移动的指针再次移动
this.onPanUpdate,//先前与屏幕接触并移动的指针不再与屏幕接触,并且当它停止接触屏幕时以特定速度移动
this.onPanEnd,//先前触发 onPanDown 的指针未完成
this.onPanCancel,//
// onScale可以取代onVerticalDrag或者onHorizontalDrag,三者不能并存,不能与onPan并存
this.onScaleStart,//
this.onScaleUpdate,//
this.onScaleEnd,//
this.behavior,
this.excludeFromSemantics = false
})
17、TabBar导航栏
//一些基本属性
const TabBar({
Key key,
@required this.tabs,//子标签
this.controller,//控制器
this.isScrollable = false,//能否滑动,false:tab宽度则等比,true:tab宽度则包裹item
this.indicatorColor,//指示器颜色
this.indicatorWeight = 2.0,
this.indicatorPadding = EdgeInsets.zero,
this.indicator,
this.indicatorSize,//TabBarIndicatorSize.label:indicator与文字同宽,TabBarIndicatorSize.tab:与tab同宽
this.labelColor,//选中标签颜色
this.labelStyle,//选中标签样式
this.labelPadding,
this.unselectedLabelColor,//未选中标签颜色
this.unselectedLabelStyle,
this.dragStartBehavior = DragStartBehavior.down,
this.onTap,//点击事件
})
配置步骤:
1、在appBar中配置基本的Tab选项
appBar: AppBar(
title: TabBar(
controller: _controller, //这里设置控制器
//appbar的属性设置都在这里
indicatorColor: Colors.red,
isScrollable: true,
tabs: <Widget>[
Tab(icon: Icon(Icons.directions_bike)),
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_subway)),
Tab(icon: Icon(Icons.directions_bike)),
Tab(icon: Icon(Icons.sort)),
Tab(icon: Icon(Icons.star)),
Tab(icon: Icon(Icons.shuffle)),
Tab(icon: Icon(Icons.category)),
],
)
),
2、一些配置
//后面加上的这个 with SingleTickerProviderStateMixin 是用来实现切换动画的
class _MyTopBarState extends State<MyTopBar> with SingleTickerProviderStateMixin{
// TabBar组件需要有一个控制器
TabController _controller;
// 定义一个钩子,其中 @override 是钩子函数的
@override
void initState() {
// 给导航控制器进行初始化
// 需要在这个勾子函数中对TabBar进行初始化
// _controller能够将页面和导航绑定到一起,最终实现切换的效果
// length 导航/页面的个数 vsync动画效果异步匹配
_controller = TabController(length: 3,vsync: this);
super.initState();
}
完整页面代码
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class MyTopBar extends StatefulWidget {
@override
_MyTopBarState createState() => _MyTopBarState();
}
// extends表示继承
// SingleTickerProviderStateMixin 对我们的组件进行配置,主要是配置动画切换效果
class _MyTopBarState extends State<MyTopBar> with SingleTickerProviderStateMixin{
// TabBar组件需要有一个控制器
TabController _controller;
var tabs = <Tab>[]; //这个是用来自动适应tabbar的数量的
@override
void initState() {
// 给导航控制器进行初始化
// 需要在这个钩子函数中对TabBar进行初始化
// _controller能够将页面和导航绑定到一起,最终实现切换的效果
// length 导航/页面的个数 vsync动画效果异步匹配
_controller = TabController(length: tabs.length,vsync: this);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TabBar(
controller: _controller,
tabs: <Widget>[
Tab(icon: Icon(Icons.directions_bike)),
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_subway)),
Tab(icon: Icon(Icons.directions_bike)),
Tab(icon: Icon(Icons.sort)),
Tab(icon: Icon(Icons.star)),
Tab(icon: Icon(Icons.shuffle)),
Tab(icon: Icon(Icons.category)),
],
)
),
body: TabBarView(
controller: _controller,
children: <Widget>[
SelfHomePage(page:1),
SelfHomePage(page:2),
SelfHomePage(page:3),
SelfHomePage(page:4),
SelfHomePage(page:5),
SelfHomePage(page:6),
SelfHomePage(page:7),
SelfHomePage(page:8),
],
),
);
}
}
class SelfHomePage extends StatelessWidget {
int page;
// 类的一个重写,重写的目的就是类可以传递参数
SelfHomePage({Key key, @required this.page}):super(key:key);
@override
Widget build(BuildContext context) {
return Center(
child: Text("page $page",style: TextStyle(fontSize: 40),),
);
}
}
18、时间日期
//方法详解
now() //命名构造,获取当前时间
millisecondsSinceEpoch //DateTime转时间戳
fromMillisecondsSinceEpoch //时间戳转DateTime
parse(string) //字符串转DateTime
isBefore(date) //时间比较---在之前
isAfter(date) //时间比较---在之后
isAtSameMomentAs(date) //时间比较---相等
compareTo(date) //大于返回1;等于返回0;小于返回-1
add(Duration) //时间增加
subtract(Duration) //时间减少
difference(date) //时间差 两个时间相差 小时数
timeZoneName //本地时区简码
timeZoneOffset //返回UTC与本地时差 小时数
year、month、day、hour、minute、second、millisecond、microsecond
//返回 年、月、日、时、分、秒、毫秒、微妙
weekday //返回星期几
简单应用:使用了 date_format 组件
//获取当前时间
DateTime _dateTime = DateTime.now();
//设置默认时间
DateTime _nowDate = DateTime(1989, 2, 21); // 1989-02-21 00:00:00.000
//格式化年月日时间(前提要安装(内置的) date_format: ^1.0.6 并导入)
DateTime _nowDate = DateTime(1989, 2, 21);
var r = formatDate(_nowDate,[yyyy, '-', mm, '-', dd]); // 1989-02-21
//格式化时分秒(同样要安装 date_format: ^1.0.6 并导入)
DateTime _nowDate = DateTime.now();
var r = formatDate(_nowDate,[HH, ':', nn, ':', ss]); // 02:45:33
//获取时间戳 (_dateTime为当前日期)
var date1 = _dateTime.millisecondsSinceEpoch; //1569124347564
//时间戳转为日期
var date2 = DateTime.fromMillisecondsSinceEpoch(date1); //2019-09-22 11:52:27.564
//Flutter DateTime日期转换
var today = DateTime.now();
print('当前时间是:$today');
var date1 = today.millisecondsSinceEpoch;
print('当前时间戳:$date1');
var date2 = DateTime.fromMillisecondsSinceEpoch(date1);
print('时间戳转日期:$date2');
//拼接成date
var dentistAppointment = new DateTime(2019, 6, 20, 17, 30, 20);
print(dentistAppointment);
// 字符串转date
DateTime date3 = DateTime.parse("2019-06-20 15:32:41");
print(date3);
// 时间比较
print(today.isBefore(date3)); // 在之前
print(today.isAfter(date3)); // 在之后
print(date3.isAtSameMomentAs(date3)); // 相同
print(date3.compareTo(today)); // 大于返回1;等于返回0;小于返回-1。
// print(DateTime.now().toString());
// print(DateTime.now().toIso8601String());
//时间增加
var fiftyDaysFromNow = today.add(new Duration(days: 5));
print('today加5天:$fiftyDaysFromNow');
//时间减少
DateTime fiftyDaysAgo = today.subtract(new Duration(days: 5));
print('today减5天:$fiftyDaysAgo');
//时间差 两个时间相差 小时数
print('比较两个时间 差 小时数:${fiftyDaysFromNow.difference(fiftyDaysAgo)}');
print('本地时区简码:${today.timeZoneName}');
print('返回UTC与本地时差 小时数:${today.timeZoneOffset}');
print(
'获取年月日:${today.year}'); //month、day、hour、minute、second、millisecond、microsecond
print('星期:${today.weekday}'); // 返回星期几
19、弹框设置
这里只介绍了一些flutter本身的弹框,还有一些第三方的弹框可自行去探索使用
showDialog 基本格式
当然,这都需要我们在界面中点击触发才行
//弹框的基本格式
void _alertdialog() async { //一般都是异步操作,使用async await
var result = await showDialog(
barrierDismissible: true, // 当点击遮罩层时,是否把弹出框消化掉
context: context, //必写,大概是一个联系上下文的东西
builder: (context) { //builder,现在还不知道为什么这样写,以后再来补🖤💙⭐
return AlertDialog( //弹框的样式,这里使用的是AlertDialog
title: Text("提示信息"), //弹框的标题
content: Text("弹框内容33333"), //弹框的内容
// backgroundColor: Colors.red, //这里还可以设置一些其他的属性
actions: <Widget>[ //设置底部按钮,如取消、确认等
FlatButton(
child: Text("取消"),
onPressed: () {
Navigator.pop(context,"sss"); //这个的作用是让我们点击按钮后弹框消失,并且可以传输一个值,这里为sss,这个值就是一开始我们声明的 result,下面的确认按钮功能一样,另外,如果不写"sss",则会返回一个null
},
),
FlatButton(
child: Text("确定"),
onPressed: () {
Navigator.pop(context,"aaa");
},
)
],
);
});
print(result); //这里可以打印结果
}
1、AlertDialog
AlertDialog 是一个用于向用户传递信息的弹出层。一般是只有两个确认取消按钮的弹框
actions -> List<Widget> //底部可选操作集,比如‘确认’、‘取消’按钮等;
backgroundColor → Color //dialog背景颜色
content → Widget //diaolog主题内容,可以放在weiget中
contentPadding → EdgeInsetsGeometry //内容的位置,距离父边距padding
contentTextStyle → TextStyle //内容文字风格
elevation → double //dialog的悬浮高度,跟底部阴影有关
shape → ShapeBorder //dialog边框的圆角
title → Widget //dialog标题
titlePadding → EdgeInsetsGeometry //标题的区域的padding
titleTextStyle → TextStyle //标题的文字风格
//注意:由于AlertDialog通常使用child的大小来调整自身大小,所以使用一些widget无法正常工作;
2、SimpleDialog
SimpleDialog 是一个用于向用户传递确定信息并提供选项的弹出层。这个是带有选项的对话框,通过选择不同选项,返回不同内容,属性与AlertDialog大致相同
3、底部弹框showModalBottomSheet
void _showModalBottomSheet() async { //方法
var result = await showModalBottomSheet( //这里就直接使用showModalBottomSheet,代表底部弹框
context: context,
builder: (context){
return Container(
height: 130, //这要根据弹框的高度需求来设置高度
child: Column( //这里就是弹框的主要内容,但是是使用Column配合ListTile创建的
children: <Widget>[
ListTile(
title: Text("分享到朋友圈1"),
onTap: (){ //点击事件
Navigator.pop(context,"分享到朋友圈1"); //同样的传值
},
),
Divider(), //这是一个组件,是一道水平分割线
ListTile(
title: Text("分享到朋友圈2"),
onTap: (){
Navigator.pop(context,"分享到朋友圈2");
},
),
],
),
);
}
);
print(result);
}
20、旋转圈圈 加载动画
// 这是一个小圈圈的组件
Widget _getMoreWidget(){
return Center(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("加载中....",style: TextStyle(fontSize: 16),),
CircularProgressIndicator(
strokeWidth: 1.0,
)
],
),
),
);
}
}
四、布局
1、stack 层叠布局
大概可以理解为 在层叠基础上的 position,且在stack中的元素是有先后顺序之分的,在靠前位置的元素不会被之后的框架元素所束缚
stack
****** Stack
alignment //布局定位 默认 AlignmentDirectional.topStart,也可以直接传入参数,自定义位置(值在1与-1之间),如:alignment: Alignment(0.5,0.5),
textDirection //正反排序
TextDirection.ltr
TextDirection.rtl
fit 默认StackFit.loose
overflow 默认Overflow.clip
children 子元素
Align
****** Align
Align控件即对齐控件,能将子控件所指定方式对齐,并根据子控件的大小调整自己的大小。
alignment 布局定位
widthFactor 如果为非null,则将其高度设置为子高度乘以此系数。必须为正数
heightFactor 如果为非null,则将其宽度设置为子宽度乘以此系数。必须为正数
child 子元素
Positioned
****** Positioned
用来在 Stack 组件中辅助子组件定位的组件,建议在有三个及以上子组件的复杂布局时使用,它的常用属性有:
left 左边距
top 上边距
right 右边距
bottom 下边距
width 子元素宽
height 子元素高
child 子元素
简单示例:
class PosionDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Container(
height: 400,
width: 300,
color: Colors.red,
child: Stack(
children: <Widget>[
Positioned(
bottom: 20,
left: 20,
child: Icon(Icons.home),
),
Positioned(
top: 30,
right: 30,
child: Icon(Icons.search),
),
Positioned(
bottom: 0,
right: 0,
child: Icon(Icons.settings),
)
],
),
),
);
}
}
2、Expanded
可以按比例“扩伸” Row
、Column
和Flex
子组件所占用的空间。
const Expanded({
int flex = 1,
@required Widget child,
})
flex
参数为弹性系数,如果为0或null
,则child
是没有弹性的,即不会被扩伸占用的空间。如果大于0,所有的Expanded
按照其flex的比例来分割主轴的全部空闲空间
3、drawer 抽屉
Drawer 左侧抽屉
endDrawer: Drawer(), 右侧抽屉
elevation 背景高度
child 子组件
semanticLabel 标签
UserAccountsDrawerHeader
decoration 头部装饰
margin 外边距 默认8.0
currentAccountPicture 主图像
otherAccountsPictures 附图像
accountName 标题
accountEmail 副标题
onDetailsPressed 点击监听
DrawerHeader 抽屉头部
DrawerHeader
通常用于在抽屉中在顶部展示一些基本信息;其包含如下属性:
decoration
:header
区域的decoration
,通常用来设置背景颜色或者背景图片duration
和curve
:如果decoration
发生了变化,则会使用curve
设置的变化曲线和duration
设置的动画时间来做一个切换动画child
:Header
里面所显示的内容控件padding
:Header
里面内容控件的padding
值,如果child
为null,则这个值无效margin
:Header
四周的间隙
如果想在DrawerHeader
中显示用户账户信息,比如类似于 Gmail 的 联系人头像、用户名、Email 等信息,则可以使用 UserAccountsDrawerHeader
这个特殊的DrawerHeader
。
UserAccountsDrawerHeader
UserAccountsDrawerHeader
可以设置用户头像、用户名、Email 等信息,显示一个符合MD
规范的 drawer header
。其常用属性如下:
margin
:Header
四周的间隙decoration
:header
区域的decoration
,通常用来设置背景颜色或者背景图片currentAccountPicture
:用来设置当前用户的头像otherAccountsPictures
:用来设置当前用户的其他账号的头像(做多显示三个)accountName
:当前用户的名字accountEmail
:当前用户的 EmailonDetailsPressed
: 当 accountName 或者 accountEmail 被点击的时候所触发的回调函数,可以用来显示其他额外的信息
示例:使用DrawerHeader设置抽屉头部样式
Widget header = DrawerHeader(
padding: EdgeInsets.zero,
/* padding置为0 */
child: new Stack(children: <Widget>[
/* 用stack来放背景图片 */
new Image.asset(
'images/mao.jpg',
fit: BoxFit.fill,
width: double.infinity,
),
new Align(
/* 先放置对齐 */
alignment: FractionalOffset.bottomLeft,
child: Container(
height: 70.0,
margin: EdgeInsets.only(left: 12.0, bottom: 12.0),
child: new Row(
mainAxisSize: MainAxisSize.min,
/* 宽度只用包住子组件即可 */
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new CircleAvatar(
backgroundImage: AssetImage('images/mao.jpg'),
radius: 35.0,
),
new Container(
margin: EdgeInsets.only(left: 6.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start, // 水平方向左对齐
mainAxisAlignment: MainAxisAlignment.center, // 竖直方向居中
children: <Widget>[
new Text(
"此处",
style: new TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w400,
color: Colors.white),
),
new Text(
"有肥猫",
style: new TextStyle(fontSize: 14.0, color: Colors.white),
),
],
),
),
],
),
),
),
]),
);
4、AspectRatio
AspectRatio
一个widget,试图将子widget的大小指定为某个特定的长宽比
这是一个比率控件,按照宽度和比率来计算高度。一般会设置父容器的宽度,然后设置**AspectRatio
的aspectRatio,那么AspectRatio
**就会按照指定比例来显示;
child: AspectRatio(
aspectRatio: 16 / 9, //此处设置宽高比例
child: Swiper(........)
)
5、wrap
属性解析
direction:主轴(mainAxis)的方向,默认为水平。
alignment:主轴方向上的对齐方式,默认为start。
spacing:主轴方向上的间距。
runAlignment:run的对齐方式。run可以理解为新的行或者列,如果是水平方向布局的话,run可以理解为新的一行。
runSpacing:run的间距。
crossAxisAlignment:交叉轴(crossAxis)方向上的对齐方式。
textDirection:文本方向。
verticalDirection:定义了children摆放顺序,默认是down,见Flex相关属性介绍。
一种遍历数组的方式(简单例子):
Wrap(
spacing: 10, //间距
children:this._history.map((value){ //this._history是遍历的数组对象,value是每一项
return Container(
child: RaisedButton(
child: Text(value),
onPressed: (){print(value);}
),
);
}).toList(),
6、屏幕适配
1、安装与引用
flutter_screenutil: ^0.6.0
import 'package:flutter_screenutil/flutter_screenutil.dart';
2、封装 --不封装也可以引入定义后使用,不过太繁琐,封装一下更方便
import 'package:flutter_screenutil/flutter_screenutil.dart';
class ScreenAdaper{
//初始化
static init(context){
ScreenUtil.instance = ScreenUtil(width: 750, height: 1334)..init(context);
}
//高度
static height(double value){
return ScreenUtil.getInstance().setHeight(value);
}
//宽度
static width(double value){
return ScreenUtil.getInstance().setWidth(value);
}
//获取屏幕高度
static getScreenHeight(){
return ScreenUtil.screenHeightDp;
}
//获取屏幕宽度
static getScreenWidth(){
return ScreenUtil.screenWidthDp;
}
static getScreenPxHeight(){
return ScreenUtil.screenHeight;
}
static getScreenPxWidth(){
return ScreenUtil.screenWidth;
}
// ScreenUtil.screenHeight
}
// ScreenAdaper
五、路由
路由(Route)在移动开发中通常指页面(Page),这跟web开发中单页应用的Route概念意义是相同的,Route在Android中通常指一个Activity,在iOS中指一个ViewController。所谓路由管理,就是管理页面之间如何跳转,通常也可被称为导航管理。Flutter中的路由管理和原生开发类似,无论是Android还是iOS,导航管理都会维护一个路由栈,路由入栈(push)操作对应打开一个新页面,路由出栈(pop)操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈。
1、简单示例:
-
创建一个新路由,命名“NewRoute”
class NewRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("New route"), ), body: Center( child: Text("This is new route"), ), ); } }
新路由继承自
StatelessWidget
,界面很简单,在页面中间显示一句"This is new route"。 -
在
_MyHomePageState.build
方法中的Column
的子widget中添加一个按钮(FlatButton
) :Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ ... //省略无关代码 FlatButton( child: Text("open new route"), textColor: Colors.blue, onPressed: () { //导航到新路由 Navigator.push( context, MaterialPageRoute(builder: (context) { return NewRoute(); })); }, //路由返回 onTap: (){ // pop后面可以跟上参数 Navigator.of(context).pop("旺财 北京市昌平区沙河镇北京科技经营管理学院"); }, ), ], )
我们添加了一个打开新路由的按钮,并将按钮文字颜色设置为蓝色,点击该按钮后就会打开新的路由页面。
2、Navigator
Navigator
是一个路由管理的组件,它提供了打开和退出路由页方法。Navigator
通过一个栈来管理活动路由集合。通常当前屏幕显示的页面就是栈顶的路由。Navigator
提供了一系列方法来管理路由栈,在此我们只介绍其最常用的两个方法:
push和pop, push 是将元素添加到堆栈的顶部,pop是从同一堆栈中删除顶部元素。
在Flutter的情况下,当我们导航到另一个屏幕时,我们使用Navigator.push方法将新屏幕添加到堆栈的顶部。当然,这些pop方法会从堆栈中删除该屏幕
context:代表上下文,也就是类似windows中的句柄,指的是当前的这个页面窗口。
Navigator.pop(context);
Navigator.pop(context):pop在javascript中用于删除数组的最末一个元素,这就明白了,就是删除当前页面返回到Navigator中的前一个页面。
Navigator.push(BuildContext context, Route route)
//等价于
Navigator.of(context).push(Route route)
3、MaterialPageRoute
我们可以直接使用 MaterialPageRoute
创建路由,它是一种模态路由,可以通过平台自适应的过渡效果来切换屏幕。默认情况下,当一个模态路由被另一个替换时,上一个路由将保留在内存中,如果想释放所有资源,可以将 maintainState
设置为 false
。
示例:
onPressed: () {
Navigator.push(
context,
//其中,new SecondScreen()是另一个界面
new MaterialPageRoute(builder: (context) => new SecondScreen()),
);
},
4、命名式路由
所谓“命名路由”(Named Route)即有名字的路由,我们可以先给路由起一个名字,然后就可以通过路由名字直接打开新的路由了,这为路由管理带来了一种直观、简单的方式。
1、注册路由表
创建Routes.dart文件
routes 规则
// 不需要传值
"/item1":(context)=>Item1(),
// 可以传值 ,传值的路由要多写一个arguments
"/item2":(context,{arguments})=>Item2(arguments:arguments),
实例 : 将路由抽出,单独做一个文件
import 'package:flutter/material.dart';
import './Item1.dart';
import './Item2.dart';
import './Item3.dart';
final routes = {
"/item1": (context) => Item1(),
//要传值的路由要多写一个arguments
"/item2": (context,{arguments}) => Item2(arguments: arguments),
"/item3": (context) => Item3()
};
// 如果你要把路由抽离出去,需要写下面这一堆的代码。。。。。。。。。必须要加。。。。。。。。。
var onGenerateRoute = (RouteSettings settings) {
// 统一处理
final String name = settings.name;
final Function pageContentBuilder = routes[name];
if (pageContentBuilder != null) {
if (settings.arguments != null) {
final Route route = MaterialPageRoute(
builder: (context) =>
pageContentBuilder(context, arguments: settings.arguments));
return route;
} else {
final Route route =
MaterialPageRoute(builder: (context) => pageContentBuilder(context));
return route;
}
}
};
2、main.dart配置
//在main.dart中配置如下:
return MaterialApp(
initialRoute: "/", // 默认访问路径
onGenerateRoute: onGenerateRoute
);
3、跳转
children: <Widget>[
//普通的路由跳转
MaterialButton(
child: Text("去item1"),
onPressed: (){
Navigator.pushNamed(context, "/item1");
},
),
//路由传值的路由跳转
MaterialButton(
child: Text("去item2"),
onPressed: (){
//与普通路由跳转相比,后多了个arguments对象,arguments就是我们传的值
Navigator.pushNamed(context, "/item2",arguments: {"id":12445});
},
),
4、接收并显示
- 首先在要接收传值的路由对象中定义并接收
//这里的arguments就是我们接受的传值
final arguments;
Item2({this.arguments});
- 然后在页面上显示出来
Text("${arguments['id']}"),
完整例子:
import 'package:flutter/material.dart';
class Item2 extends StatelessWidget {
final arguments;
Item2({this.arguments});
@override
Widget build(BuildContext context) {
return Container(
height: 300,
margin: EdgeInsets.all(10),
child: Text("${arguments['id']}"),
);
}
}
六、数据交互
1、子传父
/*----------------------------------获取值的父组件------------------------------------*/
var _data = "";
.....//中间的语句省略
RaisedButton(
child: Text("跳转到search"),
onPressed: () async{
//导航到新路由
var data = await Navigator.push(context, MaterialPageRoute(builder: (context) {
return Search();
}));
setState(() {
_data = data;
});
},
//这个也可以使用 .then 的方式来获取数据
/*onPressed: () {
//导航到新路由
var data = await Navigator.push(context, MaterialPageRoute(builder: (context) {
return Search();
})).then((res){
setState(() {
_data = data;
});
});
}, */
),
Text("${ _data =='' ? '无内容':_data}"),
/*---------------------------------发送值的子组件------------------------------------*/
body: ListView(
children: <Widget>[
RaisedButton(
child: Text("返回1"),
onPressed: () {
Navigator.of(context).pop("搜索内容11111");
},
),
RaisedButton(
child: Text("返回2"),
onPressed: () {
Navigator.of(context).pop("搜索内容22222");
},
),
],
)
2、父传子
/*----------------------------------父组件--------------------------------------------*/
RaisedButton(
child: Text("表单"),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => FormPage(title: "表单数据xxx")));
},
),
/*-----------------------------------子组件-------------------------------------------*/
class FormPage extends StatelessWidget {
String title;
FormPage({this.title = ""}); //需要在这里进行定义,并可以设置初始值,在没有传值时显示
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(this.title)), //在这里接收
body: Text("body"),
floatingActionButton: FloatingActionButton( //只是一个返回按钮
child: Text("返回"),
onPressed: () {
Navigator.of(context).pop();
},
),
);
}
}
3、网络数据请求
1、http
1、配置
http: ^0.12.0+2
2、引入
import 'package:http/http.dart' as http; //这里的 as http表示我们以后可以用http指代这个组件
3、请求数据 (URL是访问地址)
var result = await http.get(URL); //get请求数据
var result = await http.post(URL,body: {"username":"wangcai","age":"10"}); //post上传
print(json.decode(result.body)); //用来转换返回结果
4、渲染数据 (根据自己的需要,这里是渲染为简单列表)
body: this._list.length>0 ? ListView.builder( //三元表达式
itemCount: this._list.length,
itemBuilder: (context,index){
return ListTile(
leading: Image.network("${this._list[index]['cover']}"),
title: Text("${this._list[index]['title']}"),
);
},
):Text("加载中...") //可以在这里写一个请求数据成功前简单的加载动画
补充:
从服务器得到的数据,要么是buffer,要么是字符串。 要使用数据,需要把json字符串转成Map类型
import 'dart:convert';
json.decode() //json字符串转成Map类型
json.encode() //如果把一个Map类型对象,转成json字符串
json.decode(xxx is Map) //判断xxx是否是Map类型,返回值为true或者false
2、dio
简单示例:
import 'package:dio/dio.dart';
void getHttp() async {
try { //这里的try、catch只是为了更加方便地返回错误,可写可不写
Response response = await Dio().get("http://www.baidu.com");
print(response);
} catch (e) {
print(e);
}
}
1、配置
dio: ^2.1.13 //重点。。。不要用最新版,有毛病
2、引入
import 'package:dio/dio.dart';
3、请求数据 (apiUrl是访问地址)
var result = await Dio().get(apiUrl); //dio请求数据
//dio提交数据
Map jsonData = {"username":"wangcai","age":10};
Response result = await Dio().post(apiUrl,data: jsonData);
4、渲染数据 (根据自己的需要,这里是渲染为简单列表)
body: this._list.length>0 ? ListView.builder( //三元表达式
itemCount: this._list.length,
itemBuilder: (context,index){
return ListTile(
leading: Image.network("${this._list[index]['cover']}"),
title: Text("${this._list[index]['title']}"),
);
},
):Text("加载中...") //可以在这里写一个请求数据成功前简单的加载动画
4、上拉加载 与 下拉刷新
1、RefreshIndicator
下拉刷新
flutter
内部提供了一个组件,叫RefreshIndicator
,可以实现下拉刷新。并没有上拉加载更多的组件。
2、ScrollController
上拉加载
ListView
中有一个属性,叫ScrollController
属性,这是ListView
的滑动事件。我们可以利用ScrollController
实现上拉加载更多。
this._list.addAll(res); //拼接,表示把每一次的res数据都拼接到_list上
1、RefreshIndicator
/*
* 下拉刷新组件
*const RefreshIndicator
({
Key key,
@required this.child,
this.displacement: 40.0, //触发下拉刷新的距离
@required this.onRefresh, //下拉回调方法,方法需要有async和await关键字,没有await,刷新图标立马消失,没有async,刷新图标不会消失
this.color, //进度指示器前景色 默认为系统主题色
this.backgroundColor, //背景色
this.notificationPredicate: defaultScrollNotificationPredicate,
})
*/
//在body中,内容都需要写在RefreshIndicator里
RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(.......) //ListView.builder是用来循环显示数据用的
//在RefreshIndicator中的onRefresh来触发这个方法,然后通过这个去触发请求数据的_getData方法
Future<void> _onRefresh() async { //_onRefresh就是触发的方法名
await Future.delayed(Duration(milliseconds: 2000), () {
_getData();
});
}
2、ScrollController
一些属性:
offset
:可滚动组件当前的滚动位置。jumpTo(double offset)
、animateTo(double offset,...)
:这两个方法用于跳转到指定的位置,它们不同之处在于,后者在跳转时会执行一个动画,而前者不会。
1、注册
ScrollController _scrollController = new ScrollController();
2、监听
child: ListView.builder(
controller: _scrollController, //在页面处放置属性
.......)
//监听滚动条事件
_scrollController.addListener(() {
// _scrollController.position.pixels.toInt(); //滚动条下拉的距离
// _scrollController.position.maxScrollExtent.toInt(); //整个页面的高度
if (_scrollController.position.pixels.toInt() ==
_scrollController.position.maxScrollExtent.toInt()) {
this._getData(); //当滚动条下拉的距离和整个页面的高度相同时,触发请求数据事件
}
});
3、请求数据
void _getData() async {
if (this.hasMore) { //判断不是最后一页,才会去请求数据
var apiUrl =
"http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=${_page}";
var response = await Dio().get(apiUrl);
var res = json.decode(response.data)["result"];
setState(() {
this._list.addAll(res); //拼接数据
this._page++;
});
// //判断是否是最后一页
if (res.length < 20) {
setState(() {
this.hasMore = false;
});
}
}
}
5、shared_preferences 共享状态
包装NSUserDefaults
(在ios上)和SharedPreferences
(在Android上),为简单数据提供一个持久存储。数据异步地保存到磁盘。两个平台都不能保证写操作在返回后被持久化到磁盘上,而且这个插件不能用于存储关键数据。
1、安装与引入
shared_preferences: ^0.5.3+4
import 'package:shared_preferences/shared_preferences.dart';
2、使用
Ⅰ、首先新建一个Storage.dart
文件,当然也可以不新建,直接在哪调用在哪写,不过太麻烦
// 封装操作状态的方法
import 'package:shared_preferences/shared_preferences.dart';
// 浏览器中可以在localstage cookie indexDB
// shared_preferences 存储的数据也是键值对 String类型
class Storage{
// static表示静态方法 静态方法只能类名来调用 Storage.setString("name","wangcai")
static Future<void> setString(key,value) async { //设置。。也就是添加或者修改
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(key, value);
}
static Future<String> getString(key) async { //获取
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString(key);
}
static Future<void> remove(key) async { //删除
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.remove(key);
}
}
Ⅱ、使用Storage
import 'package:flutter/material.dart';
import '../common/Storage.dart'; //引入新建的Storage.dart文件
//.....
//以下为使用状态的方法,其他的省略
void _saveData() async{
//小明,18岁分别为键和值,此方法既可以添加,也可以修改,都是根据键名有无来进行判断
await Storage.setString("小明", "18岁");
}
void _getData() async {
//输入键名进行获取,
var username = await Storage.getString("小明");
print(username);
}
void _removeData() async {
//删除,同样是根据键名,
await Storage.remove("小明");
}
6、在模型类中序列化JSON
1、 序列化
//序列化操作
var strData = '{"name":"wangcai","age":20}';
// 把JSON字符串转成Map类型
print(json.decode(strData));
print(json.decode(strData) is Map); // true
var res = json.decode(strData);
print(res["name"]); // wangcai,在这里只能使用xxx[yyy]的形式来获取,在编辑过程中很难看出是否错误
2、反序列化
//反序列化操作
var strData = {"name":"wangcai","age":20};
print(json.encode(strData));
print(json.encode(strData) is String); // true
3、模型类
当请求大量数据后,我们需要对其进行JSON.decode
或者JSON.encode
,但这样转码会比较繁琐,且容易出错,我们可以通过引入一个简单的模型类(model class)来解决前面提到的问题,我们称之为User
。在User类内部,我们有:
- 一个
User.fromJson
构造函数, 用于从一个map构造出一个User
实例 map structure - 一个
toJson
方法, 将User
实例转化为一个map.
这样,调用代码现在可以具有类型安全、自动补全字段(name和email)以及编译时异常。如果我们将拼写错误或字段视为int
类型而不是String
, 那么我们的应用程序就不会通过编译,而不是在运行时崩溃。
user.dart示例
class User {
final String name;
final String email;
User(this.name, this.email);
User.fromJson(Map<String, dynamic> json)
: name = json['name'],
email = json['email'];
Map<String, dynamic> toJson() =>
{
'name': name,
'email': email,
};
}
现在,序列化逻辑移到了模型本身内部。采用这种新方法,我们可以非常容易地反序列化user。
Map userMap = JSON.decode(json);
var user = new User.fromJson(userMap);
print('Howdy, ${user.name}!');
print('We sent the verification link to ${user.email}.');
要序列化一个user,我们只是将该User
对象传递给该JSON.encode
方法。我们不需要手动调用toJson
这个方法,因为JSON.encode
已经为我们做了。
String json = JSON.encode(user);
使用:
1、可在lib下创建一个Model文件夹,然后在其中创建一些自己需要的Model文件,例如textModel.dart
2、在textModel.dart
文件中添加一些代码,步骤为
Ⅰ、将接收过来的数据在网上找个JSON格式化解析工具进行解析
{"_id":"67890fsadf56789fsadf","title":"手机","status":"1","url":"abc"} //接收的数据
Ⅱ、将解析好的数据放入JSON to Dart中,点击 Geberate Dart
, 将生成的代码复制进textModel.dart
中,并根据自己的需要更改类名
class TextModel {
String sId;
String title;
String status;
String url;
TextModel({this.sId, this.title, this.status, this.url});
TextModel.fromJson(Map<String, dynamic> json) {
sId = json['_id'];
title = json['title'];
status = json['status'];
url = json['url'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['_id'] = this.sId;
data['title'] = this.title;
data['status'] = this.status;
data['url'] = this.url;
return data;
}
}
Ⅲ、使用
import '../../model/textModel.dart'; //引入我们创建的textModel.dart文件
//str是请求的 数据
var str = '{"_id":"67890fsadf56789fsadf","title":"手机","status":"1","url":"abc"}';
//这是
var focus = TextModel.fromJson(json.decode(str)); //将数据放入TextModel中
print(focus.sId); //这里就可以通过xxx.yyy的格式来编写代码,并带有提示
print(focus.title);
print(focus.url);
七、flutter生命周期

大致可以看成三个阶段
- 初始化(插入渲染树)
- 状态改变(在渲染树中存在)
- 销毁(从渲染树种移除)
1、构造函数
这个函数不属于生命周期,因为这个时候State的widget属性为空,如果要在构造函数中访问widget的属性是行不通的。但是构造函数必然是要第一个调用的。
2、initState
/// Called when this object is inserted into the tree.
当插入渲染树的时候调用,这个函数在生命周期中只调用一次。这里可以做一些初始化工作,比如初始化State的变量。
3、didChangeDependencies
/// Called when a dependency of this [State] object changes.
这个函数会紧跟在initState之后调用,并且可以调用BuildContext.inheritFromWidgetOfExactType,那么BuildContext.inheritFromWidgetOfExactType的使用场景是什么呢?最经典的应用场景是
new DefaultTabController(length: 3, child: new TabBar(
tabs: [ "主页","订单","我的" ]
.map( (data)=>new Text(data) ).toList(),
4、didUpdateWidget
/// Called whenever the widget configuration changes.
当组件的状态改变的时候就会调用didUpdateWidget,比如调用了setState.
实际上这里flutter框架会创建一个新的Widget,绑定本State,并在这个函数中传递老的Widget。
这个函数一般用于比较新、老Widget,看看哪些属性改变了,并对State做一些调整。
需要注意的是,涉及到controller的变更,需要在这个函数中移除老的controller的监听,并创建新controller的监听。
5、deactivate
/// Called when this object is removed from the tree.
在dispose之前,会调用这个函数。
6、dispose
/// Called when this object is removed from the tree permanently.
一旦到这个阶段,组件就要被销毁了,这个函数一般会移除监听,清理环境。
小结
阶段 | 调用次数 | 是否支持setState |
---|---|---|
构造函数 | 1 | 否 |
initState | 1 | 无效(使用setState和不使用一样) |
didChangeDependencies | >=1 | 无效 |
didUpdateWidget | >=1 | 无效 |
deactivate | >=1 | 否 |
dispose | 1 | 否 |
八、第三方组件
1、时间日期
Flutter 的日期选择器控件
Ⅰ、date_format
格式化日期的简单应用程序接口。
1、安装与引入
date_format: ^1.0.6
import 'package:date_format/date_format.dart';
2、使用(部分示例)
// 1989-02-21
print(formatDate(DateTime(1989, 02, 21), [yyyy, '-', mm, '-', dd]));
// 89-feb-21
print(formatDate(DateTime(1989, 2, 21), [yy, '-', M, '-', d]));
// 15:40:10
print(formatDate(DateTime(1989, 02, 1, 15, 40, 10), [HH, ':', nn, ':', ss]));
// 15:40:10+0100
print(formatDate(DateTime(1989, 02, 1, 15, 40, 10), [HH, ':', nn, ':', ss, z]));
Ⅱ、内置的datepicker
//基本要求
Future<DateTime> showDatePicker ({
@required BuildContext context, // 上下文
@required DateTime initialDate, // 初始日期
@required DateTime firstDate, // 日期范围,开始
@required DateTime lastDate, // 日期范围,结尾
SelectableDayPredicate selectableDayPredicate,
DatePickerMode initialDatePickerMode: DatePickerMode.day,
Locale locale, // 国际化
TextDirection textDirection,
});
Future<TimeOfDay> showTimePicker({
@required BuildContext context,
@required TimeOfDay initialTime
});
示例::
var result = await showDatePicker( //showDatePicker内置时间模块
context: context, //必写,上下文
initialDate: DateTime.now(), // 初始日期
firstDate: DateTime(1980), // 日期范围,开始
lastDate: DateTime(2100), // 日期范围,结尾
locale: Locale('zh') // 国际化
);
setState(() {
this._nowDate = result;
});
}
Ⅲ、 flutter_cupertino_date_picker
Flutter 的日期选择器控件
1. 安装与引入
在项目的 pubspec.yaml
文件中添加依赖:
flutter_cupertino_date_picker: ^1.0.12
import 'package:flutter_cupertino_date_picker/flutter_cupertino_date_picker.dart';
2. 属性
/// 显示BottomSheet形式的日期时间选择器。
///
/// context: [BuildContext]
/// minDateTime: [DateTime] 日期选择器的最小值
/// maxDateTime: [DateTime] 日期选择器的最大值
/// initialDateTime: [DateTime] 日期选择器的初始值
/// dateFormat: [String] 日期时间格式化
/// locale: [DateTimePickerLocale] 国际化,语言地区
/// pickerMode: [DateTimePickerMode] 显示的类型: date(日期选择器)、time(时间选择器)、datetime(日期时间选择器)
/// pickerTheme: [DateTimePickerTheme] 日期选择器的样式
/// onCancel: [DateVoidCallback] 点击标题取消按钮的回调事件
/// onClose: [DateVoidCallback] 关闭日期时间选择器的回调事件
/// onChange: [DateValueCallback] 选择的日期时间改变的事件
/// onConfirm: [DateValueCallback] 点击标题确定按钮的回调事件
DatePicker.showDatePicker(
BuildContext context,
DateTime minDateTime,
DateTime maxDateTime,
DateTime initialDateTime,
String dateFormat,
DateTimePickerLocale locale: DATETIME_PICKER_LOCALE_DEFAULT,
DateTimePickerMode pickerMode: DateTimePickerMode.date,
DateTimePickerTheme pickerTheme: DatePickerTheme.Default,
DateVoidCallback onCancel,
DateVoidCallback onClose,
DateValueCallback onChange,
DateValueCallback onConfirm,
});
3、使用
DatePicker.showDatePicker( //显示日期方法
context, //上下文
pickerTheme: DateTimePickerTheme( //底部按钮设置
showTitle: true,
confirm: Text('确定', style: TextStyle(color: Colors.red)),
cancel: Text('取消', style: TextStyle(color: Colors.cyan)),
),
minDateTime: DateTime.parse("1990-01-01"), //最小日期
maxDateTime: DateTime.parse("2022-12-12"), //最大日期
initialDateTime: _dateTime, //当前时间
dateFormat: "yyyy-MMMM-dd", //日期格式
locale: DateTimePickerLocale.zh_cn, //语言
onClose: () => print("----- onClose -----"), //关闭时间组件事件
onCancel: () => print('onCancel'), //取消事件,与上方取消按钮关联
onChange: (dateTime, List<int> index) { //改变时间事件
setState(() {
_dateTime = dateTime;
});
},
onConfirm: (dateTime, List<int> index) { //确认事件,同样与上方确定按钮关联
setState(() {
_dateTime = dateTime;
});
},
);
2、html文本显示
flutter_html : 把前端中的标签转成flutter中的组件(但认识的标签有限,可能不能完全解析文本) webview : 在app中嵌套一个网页 flutter_inappbrowser (第三方,功能较为完善)
**flutter_inappbrowser **
InAppWebView.initialUrl //将被加载的初始url。
InAppWebView.initialFile //将要加载的初始资产文件。
InAppWebView.initialData //最初的`InAppWebViewInitialData`会被装载。
InAppWebView.initialHeaders //将使用的初始标题。
InAppWebView.initialOptions //将使用的初始选项。
InAppWebView( //调用组件
//设置初始的url
initialUrl: "http://www.phonegap100.com/newscontent.php?aid=${this.arguments["aid"]}",
//这是一个监测状态变化的方法,其中的progerss是一个上面获取数据的进度,从0~100,
onProgressChanged: (InAppWebViewController controller, int progerss){
// print(progerss); //0~100
if((progerss/100).toInt() == 1){ //当数据加载完全后。。。。
setState(() { //将设置的加载动画消去(根据需求)
this._flag = false;
});
}
},
),
3、device_info 获取设备信息
1、安装与引入
device_info: ^0.4.0+2
import 'package:device_info/device_info.dart';
2、官网案例
import 'package:device_info/device_info.dart';
//得到设备信息
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); //不管获取安卓还是ios,都要写这句
//获取安卓的设备信息
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
print('Running on ${androidInfo.model}'); // androidInfo中有很多属性,如version、type、androidId、model,,可根据需要取用
//获取ios的设备信息
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
print('Running on ${iosInfo.utsname.machine}'); // e.g. "iPod7,1"
3、在方法中使用
_getInter() async {
var connectivityResult = await (Connectivity().checkConnectivity());
// print(connectivityResult == ConnectivityResult.none);
if (connectivityResult == ConnectivityResult.none) {
print("没有网络"); //没有网络时
} else {
if (connectivityResult == ConnectivityResult.mobile) {
print("手机网络"); //手机网络时
} else if (connectivityResult == ConnectivityResult.wifi) {
print("wifi"); //wifi 时
}
}
}
3、connectivity 获取网络环境
1、安装与引入
connectivity: ^0.4.4
import 'package:connectivity/connectivity.dart';
2、使用
import 'package:connectivity/connectivity.dart';
var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.mobile) { //如果是手机网络
// I am connected to a mobile network.
} else if (connectivityResult == ConnectivityResult.wifi) { //如果是wifi
// I am connected to a wifi network.
}
4、视频播放
-
在 Flutter 里官方提供了一个
video_player
插件可以播放视频。 -
chewie
,是一个非官方的第三方视频播放组件,看起来好像是基于 HTML5 播放的组件。chewie 相对 video_player 来说,有控制栏和全屏的功能。Chewie 使用 video_player 引擎并将其包裹在友好的 Material 或 Cupertino UI 中!
1、安装与引入
video_player: ^0.10.2+1
chewie: ^0.9.8
import 'package:video_player/video_player.dart';
import 'package:chewie/chewie.dart';
2、使用示例:
class _VideoAppState extends State<VideoApp> {
VideoPlayerController _controller;
bool _isPlaying = false;
String url = 'http://vd3.bdstatic.com/mda-ifvq' +
'u9yp3eaqueep/mda-ifvqu9yp3eaqueep.mp4';
@override
void initState() {
super.initState();
_controller = VideoPlayerController.network(this.url)
// 播放状态
..addListener(() {
final bool isPlaying = _controller.value.isPlaying;
if (isPlaying != _isPlaying) {
setState(() { _isPlaying = isPlaying; });
}
})
// 在初始化完成后必须更新界面
..initialize().then((_) {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Video Demo',
home: new Scaffold(
body: new Center(
child: _controller.value.initialized
// 加载成功
? new AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
) : new Container(),
),
floatingActionButton: new FloatingActionButton(
onPressed: _controller.value.isPlaying
? _controller.pause
: _controller.play,
child: new Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
),
);
}
}
5、轮播图
官网自夸 --> flutter最强大的siwiper, 多种布局方式,无限轮播,Android和IOS双端适配 .官网地址,中文的哦
1、安装与引入
flutter_swiper: ^1.1.6
import 'package:flutter_swiper/flutter_swiper.dart';
2、参数
scrollDirection //Axis.horizontal 滚动方向,设置为Axis.vertical如果需要垂直滚动
loop //true 无限轮播模式开关
index //0 初始的时候下标位置
autoplay //false 自动播放开关.
onIndexChanged //void onIndexChanged(int index) 当用户手动拖拽或者自动播放引起下标改变的时候调用
onTap //void onTap(int index)当用户点击某个轮播的时候调用
duration //300.0 动画时间,单位是毫秒
pagination //null 设置 `new SwiperPagination()` 展示默认分页指示器
control //null 设置 `new SwiperControl()` 展示默认分页按钮
//分页指示器
//分页指示器继承自 `SwiperPlugin`,`SwiperPlugin` 为 `Swiper` 提供额外的界面.设置为`new SwiperPagination()` 展示默认分页.
alignment //Alignment.bottomCenter如果要将分页指示器放到其他位置,那么可以修改这个参数
margin //const EdgeInsets.all(10.0)分页指示器与容器边框的距离
builder //SwiperPagination.dots目前已经定义了两个默认的分页指示器样式: `SwiperPagination.dots` 、 `SwiperPagination.fraction`,都可以做进一步的自定义.
//控制按钮
iconPrevious //Icons.arrow_back_ios 上一页的IconData
iconNext //Icons.arrow_forward_ios下一页的IconData
color //Theme.of(context).primaryColor 控制按钮颜色
size //30.0 控制按钮的大小
padding //const EdgeInsets.all(5.0)控制按钮与容器的距离
//控制器
//SwiperController` 用于控制 Swiper的`index`属性, 停止和开始自动播放. 通过 `new SwiperController()` 创建一个SwiperController实例,并保存,以便将来能使用。
void move(int index, {bool animation: true}) //移动到指定下标,设置是否播放动画
void next({bool animation: true}) //下一页
void previous({bool animation: true}) //上一页
void startAutoplay() //开始自动播放
void stopAutoplay() //停止自动播放
//自动播放
autoplayDely //默认值3000 自动播放延迟毫秒数.
autoplayDisableOnInteraction //true 当用户拖拽的时候,是否停止自动播放.
3、简单使用
body: Column(
children: <Widget>[
Container(
child: AspectRatio( //可用来设置宽高比例的布局组件
aspectRatio: 16 / 9, //设置宽高比例
//轮播图
child: Swiper(
itemBuilder: (BuildContext context, int index) { //图片设置
return new Image.network(
imgList[index]["url"],
fit: BoxFit.fill,
);
},
itemCount: imgList.length, //图片数量
pagination: new SwiperPagination(),
control: new SwiperControl(),
),
),
)
],
)
九、第三方应用
有时候编写代码,在调用一些第三方应用(如摄像头,浏览器,手电筒等等时,最好重启一下项目
⭐、url_launcher 打开外部应用
1、安装并引入
url_launcher: ^5.1.3
import 'package:url_launcher/url_launcher.dart';
2、使用 (官网示例)
//这是一个打开浏览器的范例
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart'; //引入
void main() {
runApp(Scaffold(
body: Center(
child: RaisedButton(
onPressed: _launchURL, //触发方法
child: Text('Show Flutter homepage'),
),
),
));
}
_launchURL() async { //这才是重点
const url = 'https://flutter.dev'; //定义路径
if (await canLaunch(url)) { //canLaunch 表示是否能打开这个路径,返回true或false,是异步操作
await launch(url); //如果能打开的话就打开路径,使用默认浏览器,也是异步
} else {
throw 'Could not launch $url';
}
}
3、一些其他的简单例子
//打开浏览器---------------------------------------------------------------------
RaisedButton(
child: Text("打开浏览器"),
onPressed: () async {
const url = "http://www.baidu.com"; //访问地址
if(await canLaunch(url)){
await launch(url);
}else{
throw "Conld not launch $url";
}
},
),
//拨打电话---------------------------------------------------------------------
RaisedButton(
child: Text("拨打电话"),
onPressed: () async {
const tel = "tel:xxx"; //电话号码xxx
if(await canLaunch(tel)){
await launch(tel);
}else{
throw "Conld not launch $tel";
}
},
),
//发送短信---------------------------------------------------------------------
RaisedButton(
child: Text("发送短信"),
onPressed: () async {
const tel = "sms:xxx"; //与拨打电话类似,xxx也是电话号码
if(await canLaunch(tel)){
await launch(tel);
}else{
throw "Conld not launch $tel";
}
},
),
//打开微信---------------------------------------------------------------------
RaisedButton(
child: Text("打开微信"),
onPressed: () async {
const url = "weixin://"; //这是微信路径
if(await canLaunch(url)){
await launch(url);
}else{
throw "Conld not launch $url";
}
},
),
//打开支付宝---------------------------------------------------------------------
RaisedButton(
child: Text("打开支付宝"),
onPressed: () async {
const url = "alipays://"; //这是支付宝路径
if(await canLaunch(url)){
await launch(url);
}else{
throw "Conld not launch $url";
}
},
),
schema集合
QQ: mqq://
微信: weixin://
京东: openapp.jdmoble://
淘宝: taobao://
美团: imeituan://
点评: dianping://
1号店: wccbyihaodian://
支付宝: alipay://
微博: sinaweibo://
腾讯微博: TencentWeibo://
weico微博: weico://
知乎: zhihu://
豆瓣fm: doubanradio://
网易公开课: ntesopen://
Chrome: googlechrome://
QQ浏览器: mqqbrowser://
uc浏览器: ucbrowser://
搜狗浏览器: SogouMSE://
百度地图: baidumap:// bdmap://
优酷: youku://
人人: renren://
我查查: wcc://
有道词典: yddictproapp://
微盘: sinavdisk://
名片全能王: camcard://
1、amap_location 定位
使用amap_location
来进行定位,但需要在高德开发那里拿到一个定位id,然后根据pub.dev上的说明一点一点的使用,下面是使用时的一小部分
void _getlocation() async {
//先启动一下
await AMapLocationClient.startup(new AMapLocationOption(
desiredAccuracy: CLLocationAccuracy.kCLLocationAccuracyHundredMeters));
//直接获取定位:
var result = await AMapLocationClient.getLocation(true);
print(result);
setState(() {
this._longitude = result.longitude;
this._latitude = result.latitude;
});
}
2、image_picker 调用相机、相册
1、下载与引入
image_picker: ^0.6.1+4
import 'package:image_picker/image_picker.dart';
2、使用
/*拍照*/
_takePhoto() async {
var image = //可设置最小宽度maxWidth
await ImagePicker.pickImage(source: ImageSource.camera, maxWidth: 400);
setState(() {
this._image = image;
});
this._uploadImage(image);
}
/*打开相册*/
_openGallery() async {
var image =
await ImagePicker.pickImage(source: ImageSource.gallery, maxWidth: 400);
setState(() {
this._image = image;
});
}
//定义一个组件显示图片
Widget _buildImage() {
if (this._image == null) {
return Text("");
}
return Image.file(this._image);
}
//上传图片
_uploadImage(_imageDir) async {
FormData formData = new FormData.from({
"name": "zhangsna 6666666666",
"age": 20,
"sex": "男",
"file": new UploadFileInfo(_imageDir, "xxx.jpg"),
});
var response =
await Dio().post("http://jd.itying.com/imgupload", data: formData);
print(response);
}
}
核心代码
await ImagePicker.pickImage(source: ImageSource.camera, maxWidth: 400); //打开相机
await ImagePicker.pickImage(source: ImageSource.gallery, maxWidth: 400); //打开相册
//上传图片
FormData formData = new FormData.from({ //图片的各种信息
"name": "zhangsna 6666666666",
"age": 20,
"sex": "男",
"file": new UploadFileInfo(_imageDir, "xxx.jpg"), //_imageDir是我们选择的图片
});
await Dio().post("http://jd.itying.com/imgupload", data: formData);