相关帖子收藏
作为android开发者,直接用studio即可。
开发环境配置
中文文档
flutter入门电子书籍
-
打开setting,切换到plugin,然后搜索flutter,第一个就是,安装完重启。
然后file>new的时候就能看到多了个new flutter project 。
然后发现还得一个flutter SDK,那就去下呗
我的是window10系统,那就下对应的吧
flutter SDK下载 sdk下载成功,解压缩到比如D盘sdk目录下
配置环境变量,path最后添加一个 sdk的bin目录路径
环境变量中新增两个flutter访问的环境变量
| PUB_HOSTED_URL | https://pub.flutter-io.cn
| FLUTTER_STORAGE_BASE_URL | https://storage.flutter-io.cn-
检测环境变量
dos命令下输入flutter doctor
然后看结果,红框部分错误。下边还有个错误,那不管了,我装了2个studio,只给一个安装了flutter插件,我也只用一个。
发现问题
Unable to locate Android SDK
出现这个原因是环境变量没配置
加上 ANDROID_HOME 对应的value就是Android的sdk路径Android license status unknown.
证书问题,执行下边的命令
flutter doctor --android-licenses
结果返回个这个
C:\Windows\System32>flutter doctor --android-licenses
A newer version of the Android SDK is required. To update, run:
D:\sdk\tools\bin\sdkmanager --update
我都28.0.3了,不知道还往哪里新,是要29的preview版,还是28的要更新到最新,过分了啊
按照提示执行命令,然后好大一堆提示,然后出现个y/N,我输入Y没反应了。如果正常的话,会提示你N次输入yes or no,输入y就ok。
D:\sdk\tools\bin\sdkmanager --update
flutter doctor --verbose
家里更奇怪
家里安装提示都没问题
sutdio flutter插件也都装了,也重启了,可是file new 里看不到new flutter project工程。
搜个帖子解决 https://www.jianshu.com/p/ebaf065d7b1c
就是有个插件没勾上。
学习
-
随便new一个工程看看
结构如下,核心文件是lib目录下的dart文件
最后2个红框的是配置文件,相关库
知识:
- main函数使用了符号 => , 这是Dart中单行函数或方法的简写
- 2种常用的widget:StatelessWidget和StatefulWidget ,前者状态不可变,后者状态可以改变
看几个简单的
Icon,给个图片icon,给个颜色color,textDirection左右或者右到左,这个就是简单的展示图片,没有点击事件的
class Icon extends StatelessWidget {
/// Creates an icon.
///
/// The [size] and [color] default to the value given by the current [IconTheme].
const Icon(
this.icon, {
Key key,
this.size,
this.color,
this.semanticLabel,
this.textDirection,
}) : super(key: key);
IconButton 这个就带点击事件了
class IconButton extends StatelessWidget {
const IconButton({
Key key,
this.iconSize = 24.0,
this.padding = const EdgeInsets.all(8.0),
this.alignment = Alignment.center,
@required this.icon,//图片
this.color,//图片染色用
this.highlightColor,
this.splashColor,
this.disabledColor,
@required this.onPressed,
this.tooltip,//长按的提示文字
})
然后看到个BackButton,可以看到返回的就是iconbutton,不过icon和press事件写好的而已
class BackButton extends StatelessWidget {
const BackButton({ Key key, this.color }) : super(key: key);
final Color color;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
return IconButton(
icon: const BackButtonIcon(),
color: color,
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
onPressed: () {
Navigator.maybePop(context);
},
);
}
}
看下BackButtonIcon 返回的就是个icon,只是根据平台返回不同的图片而已。
class BackButtonIcon extends StatelessWidget {
const BackButtonIcon({ Key key }) : super(key: key);
/// Returns the appropriate "back" icon for the given `platform`.
static IconData _getIconData(TargetPlatform platform) {
switch (platform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return Icons.arrow_back;
case TargetPlatform.iOS:
return Icons.arrow_back_ios;
}
assert(false);
return null;
}
@override
Widget build(BuildContext context) => Icon(_getIconData(Theme.of(context).platform));
}
- 在Dart语言中使用下划线前缀标识符,会强制其变成私有的。
- 创建一个StatefulWidget
StatefulWidget 有个抽象方法要实现,返回的是一个State,完事我们需要写个State,这个有个抽象方法build返回我们需要的widget
class RandomWords extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return RandomWordsState();
}
}
class RandomWordsState extends State<RandomWords> {
@override
Widget build(BuildContext context) {
return _buildSuggestions();
}
- 修改主题颜色
在 MaterialApp下添加theme属性,ThemeData下好多属性的,自己可以点进去看看,试试,有的看名字大概就知道干啥的,有的还需要测试
return new MaterialApp(
title: "first demo",
theme: ThemeData(
primaryColor: Colors.white
brightness: Brightness.dark,//android的 白天夜晚模式
),
- 跳转页面
如下,push里传个参数route即可
Navigator.of(context).push(
new MaterialPageRoute(builder: (context){
fullscreenDialog: true//默认是false,有个后退的箭头,如果改成true,是个叉号
整体的认知
android里一个页面是个activity,而在flutter里,一个页面就是个widget
所以你需要的就是写多个widget即可。
除了启动的main.dart外,你可以写N个其他的xxx.dart 文件,然后通过
import 'xxx.dart'; 导入即可,完事xxx里的类,变量啥的也就可以用了。
所以不同的页面可以写在不同的dart文件里。需要跳转的话,引用下即可。
先学习基本控件,至于跳转就那么几种方式,后边再学。
基本控件的简单学习
- Text
就是简单的文本显示,类似TextView
几个常用的属性
overflow 文字过多显示不下的时候咋处理,有4种,下边有讲
textAlign:文字的对齐方式,left,center,right就是字面意思,justify效果下边有图说明,
还有start,end,和android的概念一样,是由textDirection决定的,ltr的话start就是left,rtl的话start就是right
textScaleFactor:文字的缩放
maxLines:行数限制
softWrap:是否换行,默认不换行的,你文字再多也只显示一行,要换行得改为true
structStyle:结构样式,里边2个比较像的参数leading和height,实际大小都是乘以font大小,现在说下他们的作用,详细的可以看源码里的注释
leading:举例fontsize为10,leading为5,实际就是50,那么效果就是每行的上边和下边个加25,默认是null,比0大就有效
height:和上边不太一样,这个是平分给baseline的上边和下边。默认值是1,比1小无效
strutStyle: StrutStyle(fontStyle: FontStyle.italic,leading: 5,height: 2,),
Text(
"textxxxxxxxxxx",
overflow: TextOverflow.fade,
textAlign: TextAlign.right,
textScaleFactor: 0.5,
maxLines: 1,
)
除了ellipsis,其他3种生效需要设置单行
enum TextOverflow {
/// Clip the overflowing text to fix its container.
clip,
/// Fade the overflowing text to transparent.
fade,
/// Use an ellipsis to indicate that the text has overflowed.
//不需要设置单行也能生效,末尾省略号
ellipsis,
/// Render overflowing text outside of its container.
visible,
}
justify看效果图,分别是left和justify效果
紫色是容器背景,看下第一行文字,left的情况最后留白比较大,而justify留白没了,平分给其他文字间隔了,就是这个效果,简单来说,就是文字两端对齐,然后大家再平分多余的空白
new Container(
width: 380,
color: Colors.purple,
child:
Text(
"Stretch lines of text that end with a soft line break to fill the width of the container",
textAlign: TextAlign.start,
),
),
富文本
用到了TextSpan,我还以为和android里一样,还能替换文字为图片啥的,貌似不是,这是文字,可以设置不同的style而已,还可以设置点击事件
style ://这里的style对下边的children也生效,除非children里的child重新设置了别的style属性
recognizer:GestureRecognizer抽象类,找下它的实现类
GestureRecognizer 里边可以看到它的4种子类
decoration:枚举类型,下划线,上划线,删除线,none
height:刚开始理解错了,以为设置text的高度了,看完注释发现这玩意就是个系数,乘以font的大小。
The height of this text span, as a multiple of the font size
Text.rich(
TextSpan(
text: "text is what",
style: TextStyle(decoration: TextDecoration.underline),
recognizer: x,
children: <TextSpan>[
TextSpan(
text: "second",
style: TextStyle(color: Colors.red, height: 2,decoration: TextDecoration.lineThrough)),
TextSpan(
text: "third",
style: TextStyle(color: Colors.purple, fontSize: 30,decoration: TextDecoration.overline))
]),
),
//
MultiTapGestureRecognizer x =
new MultiTapGestureRecognizer(kind: PointerDeviceKind.touch)
..onTap = (int pointer) {
};
- Row ,Column
这个类似android的线性布局,一个水平方向的,一个垂直方向的
里边有个children属性,添加一些widget即可。
主要看下前3个参数的作用
Column({
Key key,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
MainAxisSize mainAxisSize = MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline textBaseline,
List<Widget> children = const <Widget>[],
})
mainAxisAlignment :child如何分布,start,end,center 大家一起居左或居右或居中
其他几个space文章后边有介绍,有图,比较好理解
enum MainAxisAlignment {
start,
end,
center,
spaceBetween,//容器两端没有space的,中间平分space
spaceAround,//每个child 左右【对于row来讲】两边的space一样,换句话说,容器两边的space是1,那么child之间的space就是2
spaceEvenly,//容器两端的space和child之间的space一样,大家均分
}
mainAxisSize :
这个比较好理解了,就是容器的大小咋算的,max就相当于match_parent,min就相党羽wrap_content
crossAxisAlignment :child之间的对齐方式吧
- Statck
这个看起来像FrameLayout
overflow:默认是clip,也就是超出容器就不可见了。如果改成visible,那么超出容器的child也是可见的。当然这个是对有具体的position的child生效的,对于没有设置position的child不生效的,non-positioned children 会自动进行缩放的。
alignment:类似android里的gravity,没啥说的,枚举类很多,看自己需求
fit:有3种,都是对于 non-positioned children来说的
①默认的loose,child比stack容器size还大,那么child会缩小,以child的中心点缩放。
②expand:咋说了,字面意思,放大,就是说stack容器的大小是100,完事里边child的大小是50,你看到的效果child可能是100
③passthrough:
child修改位置用Positioned容器包裹起来,然后设置大小位置之类的。overflow如果是默认的clip,可能设置个位置看不到
好歹是个容器,咋感觉都不能设置背景。设置背景得用到下边的Container。
new Stack(
alignment: AlignmentDirectional.topStart,
fit: StackFit.expand,
overflow: Overflow.visible,
children: <Widget>[
Icon(Icons.favorite),
Positioned(child:Icon(Icons.ac_unit),left: 100,top: 100, ),
IconButton(
icon: Icon(Icons.print),
),
IconButton(
icon: Icon(Icons.save),
),
Text("xxxxxxxxxxxxxxxxxxx",overflow: TextOverflow.fade,maxLines: 1,),
Positioned(child: Text("zzzzzzzzzzzzzzzzzz",overflow: TextOverflow.fade,maxLines: 1,),left: 20,top: 20,width: 100,),
],
)
- Container
容器,有背景可以设置
color:设置一个背景颜色
decoration:和color冲突,不能都设置,也是用来装饰背景的。
padding:内容的边界,一般用EdgeInsets来设置4个边的值
margin:
constraints:设置最小最大宽高的
new Container(
// color: Colors.purpleAccent,
width: 300,
height: 300,
padding: EdgeInsets.all(100),
decoration: BoxDecoration(color: Colors.blue),
child: Text("xxxxxxxxxx"),
)
- IconButton
IconButton(
icon: Icon(Icons.save),
),
Text("xxxxxxxx"),
const double _kMinButtonSize = 48.0;//button的size的最小值
const IconButton({
Key key,
this.iconSize = 24.0,
this.padding = const EdgeInsets.all(8.0),
this.alignment = Alignment.center,
@required this.icon,
this.color,//icon的颜色
this.highlightColor,//就是按压的时候背景色
this.splashColor,//按压的时候波纹颜色
this.disabledColor,//字面意思
@required this.onPressed,
this.tooltip,
})
从效果图来看,iconButton,默认有个很大的padding的,下图那个波纹效果就能看出来。
-
RaisedButton
字面意思,凸起的button,换句话说带阴影的button
RaisedButton(
color: Colors.teal,
highlightColor: Colors.purple,
onPressed: () {
print('=============press');
},
textColor: Colors.white,
child: Text("book"),
),
常用的一些属性的解释
Color textColor,// 文字颜色
Color disabledTextColor,//button disabled的时候文字颜色
Color color,//button默认的背景
Color disabledColor,//不可用的背景
Color highlightColor,//button,按压的时候的背景颜色
Color splashColor,//按压的时候波纹的颜色
shape://ShapeBorder,背景的形状
ShapeBorder的实现类可以看下 ShapeBorder
系统实现的几个shape
-
CircleBorder(side:BorderSide(color: Colors.blueAccent,width: 3,style: BorderStyle.solid)),
可以不带参数,就是把button弄成了圆形,
参数side:BorderSide》color:边框的颜色,width:边框的线条宽度,style:可以不设置,就2种,还有个none,等于没有border了。
- RoundedRectangleBorder
圆角矩形。
BorderSide:和上边的一样,不解释了,就是边框的效果处理
borderRadius :这个设置矩形圆角的,可以设置4个角的圆角半径,有很多方法可以实例化
const RoundedRectangleBorder({
this.side = BorderSide.none,
this.borderRadius = BorderRadius.zero,
})
简单看下
BorderRadius.all(Radius radius)//设置4个角
//最终和上边的一样,设置4个角都是radius大小,radius的大小如果比button的高度一半还大,那么其实最大也就高度的一半,最终效果两边是个半圆。
BorderRadius.circular(double radius) : this.all(
Radius.circular(radius),
);
//这个就是左上,左下用的一个radius,右上右下用的一个radius
const BorderRadius.horizontal({
Radius left = Radius.zero,
Radius right = Radius.zero,
})
//道理和上边一样,左上,右上一样,左下右下一样
const BorderRadius.vertical({
Radius top = Radius.zero,
Radius bottom = Radius.zero,
})
//默认4个角都是0,你可以单独为某个角重新设置个值
const BorderRadius.only({
this.topLeft = Radius.zero,
this.topRight = Radius.zero,
this.bottomLeft = Radius.zero,
this.bottomRight = Radius.zero,
});
- ContinuousRectangleBorder
构造方法里的参数和上个RoundedRectangleBorder一模一样,可是效果不太一样
同样的borderRadius 效果完全不一样
const ContinuousRectangleBorder({
this.side = BorderSide.none,
this.borderRadius = BorderRadius.zero,
})
ContinuousRectangleBorder(borderRadius: BorderRadius.circular(113)),
看下效果,上边的circular是113,很大了,效果如下,而如果是RoundedRectangleBorder,大于15就成半圆了。所以啊,这个具体咋画的不太清楚。英文解释这个类的
smooth continuous transitions between the straight sides and the rounded corners.
- BeveledRectangleBorder
参数同上,只不过这里不是圆弧而是直线连起来
BeveledRectangleBorder(borderRadius: BorderRadius.circular(23))
效果图,radius太大就成了第二种效果了
- BoxBorder 抽象类,有2个实现类,这个只有border了,没有形状一说了。
就是设置一圈的线条,下边两个差不多,BorderDirectional这玩意用的start,end,而不是left,right,所以和direction有点关系
Border:
BorderDirectional:
const Border({
this.top = BorderSide.none,
this.right = BorderSide.none,
this.bottom = BorderSide.none,
this.left = BorderSide.none,
})
factory Border.all({
Color color = const Color(0xFF000000),
double width = 1.0,
BorderStyle style = BorderStyle.solid,
})
shape: Border.all()
BorderDirectional(top:BorderSide(color:Colors.purple)),
- InputBorder 抽象类,有2个实现类
看下构造方法就大概知道设置了啥默认属性
const OutlineInputBorder({//这个是一圈都有线
BorderSide borderSide = const BorderSide(),
this.borderRadius = const BorderRadius.all(Radius.circular(4.0)),
this.gapPadding = 4.0,
})
const UnderlineInputBorder({//这玩意的border只有下边一条线
BorderSide borderSide = const BorderSide(),
this.borderRadius = const BorderRadius.only(
topLeft: Radius.circular(4.0),
topRight: Radius.circular(4.0),
),
})
从效果来看,第一个类似RoundedRectangleBorder,多了个gapPadding的参数,其他类似
第二个可以设置4个角,默认的是上边2个角,完事它只显示下边一条线。
- StadiumBorder
这个默认效果就是两边半圆的效果,如果你需要的是这种,就比较省事了.
只有一个参数,可以设置下边框的颜色,线条粗细
const StadiumBorder({this.side = BorderSide.none})
EdgeInsetsGeometry
设置padding之类的时候经常要用到这个类,抽象的,有2个实现类
2个区别,一个用的left,right,一个用的start,end, 后者根据方向不同start和end代表的意思不同
const EdgeInsets.all(double value)
: left = value,
top = value,
right = value,
bottom = value;
const EdgeInsets.only({
this.left = 0.0,
this.top = 0.0,
this.right = 0.0,
this.bottom = 0.0,
});
const EdgeInsetsDirectional.only({
this.start = 0.0,
this.top = 0.0,
this.end = 0.0,
this.bottom = 0.0,
});
Container
前边就简单说了下两个属性,这里看下构造方法把能用的属性都看下
Container({
Key key,
this.alignment,
this.padding,
Color color,
Decoration decoration,
this.foregroundDecoration,
double width,
double height,
BoxConstraints constraints,
this.margin,
this.transform,
this.child,
})
- alignment:AlignmentGeometry
对齐方式,你也可以理解为重心,就是里边才child摆放位置在哪里,左上角,右上角,下边居中之类的
2个实现类
Alignment: x和y是百分比,左边界是-1,有边界是1,中心是0,绝对值比1大的就跑到容器外边去了
A value of -1.0 corresponds to the leftmost edge. A value of 1.0 corresponds to the rightmost edge
const Alignment(this.x, this.y)
AlignmentDirectional:
和Alignment差不多,构造方法都一样,x变成了start而已,还多了一堆静态变量可以用。
const AlignmentDirectional(this.start, this.y)
static const AlignmentDirectional topStart = AlignmentDirectional(-1.0, -1.0);
static const AlignmentDirectional topCenter = AlignmentDirectional(0.0, -1.0);
- padding:EdgeInsetsGeometry
设置内容距离边界的距离
2个实现类
EdgeInsets
看名字以及方法实现,大概就知道几个意思了,不解释了
const EdgeInsets.fromLTRB(this.left, this.top, this.right, this.bottom);
const EdgeInsets.only({
this.left = 0.0,
this.top = 0.0,
this.right = 0.0,
this.bottom = 0.0,
});
const EdgeInsets.all(double value)
: left = value,
top = value,
right = value,
bottom = value;
const EdgeInsets.symmetric({
double vertical = 0.0,
double horizontal = 0.0,
}) : left = horizontal,
top = vertical,
right = horizontal,
bottom = vertical;
EdgeInsets.fromWindowPadding(ui.WindowPadding padding, double devicePixelRatio)
: left = padding.left / devicePixelRatio,
top = padding.top / devicePixelRatio,
right = padding.right / devicePixelRatio,
bottom = padding.bottom / devicePixelRatio;
/// An [EdgeInsets] with zero offsets in each direction.
static const EdgeInsets zero = EdgeInsets.only();
static EdgeInsets lerp(EdgeInsets a, EdgeInsets b, double t)
EdgeInsetsDirectional
const EdgeInsetsDirectional.fromSTEB(this.start, this.top, this.end, this.bottom);
const EdgeInsetsDirectional.only({
this.start = 0.0,
this.top = 0.0,
this.end = 0.0,
this.bottom = 0.0,
});
static const EdgeInsetsDirectional zero = EdgeInsetsDirectional.only();
- Decoration decoration
设置容器的背景的,和color属性冲突,二选一
几个实现类
3.1. BoxDecoration 矩形的背景
const BoxDecoration({
this.color,//背景色
this.image,//DecorationImage 提供一张图片,下边有构造方法,可以看下有啥参数
this.border,//BoxBorder,上边有见过,有2个子类,画边框线条的,可以设置颜色,线条宽度
this.borderRadius,//下边的shape为矩形的时候才需要,如果为圆形的话这个不可以设置。
this.boxShadow,//背景的阴影设置
this.gradient,//设置背景的渐变色,这个设置了,上边的color就无效了
this.backgroundBlendMode,//
this.shape = BoxShape.rectangle,//就两种,矩形或者圆形
})
//DecorationImage 提供一张图片
const DecorationImage({
@required this.image,//提供一张图片
this.colorFilter,
this.fit,
this.alignment = Alignment.center,
this.centerSlice,
this.repeat = ImageRepeat.noRepeat,
this.matchTextDirection = false,
})
image:ImageProvider
常用的2种实现,应用内图片和网络图片
AssetImage("images/file.png")
NetworkImage("url....")
List<BoxShadow> boxShadow
设置阴影
BoxShadow:可以看到有3个参数,和android一样,颜色,偏移量,模糊半径
对比一下效果图,可以看到集合是按照顺序一层一层画的,所以集合前边的偏移量应该大,后边的偏移量应该小,否则后边的就把前边的盖住了
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.amber,
offset: Offset(9, 9),
),
BoxShadow(
color: Colors.indigoAccent,
offset: Offset(5, 5),
blurRadius: 1),
BoxShadow(
color: Colors.cyan, offset: Offset(2, 2), blurRadius: 2),
],
Gradient
几个子类如下
This is an interface that allows [LinearGradient], [RadialGradient], and [SweepGradient]
上图就是线性的效果,下边是代码,从左顶点到右下顶点
stops:数组长度要和colors一样,从0到1,其实就是标示颜色的分布位置,如果第一个stops不是0,那就会弄个0,并用colors的第一个颜色作为起点,如果stops的最后一个不是1,那么就会用1和colors的最后一个颜色作为终点
tileMode:重复模式3种和android一个样,repeat就是重复,mirror就是镜像,还有个clamp拉伸最后一个色值
gradient: LinearGradient(
colors: <Color>[
Colors.indigoAccent,
Colors.purple,
Colors.deepOrangeAccent
],
stops: <double>[
0,
0.8,
1
],
tileMode: TileMode.repeated,
begin: Alignment.topLeft,
end: Alignment.bottomRight),
RadialGradient
一圈一圈的
radius :一个分数,以容器的宽高里小的那个做参考,颜色1.0的位置,
const RadialGradient({
this.center = Alignment.center,
this.radius = 0.5,
@required List<Color> colors,
List<double> stops,
this.tileMode = TileMode.clamp,
this.focal,
this.focalRadius = 0.0,
})
下图是tilemode为repeat的效果图,radius默认是0.5,刚好是宽度的一半
SweepGradient
射线的效果,向外发散
const SweepGradient({
this.center = Alignment.center,
this.startAngle = 0.0,
this.endAngle = math.pi * 2,
@required List<Color> colors,
List<double> stops,
this.tileMode = TileMode.clamp,
})
如下图 repeat模式,startAngle为0,end为PI的效果,可以看到0到180度颜色渐变,180到360repeat
colorFilter
染色,这里是给image染色,主要是看blendMode,根据这个,效果不同
colorFilter: ColorFilter.mode(color, BlendMode.srcOut)
####### BlendMode
这个百度搜一下,有效果图,
简单举例说下,比如这里用到的,
file.png图片就是destination,colorFilter里的颜色,就是source
DecorationImage(
image: AssetImage('images/file.png'),
colorFilter: ColorFilter.mode(Colors.deepOrangeAccent, BlendMode.dst))),
dst: 只保留destination,source被丢弃, 效果就是显示file图片,染色无效
src:和dst相反,这个值保留source,destination被丢弃,上边代码的效果就是就剩个颜色了
srcOver:先画destination,再画source,都保留,上边代码的情况,如果source这个颜色没有透明度,下边file图片就看不见了,这个属性是默认值
dstOver:同上,不过这次先画source,再画destination
srcIn: 两者取交集,就是下图这种,周围是空白的,到时候中间的树叶就可以染色了
dstIn:和上边一样,不过这个destination图片在上层,而source在下层,虽然和上边都是取交集,不过最终效果和原图没太大变化,颜色在图片下边了,被挡住了。
srcOut:显示的是rouce,抠除和destination相交的部分,树叶效果就是树叶成透明的了,其他部分有颜色
dstOut:显示的是destination,抠除和source相交的部分,比如上边的树叶,相交部分是树叶,抠除以后就成树叶外边的空白了,所以撒都不显示了
其他的不写了,需要再研究,看多了头疼。
3.2 ShapeDecoration
比boxDecoration可以有更多的形状,其他的参数意思差不多
const ShapeDecoration({
this.color,
this.image,
this.gradient,
this.shadows,
@required this.shape,//上边有讲过ShapeBorder的子类,
})
3.3 FlutterLogoDecoration
看名字,就是使用了app的logo图片当背景了
FlutterLogoStyle:markOnly 只显示logo,后两种还显示label,一个水平显示一个垂直显示而已
const FlutterLogoDecoration({
this.lightColor = const Color(0xFF42A5F5), // Colors.blue[400]
this.darkColor = const Color(0xFF0D47A1), // Colors.blue[900]
this.textColor = const Color(0xFF616161),
this.style = FlutterLogoStyle.markOnly,
this.margin = EdgeInsets.zero,
})
enum FlutterLogoStyle {
/// Show only Flutter's logo, not the "Flutter" label.
///
/// This is the default behavior for [FlutterLogoDecoration] objects.
markOnly,
/// Show Flutter's logo on the left, and the "Flutter" label to its right.
horizontal,
/// Show Flutter's logo above the "Flutter" label.
stacked,
}
3.4 UnderlineTabIndicator
顾名思义,就是下边画条线,看构造方法,默认是条白线,
const UnderlineTabIndicator({
this.borderSide = const BorderSide(width: 2.0, color: Colors.white),
this.insets = EdgeInsets.zero,
})
改了下颜色
Image
image
这个比较坑了,如下图有5种构造方法,其实就是5种获取图片文件的方法
除了network简单,传个路径完事,其他几个都不简单,只好百度搜下,
image File获取
- asset
在工程目录下新建个文件夹,名字随便起,然后里边放上图片,其实文件夹你建在哪都行,你路径写对就可以
然后打开pubspec.yaml文件,如下图,在flutter标签下添加assets标签
【前边有个空格,反正就是和上边的uses-material-design对齐】
然后下边横杠开头 加上图片的路径,相对当前文件的相对路径,
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
assets:
- images/ 整个images目录下的图片,可以这样写,斜杠结尾即可
- images/set_select.png
- android/pic/file.png
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
代码里使用就简单了,把路径放进来即可
Image.asset('android/pic/file.png',),
上边的assets写的不太好,如果你images目录下多张图片,总不能一个一个的写进去吧,写个文件夹目录就行了,带斜杠
assets:
- images/
File
使用的时候必须先导入库
import 'dart:io';
然后File的路径咋办,首先得拿到sdcard的目录吧,这里借用第三方库
path_provider
把库添加到pubspec.yaml文件下
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
english_words: ^3.1.0
path_provider: ^1.1.0
然后dart文件里导入
import 'package:path_provider/path_provider.dart';
之后就可以用了
class LocalImage extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return LocalState();
}
}
class LocalState extends State<LocalImage>{
@override
Widget build(BuildContext context) {
_getLocalFile();
return Image.file(File("$_storageDir/Dollar.png"),width: 50,height: 50);
}
String _storageDir = '';
_getLocalFile() async {
String appDir = (await getApplicationDocumentsDirectory()).path;
// String supportDir=(await getApplicationSupportDirectory()).path;//这个是ios用的
String tempDir = (await getTemporaryDirectory()).path;
String storageDir = (await getExternalStorageDirectory()).path;
print('sd path=====$appDir=========$tempDir====$storageDir');
setState(() {
_storageDir = storageDir;
});
return storageDir;
}
}
打印路径如下
/data/user/0/com.charlie.flutter_first_app/app_flutter
/data/user/0/com.charlie.flutter_first_app/cache
/storage/emulated/0
TabBar
类似android里的TabLayout
TabBar(
labelColor: Colors.red,
controller: TabController(initialIndex: 0,length: choices.length, vsync: TestVSync() ),
onTap: (index){
print('bottom onTap===${index}');
},
isScrollable: false,
indicatorColor: Colors.purple,
tabs: choices.map((Choice choice) {
return new Tab(
text: choice.title,
icon: Icon(choice.icon),
);
}).toList()),
看下有用的属性
const TabBar({
Key key,
@required this.tabs,//一个集合widget,这里一般用Tab
this.controller,//控制器,有3个参数,初始化选中哪个,tabbar的长度,TickerProvider:用那个系统的TestVSync即可
this.isScrollable = false,//可滚动的tab大小是wrap的,不可滚动tab是平分整个tabbar宽度的
this.indicatorColor,//游标颜色
this.indicatorWeight = 2.0,//游标的高度
this.indicatorPadding = EdgeInsets.zero,//默认游标是在最下边的
this.indicator,//游标除了默认的线条,这里可以自定义一个Decoration
this.indicatorSize,//TabBarIndicatorSize枚举类型,2种,一种是整个tab的大小一样,一种是和tab里的lable大小一样
this.labelColor,//图片文字的颜色
this.labelStyle,
this.labelPadding,
this.unselectedLabelColor,//可以单独设置未选中的颜色,否则就是用上边labelColor变淡一点
this.unselectedLabelStyle,
this.dragStartBehavior = DragStartBehavior.start,//没看懂,以后知道了再修改
this.onTap,//没个tab点击的时候回调,回调里传的就是索引
})
自定义游标外观:indicator
有以下几种,默认的就是最后昂UnderlineTabIndicator
- 矩形
indicator: BoxDecoration(shape: BoxShape.rectangle,border: Border.all(color: Colors.cyan),color: Colors.amberAccent),
- 圆形
indicator: BoxDecoration(shape: BoxShape.circle,border: Border.all(color: Colors.cyan),color: Colors.amberAccent),
- FlutterLogo 这个估计没人用,
indicator: FlutterLogoDecoration()
- ShapeDecoration
indicator: ShapeDecoration(shape: Border.all(color: Colors.deepOrange,)+Border.all(color: Colors.lightGreenAccent),
image: DecorationImage(image: AssetImage("images/l.png")))
//上边的box也可以加张图片的
indicator: BoxDecoration(image: DecorationImage(image: AssetImage("images/l.png"))),
相关的类
TabPageSelector(
controller: TabController(
initialIndex: selectIndex,
length: choices.length,
vsync: TestVSync()),
)),
属性说明
const TabPageSelector({
Key key,
this.controller,
this.indicatorSize = 12.0,
this.color,//这个是中间空心的颜色,默认不设置就是透明的
this.selectedColor,//这个是选中的那个空心的颜色,以及圈圈外圈的颜色
})
TabBarView
和android里的viewpager差不多,要和上边的tabbar互动,两者用同一个controller就可以。
Container(
height: 450,
child: TabBarView(
children: <Widget>[
Container(
width: 300,
height: 400,
child: Text('first view'),
color: Colors.deepOrange,
),
Container(
width: 400,
height: 400,
color: Colors.blue,
child: FlutterLogo(
size: 200,
),
),
Container(
width: 400,
height: 300,
color: Colors.pinkAccent,
child: Icon(Icons.share),
)
],
controller:
TabController(initialIndex: index, length: 3, vsync: xxxP()),
),
physics: AlwaysScrollableScrollPhysics()
)
//physics
NeverScrollableScrollPhysics:没法手动滑动了
AlwaysScrollableScrollPhysics:可以滑动
BouncingScrollPhysics:IOS的效果,回弹,android貌似没效果
ClampingScrollPhysics:没看出和always有啥太大的区别
Material Components Widgets
return new MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(leading: BackButton(),title: Text("first page"),centerTitle: true,),
body: lv,
// floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
// floatingActionButton: FloatingActionButton(onPressed: (){},child: Icon(Icons.add),),
// floatingActionButtonAnimator: FloatingActionButtonAnimator.scaling,
),
);
- FloatingActionButton
FloatingActionButton
floatingActionButton: FloatingActionButton(
onPressed: null,
child: Icon(Icons.close),
backgroundColor: Colors.red,
),
另外一种带文字的
floatingActionButton: FloatingActionButton.extended(
onPressed: null,
icon: Icon(Icons.close),
label: Text("close"),
backgroundColor: Colors.red,
),
完事相关的还有这个按钮的位置设置,默认是在右下角的
floatingActionButtonLocation: FloatingActionButtonLocation.startTop
miniStartTop:和startTop基本一样,感觉左边的间距稍微小了那么一点点而已
startTop 是左上角,中心在appbar的底部
endTop 是在右上角,和startTop一个右边,一个左边
centerFloat :是在底部中间的位置,在bottomNavigationBar上边【如果有设置的话】
endFloat:同上,不过是在右边
endDocked:下图效果,中心线在bottom的顶部
centerDocked:同理,居中而已
- PopupMenuButton
PopupMenuButton
其实就是个按钮,点击以后弹出一个弹框里边是列表可以选
看下参数
child和icon最多只能设置一个,替换默认的省略号图标,如果这2个你都设置,那代码就挂了。
const PopupMenuButton({
Key key,
@required this.itemBuilder, //这玩意返回一个PopupMenuEntry类型的集合
this.initialValue,
this.onSelected,//选中哪个function里返回的就是那个对象
this.onCanceled,//没有选择,点击外部消失
this.tooltip,
this.elevation = 8.0,
this.padding = const EdgeInsets.all(8.0),
this.child,//child和icon最多只能设置一个,都不设置的话默认有个三个点的图片
this.icon,
this.offset = Offset.zero,
})
demo如下
首先这玩意需要一个泛型声明数据的类型
// This is the type used by the popup menu below.
enum WhyFarther { harder, smarter, selfStarter, tradingCharter }
// This menu button widget updates a _selection field (of type WhyFarther,
// not shown here).
PopupMenuButton<WhyFarther>(
onSelected: (WhyFarther result) { setState(() { _selection = result; }); },
itemBuilder: (BuildContext context) => <PopupMenuEntry<WhyFarther>>[
const PopupMenuItem<WhyFarther>(
value: WhyFarther.harder,
child: Text('Working a lot harder'),
),
const PopupMenuItem<WhyFarther>(
value: WhyFarther.smarter,
child: Text('Being a lot smarter'),
),
const PopupMenuItem<WhyFarther>(
value: WhyFarther.selfStarter,
child: Text('Being a self-starter'),
),
const PopupMenuItem<WhyFarther>(
value: WhyFarther.tradingCharter,
child: Text('Placed in charge of trading charter'),
),
],
)
常见的PopupMenuEntry
PopupMenuItem
PopupMenuDivider:就是一条线,如下图
CheckedPopupMenuItem:有个checked状态,前边多个对号,child 用的flatbutton,可以看到用的背景色是disable颜色
实例体验
整体分割成4部分:图片,中间的文字,3个按钮文字,最后边的内容
用listview来添加这些view
- 图片
复习下,工程里图片的添加过程
工程根目录下新建个文件夹xxx,完事把图片放进去
然后打开pubspec.yaml文件
打开fultter标签下的assets标签, 添加 - xxx/ 表示xxx目录下的所有图片
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
assets:
- images/
- android/pic/file.png
看下image.asset的一些常用属性
Image.asset(
String name, {//图片地址
Key key,
AssetBundle bundle,
this.semanticLabel,//这个就是注释文字了,类似android里的description吧
this.excludeFromSemantics = false,
double scale,
this.width,//宽高
this.height,
this.color,//染色的颜色
this.colorBlendMode,//染色的模式
this.fit,//图片的展示方式,拉伸,裁剪之类的,下边会有效果
this.alignment = Alignment.center,//默认居中的,可以修改
this.repeat = ImageRepeat.noRepeat,//如果图片很小的话,可以repeat
this.centerSlice,
this.matchTextDirection = false,
this.gaplessPlayback = false,
String package,
this.filterQuality = FilterQuality.low,
})
看下repeat的效果
Image.asset(
'images/imager.png',
height: 240,
fit: BoxFit.none,
repeat: ImageRepeat.repeatX,
),
fit模式是none,所以图片大小就是红框的部分,
然后repeat用的repeatX,所有两边又有了部分重复的图片,因为这里图片很大,高度已经铺满了,如果高度没铺满是下图这种。
下边2种效果图alignment 都 是默认的center,你可以改成left,这样重复就都是右边,下边了,类似地图那种没加载出来的小方框效果
4种repeat很好理解,单方向repeat,上下左右重复,不重复
enum ImageRepeat {
/// Repeat the image in both the x and y directions until the box is filled.
repeat,
/// Repeat the image in the x direction until the box is filled horizontally.
repeatX,
/// Repeat the image in the y direction until the box is filled vertically.
repeatY,
/// Leave uncovered portions of the box transparent.
noRepeat,
}
-
Container
如果要margin或者padding,那么就需要这个容器包裹了
- Expanded
有个flex,类似线性布局里的weight,里边包裹一个child
const Expanded({
Key key,
int flex = 1,
@required Widget child,
})
- crossAxisAlignment
内容的布局位置,和gravity有点类似。
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
enum CrossAxisAlignment {
/// Place the children with their start edge aligned with the start side of
/// the cross axis.
///
/// For example, in a column (a flex with a vertical axis) whose
/// [TextDirection] is [TextDirection.ltr], this aligns the left edge of the
/// children along the left edge of the column.
///
/// If this value is used in a horizontal direction, a [TextDirection] must be
/// available to determine if the start is the left or the right.
///
/// If this value is used in a vertical direction, a [VerticalDirection] must be
/// available to determine if the start is the top or the bottom.
start,
/// Place the children as close to the end of the cross axis as possible.
///
/// For example, in a column (a flex with a vertical axis) whose
/// [TextDirection] is [TextDirection.ltr], this aligns the right edge of the
/// children along the right edge of the column.
///
/// If this value is used in a horizontal direction, a [TextDirection] must be
/// available to determine if the end is the left or the right.
///
/// If this value is used in a vertical direction, a [VerticalDirection] must be
/// available to determine if the end is the top or the bottom.
end,
/// Place the children so that their centers align with the middle of the
/// cross axis.
///
/// This is the default cross-axis alignment.
center,
/// Require the children to fill the cross axis.
///
/// This causes the constraints passed to the children to be tight in the
/// cross axis.
stretch,
/// Place the children along the cross axis such that their baselines match.
///
/// If the main axis is vertical, then this value is treated like [start]
/// (since baselines are always horizontal).
baseline,
}
- mainAxisAlignment
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
buildButtonColumn(context, Icons.call, "Call"),
buildButtonColumn(context, Icons.near_me, "Location"),
buildButtonColumn(context, Icons.share, "Share"),
],
);
看下枚举的说明
enum MainAxisAlignment {
/// Place the children as close to the start of the main axis as possible.
///
/// If this value is used in a horizontal direction, a [TextDirection] must be
/// available to determine if the start is the left or the right.
///
/// If this value is used in a vertical direction, a [VerticalDirection] must be
/// available to determine if the start is the top or the bottom.
start,
/// Place the children as close to the end of the main axis as possible.
///
/// If this value is used in a horizontal direction, a [TextDirection] must be
/// available to determine if the end is the left or the right.
///
/// If this value is used in a vertical direction, a [VerticalDirection] must be
/// available to determine if the end is the top or the bottom.
end,
/// Place the children as close to the middle of the main axis as possible.
center,
/// Place the free space evenly between the children.
spaceBetween,
/// Place the free space evenly between the children as well as half of that
/// space before and after the first and last child.
spaceAround,
/// Place the free space evenly between the children as well as before and
/// after the first and last child.
spaceEvenly,
}
MainAxisAlignment.spaceBetween
child之间平分空白
MainAxisAlignment.spaceEvenly
两边和中间,一起平分空白部分,如下图,间隔一样
MainAxisAlignment.spaceAround
每个child左右的space都一样,
start,end,center
下图是start的效果,end,center类似,child是挨着的,居中或者左右
知识
一般来说, app没有使用Scaffold【materila组件】的话,会有一个黑色的背景和一个默认为黑色的文本颜色
所以啊,还是使用scaffold作为根组件比较好
否则,自己修改背景
Container(
decoration: new BoxDecoration(color: Colors.white)
犯了个致命的错误,害我检查半天不知道咋回事,onPressed后边的方法,就是个方法名字,我不小心加了个括号,它也不提示,我说咋点击没反应,因为onPress后边跟的是个无参无返回的方法
onPressed: _loveClick
页面里添加个组件,想限制下宽高,然后就套个container,设置宽比如100,结果发现无效,宽还是铺满屏幕的,那就搜下咋解决吧
container 宽高无效的问题
按照作者的,container外边再套个column或者row就ok拉,
其他一下系统的show方法
- showAboutDialog
showAboutDialog(
context: context,
applicationName: "app name",
applicationVersion: "app version",
applicationIcon: Icon(Icons.directions_car),
applicationLegalese: "legaless..",
children: <Widget>[
Text("text1"),
Text("text2"),
Text("text3"),
]);
- showTimePicker
弹个时间选择器
Future<TimeOfDay> showTimePicker()方法返回的是一个Future
var re=showTimePicker(context: context, initialTime: TimeOfDay(hour: 20, minute: 20));
re.then((TimeOfDay time){
print('then====$time');//点击cancel的话这里返回null,点击ok返回的是对应的时间
});
re.whenComplete((){
});
- showDatePicker
日期选择器
var rrr = showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now().subtract(Duration(seconds: 1)),//最小时间得比初始化时间小或者等于
lastDate: DateTime(DateTime.now().year, 8, 10));
rrr.then((DateTime date) {
print('date====$date');//点击cancel,返回date为null
}).whenComplete(() {
print('complete=====');
});
DateTime的几个方法
DateTime.now() //当前时间
.subtract(Duration(seconds: 1)//减去多少时间
.add(Duration(seconds: 1)//加上多少时间
- showGeneralDialog
自定义的对话框,下边这些值好像都必须写,要不就会异常或者不显示
barrierDismissible:点击外部是否消失
barrierLabel:类似android里的tab吧,不知道,反正不设置就会异常
barrierColor:弹框外围的颜色,默认是透明的
transitionDuration:对话框显示或者隐藏的动画时间
pageBuilder:返回一个widget用来显示
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: "lable....",
barrierColor: Colors.greenAccent,
transitionDuration: Duration(seconds: 1),
pageBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 200,
height: 200,
child: Text("content....",style: TextStyle(color: Colors.pinkAccent,),),
),
FlatButton.icon(onPressed: (){
Navigator.of(context, rootNavigator: true).pop("ok");
}, icon: Icon(Icons.looks), label:Text("ok")),
],
);
},
).then(
(value) {
print('then=====$value');//没有值的话返回的就是null
},
).whenComplete(() {
print('complete=========');
});
其实对话框都用的push,所以我们要返回数据或者隐藏对话框,就对应的pop就行了
Future<T> showGeneralDialog<T>({
//.....
return Navigator.of(context, rootNavigator: true).push<T>
隐藏对话框
可以pop一个对象回去,在then方法里就能接收到
Navigator.of(context, rootNavigator: true).pop("ok");
- showMenu
下拉菜单选择
showMenu(context: context, position: RelativeRect.fromLTRB(100, 200, 50, 50), items: choices.map((choice) {
return CheckedPopupMenuItem<Choice>(
child: FlatButton.icon(onPressed: null, icon: Icon(choice.icon), label:Text(choice.title),color: Colors.pinkAccent,disabledColor: Colors.purpleAccent,),
value: choice,
checked: choice==selectChoice,
);}
).toList()
,initialValue: selectChoice).then((choice){
print('then====$choice');
}).whenComplete((){
print('complete==========');
});
拿到一个集合或者说数组,你想同时获取索引和对应的值,可以如下操作。
choices.asMap().map((key,value){
});
- showDialog
和showGeneralDialog的区别,这个参数比较少,就个context,以及一个child或者builder返回一个child,默认dismiss为ture,点击外部消失
外层套个column是为了里用mainAxisAlignment 使内容居中显示,加上一些padding,margin,写起来真费劲,flutter控件本身没有margin,padding一说,要设置就得套个container,累的一比啊
showDialog(
context: context,
builder: (BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
width: 200,
height: 100,
child: Card(
color: Colors.deepOrange,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: BorderSide(color: Colors.blue)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("dialog content"),
MaterialButton(
onPressed: () {
Navigator.of(context, rootNavigator: true)
.pop("click ok");
},
child: Text("ok"),
)
],
),
),
)
],
);
}).then((value) {
print('then====$value');
}).whenComplete(() {
print('complete======');
});
- showBottomSheet
比android里的差很多,唯一相同的就是控件是从底部弹出来的,从底部消失。
这个顶部最大就在appbar下边,如果widget很小,andoid里点击外部是可以消失的,这里貌似不行。
下边代码就是直接弹出一个默认的页面
showBottomSheet(context: context, builder: (BuildContext context){
return
MyApp();
});
- showModalBottomSheet
这个弹出的高度只有父容器高度的一半,上半部分有变暗,点击可以消失。
showModalBottomSheet(context: context, builder: (BuildContext context){
return MyApp();
}).then((value){
print('then====$value');
});
- showSearch
会跳到一个搜索页面,系统写好的
Future<T> showSearch<T>({
@required BuildContext context,
@required SearchDelegate<T> delegate,//抽象类,需要自己实现4个抽象方法
String query = '',//默认的搜索文字,可以没有
})
效果
默认键盘是弹出来的,点击键盘上的search,就会进行查找操作了
下边是个实例
buildActions方法:返回appbar 右边的操作按钮,也可以是文字
buildLeading方法:就是那个后退箭头,你可以自己设置
buildResults方法:这里返回的是结果widget,query就是当前要查找的字段
buildSuggestions方法:提示信息,上边的search获取焦点的时候会显示这个
而且这个方法返回的是个Future,所以Future的几个方法都可以用,then可以获取到MySearch页面回调的结果数据
showSearch(context: context, delegate: MySearch(),).then((value){
print('then====$value');
}).whenComplete((){
//实现类
class MySearch extends SearchDelegate {
@override
List<Widget> buildActions(BuildContext context) {
return <Widget>[
IconButton(icon: Icon(Icons.access_alarm), onPressed: () {}),
IconButton(icon: Icon(Icons.forward), onPressed: () {})
];
}
@override
Widget buildLeading(BuildContext context) {
return BackButton();
}
@override
Widget buildResults(BuildContext context) {
print('result====${query}');
return ListView(
children: choices.map((choice) {
return GestureDetector(
child: Container(
color: Colors.transparent,
child: Column(
children: <Widget>[
FlatButton.icon(onPressed: null, icon: Icon(choice.icon), label: Text(choice.title)),
Divider(height: 2,color: Colors.grey,),
],
),
),
onTap: (){
Navigator.maybePop(context);
//如果要传递数据,可以使用 Navigator.of(context).pop(choice);
},
);
}).toList(),
);
}
@override
Widget buildSuggestions(BuildContext context) {
return Text("suggestions........");
}
//非必须的方法
//默认的search bar的主题是白色的,重写下边的方法,可以修改主题里的属性,
@override
ThemeData appBarTheme(BuildContext context) {
return super.appBarTheme(context).copyWith(primaryColor: Colors.blue);
}
}
进一步研究
看源码,默认的实现主题色是白色的,所以我们看到标题栏是白色的,这个是可以修改的
ThemeData appBarTheme(BuildContext context) {
assert(context != null);
final ThemeData theme = Theme.of(context);
assert(theme != null);
return theme.copyWith(
primaryColor: Colors.white,
primaryIconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
primaryColorBrightness: Brightness.light,
primaryTextTheme: theme.textTheme,
);
}
可以用到的方法
query 这个可以set,也可以get
void showResults(BuildContext context)//可以手动切换显示结果页还是提示页
void showSuggestions(BuildContext context)
void close(BuildContext context, T result)//关闭search页并返回数据,上边的Navigator方法可以改成这个
animation
animations tutorial
animation widget
进行动画,需要两个条件,一个就是可以动画的widget,以及animation,上边的地址
widget里列出了系统写好的一些,放大缩小,透明度变化,旋转等等,都是继承AnimatedWidget
你也可以自己写,可以实现多种变化,比如放大缩小和旋转一起进行
tutorial 告诉你咋把动画和widget关联起来
比如放大缩小的,用系统提供的ScaleTransition, 里边有个scale参数,就是需要的动画了
new ScaleTransition(
scale: curve,
alignment: Alignment.center,
child: new FlutterLogo(
size: 100.0,
))),
动画的代码
AnimationController:动画控制
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),);
curve = CurvedAnimation(parent: controller, curve: Curves.elasticOut);
curve.addListener(() {
setState(() {});//刷新ui
});
curve.addStatusListener((status) {
//状态监听,有4种
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
vsync:this 这个是个TickerProvider 可以自己实现这个抽象类
@override
Ticker createTicker(TickerCallback onTick) {
return Ticker(onTick);
}
也可以用系统写好的
SingleTickerProviderStateMixin,,TickerProviderStateMixin
class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin
因为widget比如ScaleTransition, 里边有个scale也就是上边的curvedAnimation,所以value是这个animation决定的。
取值范围在0和1之间,用系统提供的AnimationController默认lower是0,upper是1,以及CurvedAnimation 取值也是0到1之间的,具体的由Curves这个类引用的变量决定的,点进去可以看到value咋算的
如果取值不是0到1.可以用
class Tween<T extends dynamic> extends Animatable<T>
构造方法里传个begin和end即可
Tween({ this.begin, this.end });
我们可以看下它value的算法,那个t的值就是上边AnimationController的值,默认是0到1,如果你不改的话,那么可以看到这个tween的值也就是从begin到end拉。
T transform(double t) {
if (t == 0.0)
return begin;
if (t == 1.0)
return end;
return lerp(t);
}
T lerp(double t) {
return begin + (end - begin) * t;
}
常见的AnimatedWidget
初始化的animation
class TempWidgetState extends State<TempWidget> with TickerProviderStateMixin {
Animation decoration;
AnimationController controller;
Animation<double> doubleA;
Animation<Color> modalBarrier;
@override
void initState() {
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
);
doubleA = Tween<double>(begin: 0, end: 1).animate(controller);
doubleA.addListener((){
setState(() {
});
});
controller.repeat(reverse: true);//可以点击某个按钮再开启动画
super.initState();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
- RotationTransition
可以看到拿到value 乘以2π,也就是从0到1就是0到360度
const RotationTransition({
Key key,
@required Animation<double> turns,
this.alignment = Alignment.center,
this.child,
})
//
final double turnsValue = turns.value;
final Matrix4 transform = Matrix4.rotationZ(turnsValue * math.pi * 2.0);
如下
RotationTransition(turns: doubleA,child: FlutterLogo(size: 150,),),
- ScaleTransition
const ScaleTransition({
Key key,
@required Animation<double> scale,
this.alignment = Alignment.center,
this.child,
})
//
final double scaleValue = scale.value;
final Matrix4 transform = Matrix4.identity()
..scale(scaleValue, scaleValue, 1.0);
如下
ScaleTransition(scale: doubleA,child: FlutterLogo(size: 100,),alignment: Alignment.topLeft,),
- SizeTransition
咋说了,很像一个画轴的展开过程,axis 默认垂直方向,还可以水平方向
axisAlignment :这个值从-1到1比较合适,当然也可以比1大
说下效果,主要就是展开以后显示的控件从哪里开始,
0的话,从控件正中心往两边展开,
-1的话,从控件start或者top位置展开
1的话,从控件的end或者bottom位置展开,下边给几张图看下
const SizeTransition({
Key key,
this.axis = Axis.vertical,//展开的方向,水平或者垂直
@required Animation<double> sizeFactor,
this.axisAlignment = 0.0,
this.child,
})
//
Widget build(BuildContext context) {
AlignmentDirectional alignment;
if (axis == Axis.vertical)
alignment = AlignmentDirectional(-1.0, axisAlignment);
else
alignment = AlignmentDirectional(axisAlignment, -1.0);
return ClipRect(
child: Align(
alignment: alignment,
heightFactor: axis == Axis.vertical ? math.max(sizeFactor.value, 0.0) : null,
widthFactor: axis == Axis.horizontal ? math.max(sizeFactor.value, 0.0) : null,
child: child,
),
);
}
- SlideTransition
上下左右滑动child,offset里的x和y分别对应水平,垂直方向的,0是初始位置,1表示移动child宽或高的一倍距离。
const SlideTransition({
Key key,
@required Animation<Offset> position,
this.transformHitTests = true,
this.textDirection,
this.child,
})
Widget build(BuildContext context) {
Offset offset = position.value;
if (textDirection == TextDirection.rtl)
offset = Offset(-offset.dx, offset.dy);
return FractionalTranslation(
translation: offset,
transformHitTests: transformHitTests,
child: child,
);
}
使用这样
Offset(0, 0) 参数dx和dy是百分比,和child的大小来说的,dx:0表示原始位置,1表示移动往右移动容器宽度的一倍距离。-1就是左移了,LTR模式。上下移动的话修改第二个参数dy。
child: new SlideTransition(
position: Tween<Offset>(begin: Offset(0, 0),end: Offset(1, 0)).animate(controller),
child: new FlutterLogo(
size: 100.0,
))),
- PositionedTransition
Positioned widgets must be placed directly inside Stack widgets.
const PositionedTransition({
Key key,
@required Animation<RelativeRect> rect,
@required this.child,
})
这玩意比较特殊,必须放在一个Stack控件里,否则就挂了
new Container(
height: 300,
width: 500,
child: Stack(
children: <Widget>[
new PositionedTransition(
rect: rectAnim,
child: new FlutterLogo(
size: 100.0,
))
],
)),
我们简单写个rect,其实就是提供四个顶点的坐标,相对stack容器而言
rectAnim=Tween<RelativeRect>(begin: RelativeRect.fromLTRB(0, 0, 100, 100),end: RelativeRect.fromLTRB(200, 0, 400, 200)).animate(controller)
- FadeTransition
透明度变化的控件
final Animation<double> opacity;
const FadeTransition({
Key key,
@required this.opacity,
this.alwaysIncludeSemantics = false,
Widget child,
})
- DecoratedBoxTransition
看名字,就是两种Decoration的渐变,和AnimatedContainer很类似,那个是动态修改里边的属性,这个是一次提供两个,我给你圆滑过渡。
position: DecorationPosition.background //第三个参数意思是这个decoration是在上层展示还是下层展示,
上层的意思就是画在child上边
这里的decoration变化,并不影响child的大小,比如decoration变成圆了,child就会显示在圆外边。
//final Animation<Decoration> decoration;
DecoratedBoxTransition(decoration: decoration, child: FlutterLogo(size: 150,)),
decoration如下
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
);
decoration = DecorationTween(
begin: BoxDecoration(
color: Colors.purple,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: Colors.black,
width: 5.0,
)),
end: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(55),
border: Border.all(
color: Colors.black,
width: 5.0,
))).animate(controller);
------------------上边7个是一类,都是需要一个Animation,根据Animation的value变化自动修改属性-----
- Hero
使用起来也很简单,在两个跳转的页面,要进行动画的widget外边套一层Hero,加个一样的tag就行,tag是个object,啥都行,一样就行。
Hero(tag: 'xxx', child: Image.asset(
"images/imager.png",
width: 100,
height: 100,
))
因为这个hero是页面跳转用到的转场动画,所以先学下页面跳转
和android不同,android的页面都是activity,而flutter里一个页面就是个widget,所谓的跳转页面,就是另外一个widget.
跳转页面用的是Navigator这个类,用如下的方法
Navigator.push(
context, MaterialPageRoute(builder: (context) => gl));
不过这context还是有讲究的,不是所有的context都可以用,这不,下边就出错了。
Navigator operation requested with a context that does not include a Navigator.
The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.
错误代码是这样的,Navigator事件是写在State类里的,这个context不符合要求,
不过最终用到的widget都是放在Scaffold里的,这个控件是满足要求的。
所以解决办法,自定义一个widget,然后Navigator事件写在自定义的里边,这时候这个widget拿到的context是Scaffold里的,复合要求的
void main() => runApp(MyApp());
class MyApp extends StatefulWidget
class MyAppState extends State<MyApp>
return new MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.blue,
),
home: Scaffold(
body: lv,
),
);
其他一些push方法的解释
navigator说明
------下边是一类,是widget的某些属性变化的时候,有一个渐变的过程----
- AnimatedContainer
它的作用是当里边的属性发生改变的时候,有一个渐变的过程
比如宽高,颜色,margin,padding,位置等
AnimatedContainer({
Key key,
this.alignment,
this.padding,
Color color,
Decoration decoration,
this.foregroundDecoration,
double width,
double height,
BoxConstraints constraints,
this.margin,
this.transform,
this.child,
Curve curve = Curves.linear,
@required Duration duration,
})
如下,简单的改变大小和颜色
初始有个值,当点击某个按钮以后修改这些值,然后setState以后,这个容器会在duration时间里从老的变化成新的。
AnimatedContainer(
duration: Duration(seconds: 2),
child: new FlutterLogo(
size: 100,
),
color: bgColor,
width: bgW,
height: bgH,
),
- AnimatedCrossFade
里边有2个child,交替变淡,根据crossFadeState反正只显示一个,所以,我们动态修改这个值就可以在这两个child之间渐变了。
const AnimatedCrossFade({
Key key,
@required this.firstChild,
@required this.secondChild,
this.firstCurve = Curves.linear,
this.secondCurve = Curves.linear,
this.sizeCurve = Curves.linear,
this.alignment = Alignment.topCenter,
@required this.crossFadeState,
@required this.duration,
this.layoutBuilder = defaultLayoutBuilder,
})
- AnimatedBuilder
简单点讲,就是你提供的animation的value发生变化的时候,它会自动调用build重建,把child当参数给你,你可以用,可以不用。
和AnimatedWidget差不多,这个也是当提供的Listenable发生改变的时候自动rebuild该widget。
animation就是之前用到的AnimationController ,CurvedAnimation ,继承Animation就行
build是一个2个参数的方法,返回一个widget,我们这里返回一个Transform,方便动画,比如下边的rotate,我们动态修改angle即可。
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Transform.rotate(
angle: controller.value*2*3.1415926,
child: child,
);
},
child: Image.asset(
"images/imager.png",
width: 100,
height: 100,
),
)
当然也可以这样用,反正就是animation的value改变的话,builder就会执行,至于想返回啥,随便由你决定。
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Text("value:${controller.value}");
},
)
其他动画
Transform.scale(scale: null);
Transform.translate(offset: Offset(100, 0));
AnimatedWidget的实现,如下,简单实现,build里返回啥widget都可以,只要listenalbe发生变化了,下边就会build
class SpinningContainer extends AnimatedWidget {
const SpinningContainer({Key key, AnimationController controller})
: super(key: key, listenable: controller);
Animation<double> get _progress => listenable;
@override
Widget build(BuildContext context) {
return Transform.rotate(
angle: _progress.value * 2.0 * math.pi,
child: Container(width: 200.0, height: 200.0, color: Colors.green),
);
}
}
- AnimatedDefaultTextStyle
就是一个textstyle动态变化的过程,也就是第二个参数style发生变化的时候,会在第三个参数duration设置的时间里转变完成。
AnimatedDefaultTextStyle(child: Text("text style"),
style: big?TextStyle(fontWeight: FontWeight.w100,color:Colors.purple):TextStyle(fontWeight: FontWeight.w900,color: Colors.red,fontStyle: FontStyle.italic),
duration: Duration(seconds: 3)),
- AnimatedListState
The state for a scrolling container that animates items when they are inserted or removed.
就看API,根本不知道咋用,还是找个demo看看,代码太多了,有空再细看
animated-list
- AnimatedModalBarrier
是给ModalRoute<T> 用的,不能直接添加在页面里
可以理解为背景图层,阻止用户与下层页面交互,最常见的,就是弹个对话框,对话框的背景是灰色的,点击事件不会传递到dialog下层的页面
完全不知道这个玩意哪里用的,showGeneralDialog里有个参数pageBuilder,我试着返回这个,没问题,背景动画都有,可这个随便放到别的容器里就没效果了,而且其他widget也不见了。总不能这玩意只能单独显示,就为了一个变化的背景颜色吗?
待以后研究。。。。。。。。。。。。。
const AnimatedModalBarrier({
Key key,
Animation<Color> color,
this.dismissible = true,
this.semanticsLabel,
this.barrierSemanticsDismissible,
})
- AnimatedOpacity
这个比较简单,就是透明度的变化,当opacity发生改变的时候,会在duration时间内完成
AnimatedOpacity(opacity: opacity, duration: Duration(seconds: 4),child: FlutterLogo(),)
- AnimatedPhysicalModel
AnimatedPhysicalModel
对这两个属性进行渐变 borderRadius 和elevation
如果animateColor:true为真的话,那么也对color进行渐变
如果animateShadowColor为true的话,那么对shadowColor也进行渐变
AnimatedPhysicalModel(child: FlutterLogo(size: 80,),animateColor:true,borderRadius: BorderRadius.circular(radius),
shape: BoxShape.circle, elevation: eleva, color: color, shadowColor: Colors.red, duration: Duration(seconds: 5)),
- AnimatedPositioned
Only works if it's the child of a Stack
就是位置发生变化的时候会进行一个渐变的过程。必须放在Stack里。
和PositionedTransition 差不多,那边是提供一个Animation<RelativeRect>。
使用
Stack(
children: <Widget>[
AnimatedPositioned.fromRect(
duration: Duration(seconds: 4),
child: FlutterLogo(
size: 69,
),
rect: big
? Rect.fromLTRB(0, 0, 50, 50)
: Rect.fromLTRB(110, 40, 250, 250),
),
],
),
构造方法
const AnimatedPositioned({
Key key,
@required this.child,
this.left,
this.top,
this.right,
this.bottom,
this.width,
this.height,
Curve curve = Curves.linear,
@required Duration duration,
})
AnimatedPositioned.fromRect({
Key key,
this.child,
Rect rect,
Curve curve = Curves.linear,
@required Duration duration,
})
- AnimatedSize
当widget大小变化的时候,有个动画的过程,如下,我们改变size的大小,就能看到效果
AnimatedSize(duration: Duration(seconds: 5), vsync: this,child: FlutterLogo(size: size,),),
- AnimatedWidget
一个抽象类,A widget that rebuilds when the given Listenable changes value.
需要提供一个listenable,这样当listenable的value变化的时候,这个widget会rebuild,属性可以动态变了,也就有动画效果了。
前边用的AnimationController,Animation<T> 都可以用
final Listenable listenable;
这个也可以用AnimatedBuilder来写,差不多
下边是个demo
class SpinningContainer extends AnimatedWidget {
const SpinningContainer({Key key, AnimationController controller})
: super(key: key, listenable: controller);
Animation<double> get _progress => listenable;
@override
Widget build(BuildContext context) {
return Transform.rotate(
angle: _progress.value * 2.0 * math.pi,
child: Container(width: 200.0, height: 200.0, color: Colors.green),
);
}
}
- AnimatedWidgetBaseState
AnimatedWidgetBaseState<T extends ImplicitlyAnimatedWidget> class
abstract class ImplicitlyAnimatedWidget extends StatefulWidget
demo:如下,简单的修改颜色
//use
TempX(duration: Duration(seconds: 5), c: color),
//class
class TempX extends ImplicitlyAnimatedWidget {
Color c = Colors.amber;
@override
ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState() {
return TempState();
}
TempX({
Key key,
Curves curve,
this.c,
@required Duration duration,
}) : assert(duration != null),
super(
key: key,
curve: curve == null ? Curves.linear : curve,
duration: duration);
}
class TempState extends AnimatedWidgetBaseState<TempX> {
ColorTween _colorTween;
@override
Widget build(BuildContext context) {
return Icon(Icons.directions_car, color: _colorTween.evaluate(animation));
}
@override
void forEachTween(TweenVisitor<dynamic> visitor) {
_colorTween = visitor(
// The latest tween value. Can be `null`.
_colorTween,
// The color value toward which we are animating.
widget.c,
// A function that takes a color value and returns a tween
// beginning at that value.
(value) => ColorTween(begin: value),
);
}
}
other
上边用到Tween的类,有个泛型,系统已经写好了一些
页面跳转以及数据的传递
比较坑爹的问题,context不好弄啊,这玩意咋只有State里有,我简单测试随便跳个widget,拿不到context,也没法点击按钮回来,擦。
Navigator operation requested with a context that does not include a Navigator.
解决:https://blog.csdn.net/nimeghbia/article/details/84388725
前边说过,flutter没有activity一说,所谓的页面,就是个widget,所以你定义一堆widget,就可以互相跳转了
打开下个页面有两种方式
- 先注册
key,value的形式,key名字你自己起一个,后边跳转就用这个key,value就是要跳转的页面,也就是个widget
final Map<String, WidgetBuilder> routes;
typedef WidgetBuilder = Widget Function(BuildContext context);
//注册
return new MaterialApp(
routes: {
"nextPageAnyName": (context) { return Text("xxxx");},
"thirdPageAnyName":(BuildContext context)=>gl,
},
//跳转页面这样
Navigator.pushNamed(context, "nextPageAnyName",arguments: "argument is a object");
//push返回的是个Future,可以通过then方法接收下个页面到时候传递回来的数据
Navigator.pushNamed(context, "nextPageAnyName",arguments: "argument is a object").then((value){
});
//打开新的页面的同事关闭自己咋办
Navigator.pushNamedAndRemoveUntil(context, "thirdPageAnyName", (Route<dynamic> route){
route.dispose();
return true;
});
- 参数里直接返回要跳转的widget,其实和上边的差不多,上边只是用key替换了这个而已
Navigator.push(context, MaterialPageRoute(builder: (context){
return Text("next page widget");
}))
一些button的简单介绍
- MaterialButton
可以设置文字的各种状态下的颜色,背景的颜色,波纹的颜色,背景等
const MaterialButton({
Key key,
@required this.onPressed,
this.onHighlightChanged,
this.textTheme,
this.textColor,
this.disabledTextColor,
this.color,
this.disabledColor,
this.highlightColor,
this.splashColor,
this.colorBrightness,
this.elevation,
this.highlightElevation,
this.disabledElevation,
this.padding,
this.shape,
this.clipBehavior = Clip.none,
this.materialTapTargetSize,
this.animationDuration,
this.minWidth,
this.height,
this.child,
})
- FlatButton
父类就是materialButton,它有个icon的方法,可以传2个widget,如下最后组合成一个Row,传给父类的child
就是左边一个icon,右边一个lable,当然了其实这个widget没限制的,你都弄成icon也没问题
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
icon,
const SizedBox(width: 8.0),
label,
],
)
- RaisedButton
默认有个阴影,点击阴影效果更明显
-
OutlineButton
默认有个边框,点击边框颜色改变,这些颜色都是可以自定义的
生命周期的监听
用到的代码如下
WidgetsBinding 添加observer,删除observer
WidgetsBindingObserver 重写didChangeAppLifecycleState方法,参数就是当前的状态
目前android就用到onResume和onPause,和activity不太一样,这玩意整个app好像就一个状态,因为flutter里页面也是widget,就算不可见的widget和可见的wiget声明周期也是一样的
class _LifecycleWatcherState extends State<LifecycleWatcher> with WidgetsBindingObserver {
AppLifecycleState _lastLifecyleState;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
_lastLifecyleState = state;
});
}
其他控件
- android里的edittext
TextField(
decoration: InputDecoration(hintText: "hint...."),
),
InputDecoration 属性比较多了,和android里的TextInputLayout很相似,error word,help word,count都有
- PageView
相当于android里的ViewPager
PageView({
Key key,
this.scrollDirection = Axis.horizontal,//可以垂直水平滚动
this.reverse = false,
PageController controller,
this.physics,//这个上边有解释过,主要可以设置是否可以触摸滑动
this.pageSnapping = true,
this.onPageChanged,//监听页面改变
List<Widget> children = const <Widget>[],//添加child
this.dragStartBehavior = DragStartBehavior.start,
})
滚动处理
PageController({
this.initialPage = 0,
this.keepPage = true,
this.viewportFraction = 1.0,//child页面占比吧,比1小你就能看到第二个了
})
页面手动跳转
// _pageController.animateToPage(index, duration: Duration(milliseconds: 333), curve: Curves.linear);//带动画的
// _pageController.jumpToPage(index);//不带动画,直接修改view
//下一页,上一页,当没有下一页或者上一页的时候,无反应
_pageController.nextPage(duration: Duration(milliseconds: 222), curve: Curves.easeInOut);
// _pageController.previousPage(duration: Duration(milliseconds: 222), curve: Curves.easeIn);
实际中的问题
flutter里边控件的宽高真是个问题
我弄个column,第一个widget放了张图片,第二个widget是个GridView,完事啥也不显示,提示错误。
可以给GridView弄个容器,限定宽高就ok了,垂直滚动的需要确定宽,水平滚动的需要确定高
边学边记录
1.侧滑菜单
使用scaffold就可以实现
var globalkey = GlobalKey<ScaffoldState>();
Scaffold(
key: globalkey,
appBar: AppBar(
leading: FlatButton(
onPressed: () {
globalkey.currentState.openDrawer();
},
child: Icon(Icons.menu)),
title: Text("first page"),
),
body: lv,
drawer: Drawer(
child: Column(
children: <Widget>[
Text("first......."),
Icon(Icons.access_alarm),
],
),
),
endDrawer: Drawer(
child: Column(
children: <Widget>[Text("right...")],
),
),
drawer:左侧的
endDrawer:右侧的,当然都是对于LTR而言的
可以看到都套了个Drawer,这个好处是宽度固定,背景默认白色,比较省事,否则,你也可以自己写的widget,自己处理背景,宽度。
除了手指在屏幕边界把侧滑页面弄出来,还可以通过点击按钮实现。
主要用到了Scaffold的ScaffoldState,那咋获取这个玩意
Scaffold.of(context)可以拿到这个state,不过这个context要求比较高,上边的代码里直接用就不行了,这个context要求是Scaffold里context,不能是scaffold外层的context。
我们可以弄个自定义widget放在scaffold里,这个自定义widget里的context就符合要求,不过我们这里的用个button也没必要自定义啊,所以用另外一种办法,就是代码里的
声明一个GolbalKey,完事把这个key指定给Scaffold的key,之后就可以通过globalkey.currentState拿到这个state,就可以用了。
var globalkey = GlobalKey<ScaffoldState>();
key: globalkey,
globalkey.currentState
- ShapeBorder自定义
系统提供了一些常用的,矩形,圆形,棱形等,如果还不满足,可以自定义实现
如下自定义了一个
import 'package:flutter/material.dart';
class SixLinesBorder extends ShapeBorder{
@override
EdgeInsetsGeometry get dimensions => null;//文字描述太多,没咋看懂,不管了先,好像是和边框那线条有关的
/// To obtain a [Path] that describes the area of the border itself, set the
/// [Path.fillType] of the returned object to [PathFillType.evenOdd], and add
/// to this object the path returned from [getOuterPath] (using
/// [Path.addPath]).
//简单解释下,内边界生效的方法,设置fillType为evenOdd,然后把outerPath加进来就能一起生效了
//这个方法默认不执行的,得手动调用,我们在outerPath里把这加进去
@override
Path getInnerPath(Rect rect, {TextDirection textDirection}) {
//简单的范围缩进20,弄个椭圆
Rect rect=rect2.deflate(20);
Path path=new Path();
path.fillType=PathFillType.evenOdd;
path.addOval(rect);
return path;
}
//感觉整个shape是由这个path来决定的
@override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
Path path=getInnerPath(rect);//
path.addOval(rect);//简单测试加个椭圆
return path;
}
@override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
//这里边可以用canvas画边框,随便你画啥了。
Paint paint=new Paint();
paint.isAntiAlias=true;
paint.color=Colors.deepPurple;
paint.style=PaintingStyle.stroke;
canvas.drawPath(getOuterPath(rect), paint);//我们这里就按照outerPath画个边框
canvas.drawLine(rect.topCenter,rect.bottomCenter, paint);//测试,再加条线
}
@override
ShapeBorder scale(double t) {
//放大的时候咋处理,有边框的话,把边框放大,没有的话,感觉啥也不干就行
return null;
// this.scale(t);
// return this;
}
}
GridView
GridView({
Key key,
Axis scrollDirection = Axis.vertical,//滚动方向,默认垂直的
bool reverse = false,//反转数据
ScrollController controller,//滑动监听控制
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required this.gridDelegate,//控制item的显示逻辑的
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
List<Widget> children = const <Widget>[],
int semanticChildCount,
})
下边举例都是以垂直方向说明的
gridDelegate的作用,目前系统提供了2种实现
- SliverGridDelegateWithMaxCrossAxisExtent
这个就是限制下item的横轴最大值,然后横轴能放几个就放几个,效果图如下
GridView(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 70,),
children: <Widget>[
Text("search",style: TextStyle(fontSize: 33 ,backgroundColor: Colors.cyan,)),
FlatButton(onPressed: _click, child: Icon(Icons.ac_unit)),
FlatButton(onPressed: _click, child: Icon(Icons.calendar_view_day)),
FlatButton(onPressed: _click, child: Icon(Icons.eject)),
FlatButton(onPressed: _click, child: Icon(Icons.inbox)),
],
);
- SliverGridDelegateWithFixedCrossAxisCount
这个就和我们android里的一样,规定下一行显示几个,
高度和宽度一样了,不知道高度能调整不,待研究
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2,),
高度的控制
childAspectRatio:就是item的宽高比
其他两个参数看名字就知道了,主轴和横轴方向item的间距
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, mainAxisSpacing: 10,crossAxisSpacing: 20,childAspectRatio: 2)
GridView的几种创建过程
- 直接new一个,上边有
- builder
主要就是通过itemBuilder来创建每个item,其他参数看名字就知道干啥了
var gridView = GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 2),
itemBuilder: (context, index) {
return GestureDetector(
child: Container(
color: Colors.amberAccent,
child: Text(
"text...$index",
style: TextStyle(backgroundColor: Colors.lightGreenAccent),
),
),
onTap: () {
_itemClick(index);
},
);
},
itemCount: 20,
scrollDirection: Axis.vertical,
reverse: false,
controller: null,
);
- count
关键字段crossAxisCount,其实就相当于gridDelegate用了SliverGridDelegateWithFixedCrossAxisCount
gridView=GridView.count(crossAxisCount: 5,children: <Widget>[
],);
- extent
关键字段maxCrossAxisExtent 也就是item的最大宽,其实相当于gridDelegate用了SliverGridDelegateWithMaxCrossAxisExtent
gridView = GridView.extent(
maxCrossAxisExtent: 200,
children: <Widget>[],
);
- custom
参数childrenDelegate,其实里边就是用到了itemBuilder来创建item
或者用SliverChildListDelegate()参数就是children
gridView = GridView.custom(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
childrenDelegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
color: Colors.cyan,
child: Text("text&&$index"),
);
},
childCount: 22,
));
日常记录
- 获取屏幕宽高
var size=MediaQuery.of(context).size;
或者
import 'dart:ui';
final width = window.physicalSize.width;
final height = window.physicalSize.height;
- 类似android里的比重
Expanded 里边有个参数flex
return Row(
children: <Widget>[
CustomLayout(),
Expanded(child: gridView),
],
);
PageStoreKey
比如一个TabBarView有3个页面,里边是listview或者girdview,你来回切换页面的时候发现数据又跑到第一条去了,要解决这个问题,很简单,加上key即可,如下,
目前没搞懂,①里边的参数到底啥类型,好像啥类型都行,奇怪
还有个问题,②如果有3个页面里有2个GridView,发现一个滚多少距离,另一个好像也跟着滚了。
已解决:
在同一个页面里的key,那个value的类型一样的话,就会跟着一起滚,所以如果有3个滚动页面的话,大家的value类型必须不同
var lv = ListView.builder(
key: PageStorageKey(""),
GlobalKey
widget里的key还可以用这个,如下可以拿到widget的大小,当然也可以直接拿到widget
( value.key as GlobalKey).currentContext.size
滚动监听
可以滚动的控件都带有一个controller参数,如下,可以监听滚动,获取滚动的距离
var _controller=ScrollController(initialScrollOffset: initOffset);
_controller.addListener((){
// print('listener==========${_controller.offset}');
});
还有下边两种,手动设置滚动位置,一个带动画,一个不带
curve:动画的差值器,类似android里的interpolator,系统提供了很多实现,也可以自定义
_controller.jumpTo(100);
_controller.animateTo(200, duration: Duration(seconds: 1), curve: Curves.ease);