目录
1. 文本(Text组件、TextSpan组件)
2. 按钮
3. 图片、ICON
4. MaterialApp(用于快速搭建APP框架)
5. Scaffold (用于快速搭建页面)
1. 文本
- Text (用于显示文本)
// 属于基础组件库
Text(
String this.data, {
Key? key,
// 文本的显示样式(如颜色、字体、粗细、背景等 见下面注释)
this.style,
/*
TextStyle
1. height:行高=fontSize*height行高因子。
2. fontFamily :不同平台默认支持的字体集不同,指定字体时需在各平台测试。
3. fontSize:该属性和Text的textScaleFactor都用于控制字体大小。区别:
1. fontSize可以精确指定字体大小,而textScaleFactor只能通过缩放比例来控制。
2. fontSize用于单个文本且字体大小不会随系统字体大小变化,而textScaleFactor用于系统字体大小设置改变时对应用进行全局调整。
4. color:字体颜色
5. fontWeight:字体粗细。FontWeight.w800
6. fontStyle:字体样式。FontStyle.italic 倾斜
7. decoration:文本装饰线(none没有线,lineThrough删除线,overline上划线,underline下滑线)
8. decorationColor:文本装饰线颜色
9. decorationStyle:文本装饰线样式([dashed,dotted]虚线 double两根线 soild一根实现 wavy波浪线)
10. wordSpacing: 单词间隙(负值则紧凑)
*/
this.strutStyle,
/*
文本的对齐方式。
TextAlign枚举类型:left左对齐(默认)、right右对齐、center居中、justfy两端对齐。
对齐的参考系是Text组件本身,只有Text宽度大于文本内容长度时才有意义。
*/
this.textAlign,
// 文本的方向。ltr从左至右,rtl从右至左。
this.textDirection,
this.locale,
this.softWrap,
// 对超出显示的文本指定截断方式。
// TextOverflow.clip直接截断,TextOverflow.ellipsis将多余文本截断后加省略号...。
this.overflow,
// 文本的缩放因子(相对于当前系统字体大小)
// 默认值可以通过MediaQueryData.textScaleFactor获得,如果没有MediaQuery,那么会默认值将为1.0。
this.textScaleFactor,
// 文本的最大显示行数。默认情况下文本自动折行。
this.maxLines,
this.semanticsLabel,
this.textWidthBasis,
this.textHeightBehavior,
})
示例
Text("Hello world! I'm Jack. "*4,
textAlign: TextAlign.left,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textScaleFactor: 1.5,
);
Text("Hello world",
style: TextStyle(
color: Colors.blue,
fontSize: 18.0,
height: 1.2,
fontFamily: "Courier",
background: new Paint()..color=Colors.yellow,
decoration:TextDecoration.underline,
decorationStyle: TextDecorationStyle.dashed
),
);
- TextSpan (富文本:对文本内容的不同部分按照不同的样式显示)
TextSpan({
TextStyle style, // 样式
Sting text, // 内容
List<TextSpan> children, // 一个TextSpan的数组,即TextSpan可以包括其他TextSpan
GestureRecognizer recognizer, // 手势识别处理
});
示例
通过TextSpan实现了一个基础文本片段和一个链接片段,然后通过Text.rich将TextSpan添加到Text(RichText(显示富文本的widget)的一个包装)中。
Text.rich(TextSpan(
children: [
TextSpan(
text: "Home: "
),
TextSpan(
text: "https://flutterchina.club",
style: TextStyle(
color: Colors.blue
),
recognizer: _tapRecognizer
),
]
))
/*
Text.rich(
InlineSpan this.textSpan, {
Key? key,
this.style,
this.strutStyle,
this.textAlign,
this.textDirection,
this.locale,
this.softWrap,
this.overflow,
this.textScaleFactor,
this.maxLines,
this.semanticsLabel,
this.textWidthBasis,
this.textHeightBehavior,
})
Text.rich的另一种用法
RichText({
Key? key,
required this.text,
this.textAlign = TextAlign.start,
this.textDirection,
this.softWrap = true,
this.overflow = TextOverflow.clip,
this.textScaleFactor = 1.0,
this.maxLines,
this.locale,
this.strutStyle,
this.textWidthBasis = TextWidthBasis.parent,
this.textHeightBehavior,
})
*/
- DefaultTextStyle 继承父辈文本样式
在Widget树中,子孙类文本组件未指定具体样式时会使用父级DefaultTextStyle组件设置的默认样式,除非【显示指定不继承样式】inherit: false。
DefaultTextStyle({
Key? key,
required this.style,
this.textAlign,
this.softWrap = true,
this.overflow = TextOverflow.clip,
this.maxLines,
this.textWidthBasis = TextWidthBasis.parent,
this.textHeightBehavior,
required Widget child,
})
示例(DefaultTextStyle、inherit)
DefaultTextStyle(
// 1.设置文本默认样式
style: TextStyle(
color:Colors.red,
fontSize: 20.0,
),
textAlign: TextAlign.start,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("hello world"),
Text("I am Jack"),
Text("I am Jack",
style: TextStyle(
inherit: false, // 2.不继承默认样式
color: Colors.grey
),
),
],
),
);
- animated_text_kit三方库(文字动画)
波浪式填充文字
Widget liquidText(String text) {
return SizedBox(
width: 320.0,
child: TextLiquidFill(
text: text,
waveColor: Colors.blue[400]!,
boxBackgroundColor: Colors.redAccent,
textStyle: TextStyle(
fontSize: 80.0,
fontWeight: FontWeight.bold,
),
boxHeight: 300.0,
loadUntil: 0.7, // 控制波浪最终停留的高度,取值是0-1.0
),
);
}
文字按波浪式跳动
Widget wavyText(List<String> texts) {
return DefaultTextStyle(
style: const TextStyle(
color: Colors.blue,
fontSize: 20.0,
),
child: AnimatedTextKit(
animatedTexts: texts.map((e) => WavyAnimatedText(e)).toList(),
isRepeatingAnimation: true,
repeatForever: true, // 是否一直重复
onTap: () {
print("文字点击事件");
},
),
);
}
一道彩虹滑过文字,最终留下渐变效果。
Widget rainbowText(List<String> texts) {
const colorizeColors = [
Colors.purple,
Colors.blue,
Colors.yellow,
Colors.red,
];
const colorizeTextStyle = TextStyle(
fontSize: 36.0,
fontWeight: FontWeight.bold,
);
return SizedBox(
width: 320.0,
child: AnimatedTextKit(
animatedTexts: texts
.map((e) => ColorizeAnimatedText(
e,
textAlign: TextAlign.center,
textStyle: colorizeTextStyle,
colors: colorizeColors,
))
.toList(),
isRepeatingAnimation: true,
repeatForever: true,
onTap: () {
print("文字点击事件");
},
),
);
}
向下轮播滚动广告牌
Widget rotateText(List<String> texts) {
return SizedBox(
width: 320.0,
height: 100.0,
child: DefaultTextStyle(
style: const TextStyle(
fontSize: 36.0,
fontFamily: 'Horizon',
fontWeight: FontWeight.bold,
color: Colors.blue,
),
child: AnimatedTextKit(
animatedTexts: texts.map((e) => RotateAnimatedText(e)).toList(),
onTap: () {
print("点击事件");
},
repeatForever: true,
),
),
);
}
一个一个打字打出来
Widget typerText(List<String> texts) {
return SizedBox(
width: 320.0,
child: DefaultTextStyle(
style: const TextStyle(
fontSize: 30.0,
color: Colors.blue,
),
child: AnimatedTextKit(
animatedTexts: texts
.map((e) => TyperAnimatedText(
e,
textAlign: TextAlign.start,
speed: Duration(milliseconds: 300),
))
.toList(),
onTap: () {
print("文字点击事件");
},
repeatForever: true,
),
),
);
}
其他效果
渐现效果(Fade)
打字机效果(Typewriter)
缩放效果(Scale)
闪烁效果(Flicker)
自定义效果
只需要动效类继承AnimatedText,然后重载以下方法:
1. 构造方法:通过构造方法配置动效参数
2. initAnimation:初始化 Animation 对象,并将其与 AnimationController 绑定;
3. animatedBuilder:动效组件构建方法,根据 AnimationController 的值构建当前状态的组件;
4. completeText:动画完成后的组件,默认是返回一个具有样式修饰的文字。
2. 按钮
Material组件库
1. RaisedButton 凸起的按钮(已弃用)
使用ElevatedButton替代
2. FlatButton 扁平化按钮(已弃用)
使用TextButton替代
3. OutlineButton 线框按钮(已弃用)
使用OutlinedButton替代
4. IconButton 图标按钮
5. ButtonBar 按钮组
6. 带图标的按钮
7. InkWell 没有样式的带水波纹的按钮
所有Material库中的按钮:
1. 都直接或间接继承自RawMaterialButton组件
2. 按下时都会有水波动画/涟漪动画(点击时按钮上会出现水波荡漾的动画)。
3. onPressed属性可设置点击回调,如果不提供该回调则按钮会处于禁用状态(不响应用户点击)。
- RaisedButton (已弃用)
默认带有阴影和灰色背景。按下后,阴影会变大
RaisedButton(
child: Text("按钮"),
color: Colors.blue, // 背景色, Color(0x000000)透明
textColor: Colors.white, // 文本色
disabledColor: Colors.gray, // 禁用时的背景色
disabledTextColor: Colors.gray, // 禁用时的文本色
splashColor: Colors.yellow, // 点击按钮时的水波纹的颜色
highligthColor: Colors.blue, // 点击时的背景色
elevation: 2.0, // 正常状态下的阴影,值越大越明显
highlightElevation: 8.0, // 按下时的阴影
disabledElevation:0.0, // 禁用时的阴影
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0) // 圆角
), // 设置shape为CircleBorder(side:BorderSide(color:Colors.white)) 圆形按钮
onPressed: () {
},
);
通过在外层加Container:设置宽高。
通过在外层加Container和Expanded:自适应宽或高。
- FlatButton(已弃用)
默认背景透明并不带阴影。按下后,会有背景色。
FlatButton({
Key? key,
required VoidCallback? onPressed,
VoidCallback? onLongPress,
ValueChanged<bool>? onHighlightChanged,
MouseCursor? mouseCursor,
ButtonTextTheme? textTheme,
Color? textColor,
Color? disabledTextColor,
Color? color,
Color? disabledColor,
Color? focusColor,
Color? hoverColor,
Color? highlightColor,
Color? splashColor,
Brightness? colorBrightness, // 按钮主题,默认是浅色主题
EdgeInsetsGeometry? padding, // 内边距
VisualDensity? visualDensity,
ShapeBorder? shape,
Clip clipBehavior = Clip.none,
FocusNode? focusNode,
bool autofocus = false,
MaterialTapTargetSize? materialTapTargetSize,
required Widget child,
double? height,
double? minWidth,
})
FlatButton.icon()
基本同上,没有visualDensity、child,多了2个必选参数icon、label。
示例
FlatButton(
child: Text("normal"),
colorBrightness: Brightness.dark,
padding: 10,
onPressed: () {},
)
- OutlineButton(已弃用)
默认有一个边框,不带阴影且背景透明。按下后,边框颜色会变亮、同时出现背景和阴影(较弱)
OutlineButton({
Key? key,
required VoidCallback? onPressed,
VoidCallback? onLongPress,
MouseCursor? mouseCursor,
ButtonTextTheme? textTheme,
Color? textColor,
Color? disabledTextColor,
Color? color,
Color? focusColor,
Color? hoverColor,
Color? highlightColor,
Color? splashColor,
double? highlightElevation,
this.borderSide,
this.disabledBorderColor,
this.highlightedBorderColor,
EdgeInsetsGeometry? padding,
VisualDensity? visualDensity,
ShapeBorder? shape,
Clip clipBehavior = Clip.none,
FocusNode? focusNode,
bool autofocus = false,
MaterialTapTargetSize? materialTapTargetSize,
Widget? child,
})
BorderSide({
this.color = const Color(0xFF000000),
this.width = 1.0,
this.style = BorderStyle.solid,
})
ElevatedButton、TextButton、OutlinedButton
Key? key,
required VoidCallback? onPressed,
VoidCallback? onLongPress,
ButtonStyle? style, //
FocusNode? focusNode,
bool autofocus = false,
Clip clipBehavior = Clip.none,
required Widget? child,
/*
例(ButtonStyle)
ButtonStyle(
foregroundColor: MaterialStateProperty.all<Color>(Colors.white),
backgroundColor:
MaterialStateProperty.all<Color>(Theme.of(context).primaryColor),
)
*/
- IconButton
一个可点击的Icon,不包括文字,默认没有背景,点击后会出现背景
IconButton({
Key? key,
this.iconSize = 24.0,
this.visualDensity,
this.padding = const EdgeInsets.all(8.0),
this.alignment = Alignment.center,
this.splashRadius,
required this.icon,
this.color,
this.focusColor,
this.hoverColor,
this.highlightColor,
this.splashColor,
this.disabledColor,
required this.onPressed,
this.mouseCursor = SystemMouseCursors.click,
this.focusNode,
this.autofocus = false,
this.tooltip,
this.enableFeedback = true,
this.constraints,
})
- ButtonBar
ButtonBar({
Key? key,
this.alignment,
this.mainAxisSize,
this.buttonTextTheme,
this.buttonMinWidth,
this.buttonHeight,
this.buttonPadding,
this.buttonAlignedDropdown,
this.layoutBehavior,
this.overflowDirection,
this.overflowButtonSpacing,
this.children = const <Widget>[], // 放入RaisedButton等类型按钮
})
- 带图标的按钮
前面图片后面文本的按钮
// ElevatedButton
RaisedButton.icon(
icon: Icon(Icons.send),
label: Text("发送"),
onPressed: _onPressed,
),
// OutlinedButton
OutlineButton.icon(
icon: Icon(Icons.add),
label: Text("添加"),
onPressed: _onPressed,
),
// TextButton
FlatButton.icon(
icon: Icon(Icons.info),
label: Text("详情"),
onPressed: _onPressed,
),
- InkWell
InkWell({
Key? key,
Widget? child,
GestureTapCallback? onTap,
GestureTapCallback? onDoubleTap,
GestureLongPressCallback? onLongPress,
GestureTapDownCallback? onTapDown,
GestureTapCancelCallback? onTapCancel,
ValueChanged<bool>? onHighlightChanged,
ValueChanged<bool>? onHover,
MouseCursor? mouseCursor,
Color? focusColor,
Color? hoverColor,
Color? highlightColor,
MaterialStateProperty<Color?>? overlayColor,
Color? splashColor,
InteractiveInkFeatureFactory? splashFactory,
double? radius,
BorderRadius? borderRadius,
ShapeBorder? customBorder,
bool? enableFeedback = true,
bool excludeFromSemantics = false,
FocusNode? focusNode,
bool canRequestFocus = true,
ValueChanged<bool>? onFocusChange,
bool autofocus = false,
})
- PopupMenuButton
菜单按钮
PopupMenuButton({
Key? key,
required this.itemBuilder, // itemBuilder
this.initialValue, // 初识选中值
this.onSelected, // 选中某项后回调,参数为选中值
this.onCanceled, // 取消后回调
this.tooltip,
this.elevation,
this.padding = const EdgeInsets.all(8.0),
this.child,
this.icon,
this.offset = Offset.zero,
this.enabled = true,
this.shape,
this.color,
})
PopupMenuItem({
Key? key,
this.value, // 值
this.enabled = true,
this.height = kMinInteractiveDimension,
this.textStyle,
this.mouseCursor,
required this.child,
})
3. 图片、ICON
- 图片(Image组件,支持gif)
Image({
Key? key,
required this.image,
this.frameBuilder,
this.loadingBuilder,
this.errorBuilder,
this.semanticLabel,
this.excludeFromSemantics = false,
this.width,
this.height,
this.color,
this.colorBlendMode,
this.fit,
this.alignment = Alignment.center,
this.repeat = ImageRepeat.noRepeat,
this.centerSlice,
this.matchTextDirection = false,
this.gaplessPlayback = false,
this.isAntiAlias = false,
this.filterQuality = FilterQuality.low,
})
1. image: (必选参数)
对应一个ImageProvider(抽象类,定义了获取图片数据的接口load)。
AssetImage从Asset中加载图片、NetworkImage从网络加载图片。
// 数据源可以是asset、文件、内存以及网络。
// Flutter框架对加载过的图片是有缓存的,默认最大缓存数量是1000,最大缓存空间为100M。
2. width、height:
图片的宽、高
当不指定宽高时,图片会根据当前父容器的限制,尽可能的显示其原始大小。当只设置width、height的其中一个时,另一个属性默认会按比例缩放,可以通过fit属性来指定适应规则。
3. fit:
缩放模式
用于在图片的显示空间和图片本身大小不同时指定图片的适应模式。缩放模式是在BoxFit中定义,它是一个枚举类型,有如下值:
fill:不按比例填充。
cover:按比例填充,超出显示空间部分会被剪裁。
contain:默认,按比例填充,会有留白。
fitWidth:图片的宽度会缩放到显示空间的宽度,高度会按比例缩放,然后居中显示,图片不会变形,超出显示空间部分会被剪裁。
fitHeight:图片的高度会缩放到显示空间的高度,宽度会按比例缩放,然后居中显示,图片不会变形,超出显示空间部分会被剪裁。
none:图片没有适应策略,会在显示空间内显示图片,如果图片比显示空间大,则显示空间只会显示图片中间部分。
4. color和 colorBlendMode:
在图片绘制时可以对每一个像素进行颜色混合处理
color指定混合色,而colorBlendMode指定混合模式(如:Blend.screen)
5. repeat:
重复方式
当图片本身大小小于显示空间时,指定图片的重复规则。
ImageRepeat.repeat 横纵向重复
ImageRepeat.repeatX 横向重复
ImageRepeat.repeatY 纵向重复
6. alignment
对齐方式 Alignment.center居中
示例(colorBlendMode、repeat)
Image(
image: AssetImage("images/avatar.png"),
width: 100.0,
color: Colors.blue,
colorBlendMode: BlendMode.difference,
);
Image(
image: AssetImage("images/avatar.png"),
width: 100.0,
height: 200.0,
repeat: ImageRepeat.repeatY ,
)
示例(从asset中加载图片)
1. 在工程根目录下创建一个images目录,并将图片avatar.png拷贝到该目录。
2. 在pubspec.yaml中的flutter部分添加如下内容:(由于 yaml 文件对缩进严格,所以必须严格按照每一层两个空格的方式进行缩进,此处assets前面应有两个空格。)
flutter:
assets:
- images/avatar.png
3. 加载该图片
Image(
image: AssetImage("images/avatar.png"),
width: 100.0
);
或(常用)
Image.asset("images/avatar.png",
width: 100.0,
)
示例(从网络加载图片)
Image(
image: NetworkImage(
"https://avatars2.githubusercontent.com/u/20411648?s=460&v=4"),
width: 100.0,
)
或
Image.network(
"https://avatars2.githubusercontent.com/u/20411648?s=460&v=4",
width: 100.0,
)
无法对图片进行缓存
示例(fit)
import 'package:flutter/material.dart';
class ImageAndIconRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
var img=AssetImage("imgs/avatar.png");
return SingleChildScrollView(
child: Column(
children: <Image>[
Image(
image: img,
height: 50.0,
width: 100.0,
fit: BoxFit.fill,
),
Image(
image: img,
height: 50,
width: 50.0,
fit: BoxFit.contain,
),
Image(
image: img,
width: 100.0,
height: 50.0,
fit: BoxFit.cover,
),
Image(
image: img,
width: 100.0,
height: 50.0,
fit: BoxFit.fitWidth,
),
Image(
image: img,
width: 100.0,
height: 50.0,
fit: BoxFit.fitHeight,
),
Image(
image: img,
width: 100.0,
height: 50.0,
fit: BoxFit.scaleDown,
),
Image(
image: img,
height: 50.0,
width: 100.0,
fit: BoxFit.none,
),
Image(
image: img,
width: 100.0,
color: Colors.blue,
colorBlendMode: BlendMode.difference,
fit: BoxFit.fill,
),
Image(
image: img,
width: 100.0,
height: 200.0,
repeat: ImageRepeat.repeatY ,
)
].map((e){
return Row(
children: <Widget>[
Padding(
padding: EdgeInsets.all(16.0),
child: SizedBox(
width: 100,
child: e,
),
),
Text(e.fit.toString())
],
);
}).toList()
),
);
}
}
例(placeholder,淡入。依赖transparent_image包)
import 'package:flutter/material.dart';
import 'package:transparent_image/transparent_image.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final title = 'Fade in images';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Stack(
children: <Widget>[
new Center(child: new CircularProgressIndicator()),
new Center(
child: new FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image:
'https://github.com/flutter/website/blob/master/_includes/code/layout/lakes/images/lake.jpg?raw=true',
),
),
],
),
),
);
}
}
例(缓存图片,cached_network_image包)
支持缓存、占位符和淡入淡出图片
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final title = 'Cached Images';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
child: new CachedNetworkImage(
placeholder: new CircularProgressIndicator(),
imageUrl:
'https://github.com/flutter/website/blob/master/_includes/code/layout/lakes/images/lake.jpg?raw=true',
errorWidget: (context, url, error) => Image.asset('images/image-failed.png'),
// CircularProgressIndicator圆形
progressIndicatorBuilder: (context, url, downloadProgress) => LinearProgressIndicator(value: downloadProgress.progress),
),
),
),
);
}
}
- ICON
Flutter可以像Web开发一样使用iconfont(字体图标),将图标做成字体文件,然后通过指定不同的字符而显示不同的图片。
在字体文件中,每一个字符都对应一个位码,而每一个位码对应一个显示字形,不同的字体就是指字形不同,即字符对应的字形是不同的。
在iconfont中,只是将位码对应的字形做成了图标,所以不同的字符最终就会渲染成不同的图标。
iconfont和图片相比有如下优势:
1. 体积小:可以减小安装包大小。
2. 矢量:iconfont都是矢量图标,放大不会影响其清晰度。
3. 可以应用文本样式:可以像文本一样改变字体图标的颜色、大小对齐等。
4. 可以通过TextSpan和文本混用。
- 使用内置的Material Design字体图标。在图标官网搜索
Flutter默认内置了一套Material Design的字体图标,在pubspec.yaml文件中的配置如下
flutter:
uses-material-design: true
Icons类中包含了所有Material Design图标的IconData静态变量定义。
例:
String icons = "";
// accessible:  or 0xE914 or E914
icons += "\uE914";
// error:  or 0xE000 or E000
icons += " \uE000";
// fingerprint:  or 0xE90D or E90D
icons += " \uE90D";
Text(icons,
style: TextStyle(
fontFamily: "MaterialIcons",
fontSize: 24.0,
color: Colors.green
),
);
使用图标就像使用文本一样,但是这种方式需要提供每个图标的码点,这对开发者并不友好,所以,Flutter封装了IconData和Icon来专门显示字体图标,上面的例子也可以用如下方式实现:
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.accessible,color: Colors.green,),
Icon(Icons.error,color: Colors.green,),
Icon(Icons.fingerprint,color: Colors.green,),
],
)
- 使用自定义字体图标
iconfont.cn上有很多字体图标素材,可以选择自己需要的图标打包下载后,会生成一些不同格式的字体文件,在Flutter中使用ttf格式即可。
例
1. 导入字体图标文件;
和导入字体文件相同,假设字体图标文件保存在项目根目录下,路径为"fonts/iconfont.ttf":
fonts:
- family: myIcon #指定一个字体名
fonts:
- asset: fonts/iconfont.ttf
2. 定义一个MyIcons类便于使用。
功能和Icons类一样:将字体文件中的所有图标都定义成静态变量:
class MyIcons{
// book 图标
static const IconData book = const IconData(
0xe614,
fontFamily: 'myIcon',
matchTextDirection: true
);
// 微信图标
static const IconData wechat = const IconData(
0xec7d,
fontFamily: 'myIcon',
matchTextDirection: true
);
}
3.使用
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(MyIcons.book,color: Colors.purple,),
Icon(MyIcons.wechat,color: Colors.green,),
],
)
常用三方库
- 加载网络图片(cached_network_image三方库)
1.
import 'package:cached_network_image/cached_network_image.dart';
2.
// 展位图、出错时Widget
CachedNetworkImage(
imageUrl: "http://dd.com/350x150",
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
),
// 进度图片时的加载指示器
CachedNetworkImage(
imageUrl: imageUrl,
// LinearProgressIndicator是线型指示器,CircularProgressIndicator 是圆形指示器
progressIndicatorBuilder: (context, url, downloadProgress) =>
LinearProgressIndicator(value: downloadProgress.progress),
errorWidget: (context, url, error) =>
Image.asset('images/image-failed.png'),
),
- 获取相机/相册中的图片(image_picker、multi_image_picker、multi_image_picker2、wechat_assets_picker)
image_picker支持单张图片选择、multi_image_picker支持多图选择。
二者均支持相机或从相册选择图片。
multi_image_picker默认语言是英文,需要自己配置本地语言。
/*
访问权限
iOS(info.plist)
<key>NSPhotoLibraryUsageDescription</key>
<string>需要相册权限</string>
Android(AndroidManifest.xml)
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/>
*/
import 'dart:io';
import 'package:image_picker/image_picker.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
class _MyHomePageState extends State<MyHomePage> {
File _image;
final picker = ImagePicker();
Future getImage() async {
final pickedFile = await picker.getImage(source: ImageSource.camera);
setState(() {
if (pickedFile != null) {
_image = File(pickedFile.path);
} else {
print('No image selected.');
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Image Picker Example'),
),
body: Center(
child: _image == null
? Text('No image selected.')
: Image.file(_image),
),
floatingActionButton: FloatingActionButton(
onPressed: getImage,
tooltip: 'Pick Image',
child: Icon(Icons.add_a_photo),
),
);
}
}
4. MaterialApp
用于快速搭建APP框架(名称、主题、语言、首页及路由列表等)。
1. home
首页
2. initialRoute
使用命名路由时的首页路径(默认是'/')
3. routes
命名路由列表
4. theme
主题
5. title
应用名
5. debugShowCheckedModeBanner
是否显示右上角debug角标
示例
MaterialApp(
title: ProjectConfig.packageInfo.appName,
theme: ProjectTheme.theme,
routes: {
'/':(context)=>BootstrapPage(),
'/login':(context)=>LoginPage(),
'/register':(context)=>RegisterPage(),
'/tab':(context)=>TabPage(),
},
)
5. Scaffold 页面骨架
用于快速搭建页面(导航栏、底部TabBar、悬浮按钮、抽屉等)。
// 属于Material组件库
1. appBar
导航栏(AppBar)
2. body
页面内容
3. bottomNavigationBar
底部tabbar(BottomNavigationBar)
4. floatingActionButton
悬浮按钮(FloatingActionButton)
5. floatingActionButtonLocation
悬浮按钮位置
6. backgroundColor
背景色
7. drawer
左侧抽屉
8. endDrawer
右侧抽屉
示例
实现一个页面,包含:
一个导航栏
导航栏右边有一个分享按钮
有一个抽屉菜单
有一个底部导航
右下角有一个悬浮的动作按钮
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
title: 'Flutter Tutorial',
home: new ScaffoldRoute(),
));
}
class ScaffoldRoute extends StatefulWidget {
@override
_ScaffoldRouteState createState() => _ScaffoldRouteState();
}
class _ScaffoldRouteState extends State<ScaffoldRoute> {
int _selectedIndex = 1;
@override
Widget build(BuildContext context) {
// Material必须有Scaffold
return Scaffold(
appBar: AppBar( // 导航栏
title: Text("App Name"),
actions: <Widget>[ // 导航栏右侧菜单
IconButton(icon: Icon(Icons.share), onPressed: () {}),
],
// leading: new IconButton(
// icon: new Icon(Icons.menu),
// tooltip: 'Navigation menu',
// onPressed: null,
// ),
),
// body: this._pageList[this._selectedIndex],
drawer: new MyDrawer(), // 抽屉
bottomNavigationBar: BottomNavigationBar( // 底部导航
items: <BottomNavigationBarItem>[ // items
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(icon: Icon(Icons.business), title: Text('Business')),
BottomNavigationBarItem(icon: Icon(Icons.school), title: Text('School')),
],
currentIndex: _selectedIndex, // 当前选中
fixedColor: Colors.blue, // 选种颜色
onTap: _onItemTapped, // 点击后调用
// iconSize:35, // 图标大小
// type: BottomNavigationBarType.fixed 按钮过多时允许设置多个
),
floatingActionButton: FloatingActionButton( // 悬浮按钮
child: Icon(Icons.add),
onPressed:_onAdd // 可为null
),
);
}
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
void _onAdd(){}
}
AppBar (导航栏)
一个Material风格的导航栏(导航栏标题、导航栏菜单、导航栏底部的Tab标题等)。
AppBar({
Key key,
// 导航栏最左侧Widget(常见为抽屉菜单按钮或返回按钮,在首页一般显示logo)。Icon、IconButton
this.leading,
this.automaticallyImplyLeading = true, // 如果leading为null,是否自动实现默认的leading按钮
this.title, // 页面标题Widget
this.actions, // 导航栏右侧Widget列表,在右边 从左至右显示
this.bottom, // 导航栏底部菜单,通常为Tab按钮组
this.elevation = 4.0, // 导航栏阴影
this.centerTitle, // 标题是否居中
this.backgroundColor, // 背景色
iconTheme // 图标样式
textTheme // 文本样式
centerTitle // 标题是否居中
})
leading
如果给Scaffold添加了抽屉菜单,默认情况下Scaffold会自动将AppBar的leading设置为菜单按钮, 点击它便可打开抽屉菜单。
如果想自定义菜单图标,可以手动来设置leading,如:
Scaffold(
appBar: AppBar(
title: Text("App Name"),
leading: Builder(builder: (context) {
return IconButton(
icon: Icon(Icons.dashboard, color: Colors.white), //自定义图标
onPressed: () {
// 打开抽屉菜单 Scaffold.of(context)可以获取父级最近的Scaffold 组件的State对象。
Scaffold.of(context).openDrawer();
},
);
}),
...
)
TabBar组件(Tab菜单栏)
// 属于Material组件库
1. tabs
Tab列表
2. controller
TabController对象
3. isScrollable
是否可滚动
4. indicatorColor
指示器颜色
5. indicatorWeight
指示器高度
6. indicatorPadding
指示器Padding
7. indicatorSize
指示器大小,TabBarIndicatorSize.label 与文本同宽
8. indicator
自定义指示器
9. labelColor
选中文本色
10. labelStyle
选中文本样式
11. labelPadding
文本Padding
12. unselectedLabelColor
未选中文本色
13. unselectedLabelStyle
未选中文本样式
Tab
Tab({
Key key,
this.text, // 菜单文本
this.icon, // 菜单图标
this.child, // 自定义组件样式
})
示例(方式1: with SingleTickerProviderStateMixin)
class _ScaffoldRouteState extends State<ScaffoldRoute>
with SingleTickerProviderStateMixin {
TabController _tabController; // 需要定义一个Controller
List tabs = ["新闻", "历史", "图片"];
@override
void initState() {
super.initState();
// 创建Controller ,用于控制/监听Tab菜单切换
_tabController = TabController(length: tabs.length, vsync: this);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
... //省略无关代码
bottom: TabBar( // 生成Tab菜单
controller: _tabController,
tabs: tabs.map((e) => Tab(text: e)).toList()
),
),
... //省略无关代码
);
}
通过TabBar只能生成一个静态的菜单,真正的Tab页(通过TabBarView来实现)还没有实现。
由于Tab菜单和Tab页的切换需要同步,需要通过TabController去监听Tab菜单的切换去切换Tab页
在initState方法中,创建完成后,添加监听
_tabController.addListener((){
switch(_tabController.index){
case 1: ...;
case 2: ... ;
}
});
如果Tab页可以滑动切换的话,还需要在滑动过程中更新TabBar指示器的偏移!显然,要手动处理这些是很麻烦的。
为此,Material库提供了一个TabBarView组件,通过它不仅可以轻松的实现Tab页,而且可以非常容易的配合TabBar来实现同步切换和滑动状态同步:
Scaffold(
appBar: AppBar(
... //省略无关代码
bottom: TabBar(
controller: _tabController, // 配置controller
tabs: tabs.map((e) => Tab(text: e)).toList()),
),
),
drawer: new MyDrawer(),
body: TabBarView(
controller: _tabController, // 配置controller
children: tabs.map((e) { // 创建3个Tab页,分别对应TabBar中的tabs
return Container(
alignment: Alignment.center,
child: Text(e, textScaleFactor: 5),
);
}).toList(),
),
... // 省略无关代码
);
现在,无论是点击导航栏Tab菜单还是在页面上左右滑动,Tab页面都会切换,并且Tab菜单的状态和Tab页面始终保持同步。
TabBar和TabBarView正是通过同一个controller来实现菜单切换和滑动状态同步的。
示例(方式2:DefaultTabController)
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('Home'),
centerTitle: true,
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: _searchFunc,
)
],
bottom: TabBar(
tabs: <Widget>[
Tab(text: '热门'),
Tab(text: '推荐')
],
),
),
drawer: Drawer(
child: Text('左侧抽屉'),
),
endDrawer: Drawer(
child: Text('右侧抽屉'),
),
body: TabBarView(
children: <Widget>[
ListView(
children: <Widget>[
ListTile(
title: Text('Hot1'),
),
ListTile(
title: Text('Hot2'),
),
],
),
ListView(
children: <Widget>[
ListTile(
title: Text('Reconmend1'),
),
ListTile(
title: Text('Reconmend2'),
),
],
)
],
),
),
);
}
void _searchFunc(){
}
抽屉菜单(Drawer)
Scaffold的drawer和endDrawer属性可以分别接受一个Widget来作为页面的左、右抽屉菜单。如果开发者提供了抽屉菜单,那么当用户手指从屏幕左(或右)侧向里滑动时便可打开抽屉菜单。
路由跳转
Navigator.pop(); // 隐藏侧边栏
Navigator.pushNamed(context, "/new_page"); // 跳转到新页面
示例(一个左抽屉菜单)
class MyDrawer extends StatelessWidget {
const MyDrawer({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Drawer(
child: MediaQuery.removePadding(
context: context,
// 移除抽屉菜单顶部默认留白
removeTop: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 38.0),
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: ClipOval(
child: Image.asset(
"imgs/avatar.png",
width: 80,
),
),
),
Text(
"Wendux",
style: TextStyle(fontWeight: FontWeight.bold),
)
],
),
),
Expanded(
child: ListView(
children: <Widget>[
ListTile(
leading: const Icon(Icons.add),
title: const Text('Add account'),
),
ListTile(
leading: const Icon(Icons.settings),
title: const Text('Manage accounts'),
),
],
),
),
],
),
),
);
}
}
抽屉菜单通常将Drawer组件作为根节点,它实现了Material风格的菜单面板,MediaQuery.removePadding可以移除Drawer默认的一些留白(比如Drawer默认顶部会留和手机状态栏等高的留白)
DrawerHeader 抽屉头部
1. child
子组件
2. decoration
装饰
3. padding
内边距
4. margin
外边距
UserAccountsDrawerHeader 抽屉头部
左侧依次显示头像、名称、邮箱
1. decoration
装饰
2. margin
外边距
3. currentAccountPicture
账户头像
4. accountName
账户名
5. accountEmial
账户邮箱
6. otherAccountsPictures
右侧图片列表(和头像顶部对齐)
FloatingActionButton 浮动按钮
悬浮在页面的某一个位置作为某种常用动作的快捷入口。
可以通过Scaffold的floatingActionButton属性来设置一个FloatingActionButton,同时通过Scaffold的floatingActionButtonLocation属性来指定其在页面中悬浮的位置
FloatingActionButton({
Key? key,
this.child, //
this.tooltip,
this.foregroundColor,
this.backgroundColor,
this.focusColor,
this.hoverColor,
this.splashColor,
this.heroTag = const _DefaultHeroTag(),
this.elevation, // 阴影
this.focusElevation,
this.hoverElevation,
this.highlightElevation,
this.disabledElevation,
required this.onPressed, // 点击后的回调
this.mouseCursor,
this.mini = false,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.materialTapTargetSize,
this.isExtended = false,
})
示例
floatingActionButton: FloatingActionButton(
onPressed: (){
},
child: Icon(Icons.add,color: Colors.black,size: 55),
tooltip: 'hello', // 长按时显示
backgroundColor: Colors.blue, // 背景色
elevation: 4.0, // 未点击时的阴影
highlightElevation: 12.0, // 点击时的阴影,默认12.0
shape:CircularNotchedRectangle(), // 形状
mini: // 是否是min类型,默认false
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, // centerFloat底部中间,centerDocked底部中间略向下(打洞),默认在底部右侧,
通过在外层添加Container设置大小。Container中设置margin移动位置。
Tab底部导航栏(TabBar、BottomAppBar、BottomNavigationBar)
可以通过Scaffold的bottomNavigationBar属性来设置底部导航
BottomAppBar({
Key? key,
this.color, // 背景色
this.elevation, // 阴影
this.shape,
this.clipBehavior = Clip.none,
this.notchMargin = 4.0,
this.child, //
})
// 可配合IndexedStack(index: _index,children: [DynamicPage(),MessagePage()])实现tab页面切换
BottomNavigationBar({
Key? key,
required this.items, // items(BottomNavigationBarItem)
this.onTap, // 点击后的回调
this.currentIndex = 0, // 默认显示第几个页面
this.elevation,
this.type, // 布局类型(BottomNavigationBarType:fixed图片固定;shifting图片漂移)
Color? fixedColor, //
this.backgroundColor, // 背景色
this.iconSize = 24.0, // 图标大小
Color? selectedItemColor, // 选中色
this.unselectedItemColor, // 未选中色
this.selectedIconTheme,
this.unselectedIconTheme,
this.selectedFontSize = 14.0, // 选中字体
this.unselectedFontSize = 12.0, // 未选中字体
this.selectedLabelStyle, // 选中字体样式
this.unselectedLabelStyle, // 未选中字体样式
this.showSelectedLabels,
this.showUnselectedLabels,
this.mouseCursor,
})
BottomNavigationBarItem({
required this.icon,
this.title, // 弃用
this.label,
Widget? activeIcon,
this.backgroundColor,
})
示例(打洞)(BottomAppBar)
BottomAppBar组件可以和FloatingActionButton配合也可以实现“打洞”效果,源码如下:
bottomNavigationBar: BottomAppBar(
color: Colors.white,
shape: CircularNotchedRectangle(), // 底部导航栏打一个圆形的洞
child: Row(
children: [
IconButton(icon: Icon(Icons.home)),
SizedBox(), // 中间位置空出
IconButton(icon: Icon(Icons.business)),
],
mainAxisAlignment: MainAxisAlignment.spaceAround, //均分底部导航栏横向空间
),
)
上面代码中没有控制打洞位置的属性,实际上,打洞的位置取决于FloatingActionButton的位置,上面FloatingActionButton的位置为:
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
所以打洞位置在底部导航栏的正中间。
BottomAppBar的shape属性决定洞的外形,CircularNotchedRectangle实现了一个圆形的外形,也可以自定义外形。
示例(BottomNavigationBar)
bottomNavigationBar: BottomNavigationBar( // 底部导航
items: <BottomNavigationBarItem>[ // items
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(icon: Icon(Icons.business), title: Text('Business')),
BottomNavigationBarItem(icon: Icon(Icons.school), title: Text('School')),
],
)
示例(BottomNavigationBar)
import 'package:flutter/material.dart';
import 'dynamic.dart';
import 'message.dart';
import 'category.dart';
import 'mine.dart';
class AppHomePage extends StatefulWidget {
AppHomePage({Key key}) : super(key: key);
@override
_AppHomePageState createState() => _AppHomePageState();
}
class _AppHomePageState extends State<AppHomePage> {
int _index = 0;
List<Widget> _homeWidgets = [
DynamicPage(),
MessagePage(),
CategoryPage(),
MinePage(),
];
void _onBottomNagigationBarTapped(index) {
setState(() {
_index = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('hello'),
),
body: IndexedStack( // 管理页面显示层级,index指定最上层
index: _index,
children: _homeWidgets,
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: _index,
onTap: _onBottomNagigationBarTapped,
items: [
_getBottomNavItem(
'动态', 'images/dynamic.png', 'images/dynamic-hover.png', 0),
_getBottomNavItem(
' 消息', 'images/message.png', 'images/message-hover.png', 1),
_getBottomNavItem(
'分类浏览', 'images/category.png', 'images/category-hover.png', 2),
_getBottomNavItem(
'个人中心', 'images/mine.png', 'images/mine-hover.png', 3),
],
),
);
}
BottomNavigationBarItem _getBottomNavItem(
String title, String normalIcon, String pressedIcon, int index) {
return BottomNavigationBarItem(
icon: _index == index
? Image.asset(
pressedIcon,
width: 32,
height: 28,
)
: Image.asset(
normalIcon,
width: 32,
height: 28,
),
label: title,
);
}
}