本片文章仅供学习参考,如有不对的地方,请在评论区留言,我会立即改正。
如果你觉得有用的话,一键三连就是对我更新的最大动力!
添加依赖
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
在MaterialApp
中添加下面代码:
localizationsDelegates: const [
GlobalWidgetsLocalizations.delegate,
GlobalMaterialLocalizations.delegate
],
supportedLocales: const [
const Locale('en', 'US'), // 美国英语
const Locale('zh', 'CN'), // 中文简体
//其它Locales
],
使用LayoutBuilder
可以根据约束大小进行显示子组件,constraints
可以得到父盒子的约束是多少。
Center(
child: LayoutBuilder(builder: (context, constraints) {
if (constraints.maxHeight < 150) {
return Container(
color: Colors.amber,
width: 100,
height: 100,
);
} else if (constraints.maxHeight > 350) {
print(constraints);
return Container(
color: Colors.deepOrange,
width: 300,
height: 300,
child: LayoutBuilder(builder: (context, constraints) {
print(constraints);
return Column(
children: [
AnimatedContainer(
duration: Duration(seconds: 1),
width: 150,
height: 150,
color: Colors.green,
),
],
);
}),
);
}
return Container(
color: Colors.cyan,
width: 200,
height: 200,
);
}),
)
该组件可以为他的子组件设置阴影效果等属性。
const Material({
Key? key,
this.type = MaterialType.canvas,
this.elevation = 0.0,
this.color,
this.shadowColor,
this.textStyle,
this.borderRadius,
this.shape,
this.borderOnForeground = true,
this.clipBehavior = Clip.none,
this.animationDuration = kThemeChangeDuration,
this.child,
Material(
elevation: 10,
shadowColor: Colors.green,
child: Container(
width: 150,
height: 150,
color: Colors.green,
),
)
使用ScaffoldMessenger.of(context).showSnackBar()
进行显示消息弹窗。
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
content: Text("这是Scaffold展示的内容"),
backgroundColor: Theme.of(context).primaryColor,
duration: Duration(seconds: 1),
behavior: SnackBarBehavior.floating,
),
);
},
child: Text("SnackBar"),
)
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
content: Text("这是Scaffold展示的内容"),
backgroundColor: Theme.of(context).primaryColor,
duration: Duration(seconds: 1),
action: SnackBarAction(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(),
)));
},
label: '跳转',
textColor: Colors.white,
),
behavior: SnackBarBehavior.floating,
),
);
},
child: Text("SnackBarAction"),
)
使用它需要用 showDiglog
组件将它显示出来,并且barrierDismissible
设置为false
,点击周围区域不能退出,只能使用路由进行退出。
showDialog.then()
可以将路由返回的值取出。
ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text("提示"),
content: const Text("确定删除吗?"),
actions: [
ElevatedButton(
onPressed: () {
Navigator.of(context).pop("确定");
},
child: const Text("确定")),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop("取消");
},
child: const Text("取消")),
],
);
},
// 点击周围区域不能退出
barrierDismissible: false,
).then(
// 使用异步获取数据的方法将按钮返回的值取出
(value) => print(value),
);
},
child: const Text("AlertDialog"))
SimpleDialog
需要和SimpleDialogOption
一起使用,使用then
可以将选择的结果返回。
ElevatedButton(
onPressed: () {
showDialog(
barrierDismissible:false,
context: context,
builder: (context) => SimpleDialog(
title: Text("请选择科目"),
children: [
SimpleDialogOption(
onPressed: () =>
Navigator.of(context).pop("语文"),
child: Text("语文")),
SimpleDialogOption(
onPressed: () =>
Navigator.of(context).pop("数学"),
child: Text("数学")),
SimpleDialogOption(
onPressed: () =>
Navigator.of(context).pop("英语"),
child: Text("英语")),
],
)).then((value) => print(value));
},
child: Text("SimpleDialog"))
使用它你就可以自定义弹框。
ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: (context) => Dialog(
child: Container(
padding: EdgeInsets.all(30),
height: MediaQuery.of(context).size.width,
child: ListView(
children: [
...List.generate(
20,
(index) => GestureDetector(
child: Card(
child: Text(index.toString()),
),
onTap: () => Navigator.of(context)
.pop("返回的数字:${index}"),
))
],
)),
),
).then((value) => print(value));
},
child: Text("Dialog"))
Container(
padding: EdgeInsets.all(50),
height: size.height*0.5,
color: Colors.amber,
child: ListView(
// 反转列表
reverse: true,
// 对其方向
scrollDirection: Axis.horizontal,
itemExtent: 100,
children: [
...List.generate(
10,
(index) => Card(
child: Center(
child: Text(index.toString()),
),
))
],
),
)
Container(
color: Colors.black12,
padding: EdgeInsets.all(50),
height: size.height,
child: ListView.builder(
itemBuilder: (context, index) => Container(
margin: EdgeInsets.all(10),
color: Colors.primaries[index],
height: 30,
),
itemCount: 18,
),
)
Container(
color: Colors.cyan,
padding: EdgeInsets.all(50),
height: size.height,
child: ListView.custom(
childrenDelegate: SliverChildBuilderDelegate((context, index) {
return Container(
color: Colors.primaries[index],
height: 30,
margin: EdgeInsets.all(10),
);
}, childCount: 18),
),
)
Container(
padding: const EdgeInsets.all(50),
color: Colors.green,
height: size.height,
child: ListView.separated(
itemBuilder: (context, index) => Container(
color: Colors.primaries[index],
margin: EdgeInsets.all(10),
height: 30,
),
separatorBuilder: (context, index) => const Divider(
thickness: 0.5,
),
itemCount: 18),
)
ListVew
可以制作多个可以滚动的列表,并且它的操作方式和GirdView
类似,因为他们都是继承BoxScrollView
。class _ListView_DemoState extends State<ListView_Demo> {
// 添加控制器
ScrollController _scrollController = ScrollController();
bool showToUp = false;
@override
void initState() {
super.initState();
// 添加监听控制器
_scrollController.addListener(() {
print(_scrollController.offset.toInt().ceil());
if (_scrollController.offset < 200 && showToUp) {
// 当没有滑到200的时候,返回顶部按钮不显示
setState(() {
showToUp = false;
});
} else if (_scrollController.offset > 200 && showToUp == false) {
// 当滑到200的时候,讲返回顶部按钮显示出来
setState(() {
showToUp = true;
});
}
});
}
// 防止内存泄漏
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_scrollController.dispose();
}
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
floatingActionButton: showToUp == false
? null
: FloatingActionButton(
onPressed: () {
setState(() {
// 返回顶部
// _scrollController.jumpTo(0.0);
// 添加返回顶部动画
_scrollController.animateTo(.0,
duration: Duration(milliseconds: 200),
curve: Curves.ease);
});
},
child: Icon(Icons.arrow_upward),
),
appBar: AppBar(
title: Text("ListView"),
),
body: Container(
padding: EdgeInsets.all(10),
// width: size.width,
height: size.height,
color: Colors.amber,
child: ListView(
key: PageStorageKey("a"),
controller: _scrollController,
itemExtent: 100,
children: [
...List.generate(
10,
(index) => Card(
child: Center(
child: Text(index.toString()),
),
))
],
),
),
);
}
}
GridView
是一个可以滚动的网格布局,可以用在多个格子布局形势下。
使用physics: NeverScrollableScrollPhysics(),
可以将它滚动取消。缺点:但是如果父盒子设置了高度,就不能显示不能滚动的地方。
Container(
color: Colors.blueGrey,
padding: const EdgeInsets.all(10),
width: size.width,
height: size.width,
child: GridView(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
mainAxisSpacing: 3,
crossAxisSpacing: 3,
crossAxisCount: 3,
// 元素在主轴方向的长度
mainAxisExtent: 90,
),
children: [
...List.generate(
18,
(index) => Container(
width: 100, // 无用
height: 100,
color: Colors.primaries[index],
))
],
),
)
Container(
// width: MediaQuery.of(context).size.width*0.6,
// height: MediaQuery.of(context).size.height*0.6,
color: Colors.amber,
child: GridView.count(
controller: ScrollController(),
// 控制器
physics: NeverScrollableScrollPhysics(),
// 设置不能滚动
crossAxisCount: 3,
// 次轴个数
shrinkWrap: true,
// 收缩包裹范围
mainAxisSpacing: 2,
crossAxisSpacing: 2,
// 间隙
padding: EdgeInsets.all(10),
children: List.generate(
9,
(index) => ElevatedButton(
onPressed: () {},
child: Text(
'${index + 1}',
style: TextStyle(fontSize: 24),
),
)),
),
)
Container(
width: size.width,
padding: EdgeInsets.all(10),
height: size.height * 0.6, // 需要设置高度,但是当不能滚动的时候,就不能显示需要滚动部分内容。
child: GridView.builder(
// 设置不能滚动
physics: NeverScrollableScrollPhysics(),
// 设置网格的代表
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 3,
mainAxisSpacing: 3, // 间隙
mainAxisExtent: 50, // 设置范围
crossAxisCount: 3,
),
itemBuilder: (context, index) {
return InkWell(
onTap: () {},
child: Container(
color: Colors.primaries[index],
),
);
},
itemCount: 18, // 个数
),
)
Container(
width: size.width,
height: size.width,
color: Colors.green,
padding: const EdgeInsets.all(10),
child: GridView.custom(
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
mainAxisSpacing: 3, crossAxisSpacing: 3, crossAxisCount: 3),
childrenDelegate: SliverChildBuilderDelegate((context, index) {
return Container(
color: Colors.primaries[index],
);
}, childCount: 9),
),
)
Container(
padding: const EdgeInsets.all(10),
width: size.width,
height: size.width,
color: Colors.black26,
// 用于创建指定宽高
child: GridView.extent(
maxCrossAxisExtent: size.width / 3,
mainAxisSpacing: 3,
crossAxisSpacing: 3,
// physics: NeverScrollableScrollPhysics(),
children: [
...List.generate(
90,
(index) => Container(
width: 100, // 无作用
color: Colors.primaries[index % 18],
))
],
),
)
GridView
、GridView.custom
、GridView.builder
有gridDelegate
属性,需要使用SliverGridDelegateWithFixedCrossAxisCount
进行设置次轴显示的个数
等属性;childrenDelegate
属性,SliverChildBuilderDelegate((context, index) { return Container();}, childCount: 9)
就是设置需要显示的组件及个数,GridView.bilder
则使用的是itemBuilder
,同理。SliverGridDelegateWithFixedCrossAxisCount
的GridView
,它里面就可以设置格子间隙等属性,则只能在外面设置间隙…可以用于布局多个页面的布局,适合做轮播图,多个页面左右、上下滑动效果。
Container(
padding: EdgeInsets.all(10),
height: size.width * 0.5,
width: size.width * 0.5,
color: Colors.black12,
child: PageView(
// 回调方法
onPageChanged: (value) {
print(value);
},
controller: PageController(
// 不占满全部
viewportFraction: 0.8,
// 初始页面
initialPage: 2,
),
scrollDirection: Axis.vertical,
children: [
...List.generate(
18,
(index) => Container(
child: Center(child: Text(index.toString())),
color: Colors.primaries[index],
))
],
),
)
Container(
// width: size.width,
height: size.width * 0.5,
child: PageView.builder(
controller: PageController(viewportFraction: 0.8),
// 跳动物理效果
physics: BouncingScrollPhysics(),
itemCount: 18,
itemBuilder: (context, index) => Container(
height: double.maxFinite,
width: size.width * 0.9,
color: Colors.primaries[index],
)),
)
Container(
width: size.width,
height: size.width * 0.5,
child: PageView.custom(
childrenDelegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
color: Colors.primaries[index],
);
},
childCount: 18
),
),
)
效果如下:
小结: 使用` physics: BouncingScrollPhysics()可以设置弹性效果。
// ignore_for_file: unused_import, file_names, prefer_const_constructors, prefer_const_literals_to_create_immutables, use_key_in_widget_constructors, deprecated_member_use
import 'package:flutter/material.dart';
class DrawerPage extends StatelessWidget {
var selector = 0;
List _list = ["游泳馆", "羽毛球场", "篮球场", "拳击场"];
List _result() {
return _list
.map((itme) => ListTile(
title: Text(itme),
leading: Icon(Icons.sports_soccer),
subtitle: Text("挥洒汗水逐梦的土地"),
onTap: () {},
))
.toList();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("DrawerPage"),
// 添加图标按钮,并为它绑定点击事件,用于打开Drawer
leading: Builder(builder: (BuildContext context){
return IconButton(icon: Icon(Icons.list), onPressed: () {
Scaffold.of(context).openDrawer();
},);
},),
elevation: 15.0,
),
// 底部导航栏
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(
Icons.add_a_photo,
),
title: Text("照相")),
BottomNavigationBarItem(
icon: Icon(
Icons.center_focus_strong,
),
title: Text("扫码")),
BottomNavigationBarItem(
icon: Icon(Icons.safety_divider), title: Text("pay"))
],
// 获取底部 BottomNavigationBarItem的索引下标
onTap: (value) {
selector = value;
print(value);
},
// 设置底部选择,结合点击事件可以切换选项
currentIndex: selector,
),
// 左侧侧边栏
drawer: Drawer(
// 添加listView
child: ListView(
children:
// 使用遍历方式将数据取出放入children,注意这个这个函数是widget数组,不需要加中括号
// _result(),
[
UserAccountsDrawerHeader(accountName: Text("泸州彭于晏"), accountEmail: Text("[email protected]"),currentAccountPicture: CircleAvatar(backgroundImage: AssetImage("images/girl.jpg"),),onDetailsPressed: ()=>print("你点击用户名片"),),
ListTile(title: Text("学校"),leading: Icon(Icons.school),subtitle: Text("充满师生情怀的圣地"),onTap: ()=>print("你点击了学校"),),
ListTile(title: Text("食堂"),leading: Icon(Icons.dining),subtitle: Text("皆是学生恋爱的宝地"),onTap: ()=>print("你点击了食堂"),),
ListTile(title: Text("操场"),leading: Icon(Icons.sports_soccer),subtitle: Text("挥洒汗水逐梦的土地"),onTap: (){},),
]
),
),
// 浮动按钮
floatingActionButton: FloatingActionButton(onPressed: () { print("你点击了打印按钮"); },
child: Icon(Icons.print),
// 长按提示文本
tooltip: '打印文档',
// 设置前景色,默认是白色
foregroundColor: Colors.red,
// 设置背景色,默认是洋红色
backgroundColor: Colors.teal,
// 设置焦点颜色
focusColor: Colors.orange,
// 设置点击时的阴影颜色
highlightElevation: 30,
// 设置没有点击时的阴影颜色
elevation: 30,
// 设置图标为mini类型
mini: true,
),
// 设置浮动按钮的显示位置
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
// 允许底部Button,这里面可以添加多个Button,使用的不多
persistentFooterButtons: [
FlatButton(onPressed: (){print("确定");}, child: Text("确定")),
TextButton(onPressed: (){print("取消");}, child: Text("取消"))
],
);
}
}
用于控制输入框中的内容,包括想输入框中赋值和从输入框中取值,该属性值为TextEditingController类型。
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
// 定义初始值
String info ="初始值";
// 定义一个controller对象之后,整个类里面都可以使用,并且将它与TextFeild的controller进行绑定
TextEditingController controller1 = TextEditingController();
// 创建一个方法并重写setState方法,告诉脚手架进行重写绘制该界面
void getValue(){
setState(() {
// 取出controller1的值赋值给info变量,进行传递给Text进行显示
info = controller1.text;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
"ContorllerDemo",
),
),
body: Column(
children: [
TextField(controller: controller1,),
Text(info),
],
),
floatingActionButton: FloatingActionButton(
child: Text("确定"),
onPressed: () {
getValue();
},
),
);
}
}
按钮的属性大致都一样。
// 漂浮按钮
RaisedButton(
onPressed: () {}, // 单击事件,
child: Text("漂浮按钮"), // 设置按钮文本
textColor: Colors.blue, // 设置按钮颜色
highlightColor: Colors.lightGreen[100], // 设置长按按钮颜色
splashColor: Colors.orangeAccent, // 设置水波纹颜色
colorBrightness: Brightness.light, //设置高亮显示,默认不高亮
elevation: 10, // 设置按钮阴影高度
highlightElevation: 50,// 设置高亮时的阴影颜色
// 带水波纹高亮变化的回调函数,按下为true,松开
onHighlightChanged: (value) {
print(value);
}, // 按钮名称
),
class buttonPage extends StatelessWidget {
const buttonPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
RaisedButton(
// 设置漂浮按钮
onPressed: () {}, // 单击事件,
child: Text("漂浮按钮"), // 设置按钮文本
textColor: Colors.blue, // 设置按钮颜色
highlightColor: Colors.lightGreen[100], // 设置长按按钮颜色
splashColor: Colors.orangeAccent, // 设置水波纹颜色
colorBrightness: Brightness.light, //设置高亮显示,默认不高亮
elevation: 10, // 设置按钮阴影高度
highlightElevation: 50, // 设置高亮时的阴影颜色
// 带水波纹高亮变化的回调函数,按下为true,松开
onHighlightChanged: (value) {
print(value);
}, // 按钮名称
),
// 漂浮按钮
FlatButton(
onPressed: () {}, // 设置漂浮按钮
child: Text("扁平按钮"), // 点击事件
textColor: Colors.deepOrange, //文本颜色
height: 50, // 按钮高度
color: Colors.lightGreen[100], //设置背景颜色
),
//带图标的漂浮按钮
FlatButton.icon(
// 带图标的漂浮按钮
onPressed: () {},
icon: Icon(Icons.add),
label: Text("添加"),
color: Colors.blueGrey,
textColor: Colors.white,
),
// 边框按钮
OutlineButton(
// 有边框不带阴影的按钮,用的比较少
// 单击事件
onPressed: () {
print("你点击了outlineButton");
},
// 设置长按事件
onLongPress: () {
print("你长按了outlineButton");
},
child: Column(
children: [Icon(Icons.input), Icon(Icons.outbox)],
),
color: Colors.green,
focusColor: Colors.black45,
textColor: Colors.green,
padding: const EdgeInsets.all(30.0), // 设置按钮的内边距
borderSide: BorderSide(color: Colors.red),
),
// 图标按钮
IconButton(
onPressed: () {}, // 按钮点击事件
icon: Icon(Icons.home), // 按钮图标
color: Colors.green[200], // 按钮颜色
iconSize: 30.0, // 按钮大小
padding: const EdgeInsets.all(30.0), // 按钮内边距
tooltip: "home", // 长按显示提示文本
),
// 文本按钮
TextButton(onPressed: () {}, child: Text("文本按钮"),)
],
),
);
}
}
他是一个滑块的组件,可以滑动,懂我意思吧!
这是原始的样子:
Slider(value: 0, onChanged: (value) {}),
class _SliderDemoState extends State<SliderDemo> {
// 设置回调值为零,每次拖动就会改变这个值
double _value = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Slider"),
),
body: Container(
child: Column(
children: [
// 不能滑动
Slider(value: 0, onChanged: (value) {}),
Slider(
// 当拖动的时候,确定当前的圆点的位置
value: _value,
// 标签,需要和divisions连用
label: "${_value}",
// 设置刻度,将这个其分为四份
divisions: 4,
// 设置最小值
min: 0,
// 设置最大值
max: 100,
// 设置已滑动过的颜色
activeColor: Colors.teal,
// 设置还没有滑到的颜色
inactiveColor: Colors.redAccent,
// 设置圆点的颜色
thumbColor: Colors.blueGrey,
// 回调方法,用于返回当前圆点的位置
onChanged: (value) {
setState(() {
_value = value;
});
},
),
],
),
));
}
}
const Slider({
Key? key,
required this.value,
required this.onChanged,
this.onChangeStart,
this.onChangeEnd,
this.min = 0.0,
this.max = 1.0,
this.divisions,
this.label,
this.activeColor,
this.inactiveColor,
this.thumbColor,
this.mouseCursor,
this.semanticFormatterCallback,
this.focusNode,
this.autofocus = false,
}) : _sliderType = _SliderType.material,
assert(value != null),
assert(min != null),
assert(max != null),
assert(min <= max),
assert(value >= min && value <= max),
assert(divisions == null || divisions > 0),
super(key: key);
可以实现开和关的组件。
原始样子:
Switch(value: false, onChanged: (v){}),
Text("当前开关为:${_value == false ? "关闭状态" : "开启状态"}"),
Switch(
// 设置当前开关是否 开启/关闭 状态
value: _value,
// 回调当前是否点击了开关
onChanged: (v) {
setState(() {
_value = v;
});
}),
const Switch.adaptive({
Key? key,
required this.value,
required this.onChanged,
this.activeColor,
this.activeTrackColor,
this.inactiveThumbColor,
this.inactiveTrackColor,
this.activeThumbImage,
this.onActiveThumbImageError,
this.inactiveThumbImage,
this.onInactiveThumbImageError,
this.materialTapTargetSize,
this.thumbColor,
this.trackColor,
this.dragStartBehavior = DragStartBehavior.start,
this.mouseCursor,
this.focusColor,
this.hoverColor,
this.overlayColor,
this.splashRadius,
this.focusNode,
this.autofocus = false,
}) : assert(autofocus != null),
assert(activeThumbImage != null || onActiveThumbImageError == null),
assert(inactiveThumbImage != null || onInactiveThumbImageError == null),
_switchType = _SwitchType.adaptive,
super(key: key);
再来个苹果风格的开关
CupertinoSwitch(
value: _value,
onChanged: (v) {
setState(() {
_value = v;
});
})
可以设置标题等信息的开关条,
这个可以设置多个。
SwitchListTile(
value: _value,
title: Text("这是标题"),
subtitle: Text("这是副标题"),
onChanged: (v) {
setState(() {
_value = v;
});
}),
SwitchListTile(
// 主标题
title: Text("这是标题"),
// 子标题
subtitle: Text("这是副标题"),
// 开关的状态
value: _value,
// 标题颜色
tileColor: Color(0xffededed),
// 回调方法
onChanged: (v) {
setState(() {
_value = v;
});
})
const SwitchListTile({
Key? key,
required this.value,
required this.onChanged,
this.tileColor,
this.activeColor,
this.activeTrackColor,
this.inactiveThumbColor,
this.inactiveTrackColor,
this.activeThumbImage,
this.inactiveThumbImage,
this.title,
this.subtitle,
this.isThreeLine = false,
this.dense,
this.contentPadding,
this.secondary,
this.selected = false,
this.autofocus = false,
this.controlAffinity = ListTileControlAffinity.platform,
this.shape,
this.selectedTileColor,
}) : _switchListTileType = _SwitchListTileType.material,
assert(value != null),
assert(isThreeLine != null),
assert(!isThreeLine || subtitle != null),
assert(selected != null),
assert(autofocus != null),
super(key: key);
可以用于显示网络的加载状况和文件下载等进度,主要用于显示进度。
线性进度条
默认样式如下:
LinearProgressIndicator(),
样式如下:
可以设置他的样式,但是一旦设置了他的value
之后就不能动了,可以借助计时器(Timer)
改变value
的值,从而达到动起来的效果。
LinearProgressIndicator(
value: 0.8,
color: Colors.blueGrey,
backgroundColor: Colors.redAccent,
)
样式如下:
源码如下:
const LinearProgressIndicator({
Key? key,
double? value,
Color? backgroundColor,
Color? color,
Animation<Color?>? valueColor,
this.minHeight,
String? semanticsLabel,
String? semanticsValue,
}) : assert(minHeight == null || minHeight > 0),
super(
key: key,
value: value,
backgroundColor: backgroundColor,
color: color,
valueColor: valueColor,
semanticsLabel: semanticsLabel,
semanticsValue: semanticsValue,
);
圆形进度指示器
和上面的线性进度指示器
的用法一样。
默认样式如下:
// 默认圆形进度指示器
CircularProgressIndicator(),
// 自适应圆形进度指示器
CircularProgressIndicator.adaptive(value: 0.5,)
样式如下:
刷新进度指示器
和上面的线性进度指示器
的用法一样。
默认样式如下:
RefreshProgressIndicator(),
CupertinoActivityIndicator()
可以放置图片的组件
在这之前,如果使用的是静态图片
,则需要在pubspec.yaml
文件中进行配置,并且需要这个项目的根目录下创建一个放置图片的文件夹。
// 这是直接添加整个文件夹下面的图片
assets:
- images/
// 添加单张图片
assets:
- images/1.png
- images/2.png
注意:在添加之后,需要点击Android studio
右上角的Pub get
进行添加依赖:图片组件
使用本地图片资源,即使用asset
方法:
Image.asset("images/1.png")
Wrap(
children: [
Container(
alignment: Alignment.center,
width: 100,
height: 100,
child: Image.asset("images/1.png"),
),
Container(
alignment: Alignment.center,
width: 100,
height: 100,
child: Image.asset(
"images/1.png",
fit: BoxFit.cover,
),
),
Container(
alignment: Alignment.center,
width: 100,
height: 100,
child: Image.asset(
"images/1.png",
// 设置填充方式
fit: BoxFit.fitWidth,
// 设置宽、高
width: 50,
height: 50,
// 填充方式
repeat: ImageRepeat.repeat,
),
),
],
)
使用网络图片
使用网络图片的前提是要有网络,不然一切都是扯淡,
用法和上面的静态图片的用法相同。
Container(
alignment: Alignment.center,
width: 100,
height: 100,
child: Image.network(
"https://pic.netbian.com/uploads/allimg/220131/012219-16435633391d32.jpg",
// 设置填充方式
fit: BoxFit.fitWidth,
// 设置宽、高
// 填充方式
repeat: ImageRepeat.repeat,
),
),
Container(
alignment: Alignment.center,
width: 100,
height: 100,
child: Image.network(
"https://pic.netbian.com/uploads/allimg/220205/002942-1643992182534d.jpg",
// 设置填充方式
fit: BoxFit.fitWidth,
// 设置宽、高
// 填充方式
repeat: ImageRepeat.repeat,
),
)
样式如下:
还有其他加载图片的方式,我就只举这两种加载方式!
源码如下:
const Image({
Key? key,
required this.image,
this.frameBuilder,
this.loadingBuilder,
this.errorBuilder,
this.semanticLabel,
this.excludeFromSemantics = false,
this.width,
this.height,
this.color,
this.opacity,
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,
}) : assert(image != null),
assert(alignment != null),
assert(repeat != null),
assert(filterQuality != null),
assert(matchTextDirection != null),
assert(isAntiAlias != null),
super(key: key);
使用Icon
组件进行实现:
// 图标
Icon(Icons.add)
Icon(Icons.add,size: 35,color: Colors.blueGrey,),
Row
Row
组件是多孩子组件,放置在其中的组件沿水平方向进行分布。
他只是布局组件,里面没有其他组件相当于无用,懂我意思吧!
Container(
decoration: BoxDecoration(border: Border.all(width: 1)),
child: Row(
children: [
Container(
width: 50,
height: 50,
color: Colors.primaries[0],
),
Container(
width: 50,
height: 50,
color: Colors.primaries[1],
),
Container(
width: 50,
height: 50,
color: Colors.primaries[2],
),
],
),
)
设置他的可以设置他的对其方式,对于水平布局组件来说,它只有沿main
轴进行分布,就是X
轴。
他又6
种对齐方式,如下所示:
// 默认
mainAxisAlignment: MainAxisAlignment.start,
// 末尾对齐
mainAxisAlignment: MainAxisAlignment.end,
// 居中对其
mainAxisAlignment: MainAxisAlignment.center,
// 周围有空间
mainAxisAlignment: MainAxisAlignment.spaceAround,
// 将剩余的空间均匀分布
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// 周围的空间均匀
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
Row({
Key? key,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
MainAxisSize mainAxisSize = MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection? textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline? textBaseline, // NO DEFAULT: we don't know what the text's baseline should be
List<Widget> children = const <Widget>[],
}) : super(
children: children,
key: key,
direction: Axis.horizontal,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textDirection: textDirection,
verticalDirection: verticalDirection,
textBaseline: textBaseline,
);
Column
垂直方向分布的组件,放置在它里面的组件可以垂直分布。
Container(
child: Column(
children: [
Container(
width: 50,
height: 50,
color: Colors.primaries[0],
),
Container(
width: 50,
height: 50,
color: Colors.primaries[1],
),
Container(
width: 50,
height: 50,
color: Colors.primaries[2],
),
],
),
)
放置在该组件中的组件呈现叠加效果。
共两个参数,constrains
可以获得屏幕的宽(高)最大值、最小值…
LayoutBuilder(builder: (context, constrains) {
if (constrains.maxWidth <600) {
return Container(
margin: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
colorBlock(Colors.primaries[1], "优雅红"),
colorBlock(Colors.primaries[2], "葡萄紫"),
],
),
);
} else {
return Container(
margin: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
colorBlock(Colors.primaries[1], "优雅红"),
colorBlock(Colors.primaries[2], "葡萄紫"),
colorBlock(Colors.primaries[3], "靛蓝紫"),
colorBlock(Colors.primaries[4], "笔墨蓝"),
],
),
);
}
})
添加依赖
flutter pub add shared_preferences
分别使用setString
、setInt
、setDouble
、setBool
、setStringList
进行设置需要存储的数据。
取数据则用getInt
、getDouble
、getBool
、getBool
、getString
、getStringList
、getKeys
,以及用get
去任意键的值,它的是Object
类型。
使用clear
可以清空所用键的值。
使用containsKey
查找是否存在该键。
// 设置值
// 用来存放得到对应键之后的值,并在组件中进行显示
String aaa = "";
_setMyData() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
// 下面是设置各个键名和对应的值
await preferences.setInt("mySetInt", 1);
await preferences.setDouble("mySetDouble", 1.1);
await preferences.setBool("mySetBool", false);
await preferences.setString("mySetString", "string");
await preferences.setStringList("mySetStringList", ["string"]);
}
// 得到值
_getMyData() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
// 得到int值
int myGetInt = preferences.getInt("mySetInt") ?? 2;
// 得到Double值
double myGetDouble = preferences.getDouble("mySetDouble") ?? 2.2;
// 得到bool值
bool myGetBool = preferences.getBool("mySetBool") ?? true;
// 得到String值
String myGetString = preferences.getString("mySetString") ?? "空值";
// 得到list<String>值
List<String> myGetListString =
preferences.getStringList("mySetStringList") ?? ["空值"];
// 得到全部键名
Set<String> myGetKey = preferences.getKeys();
// 得到任意值键的值
Object object = preferences.get("mySetStringList");
print("得到int数据:${myGetInt}");
print("得到Double数据:${myGetDouble}");
print("得到bool数据:${myGetBool}");
print("得到String数据:${myGetString}");
print("得到ListString数据:${myGetListString}");
print("得到Key数据:${myGetKey}");
print(object);
// 得到数据之后重绘页面
setState(() {
aaa = myGetString;
});
print(aaa);
// 清空键对应的 值
preferences.clear();
print("清空数据之后显示的String值、int值:${myGetString}、${myGetInt}");
// 查找是否存在该 键
bool flag = preferences.containsKey("aaa");
print("是否找到:${flag}");
}
命令行中添加path_provider
:
flutter pub add path_provider
首先,获取文件路径是个异步操作,需要使用异步方法,
创建一个方法:
getTemporaryDirectory()
void getTDir() {
// 放置目录的返回值
String tDir = '';
// 获取临时目录
Future<Directory> tempDir = getTemporaryDirectory();
// 所用them 来得到路径
tempDir.then((value) {
tDir = value.path;
print(tDir);
});
}
结果如下:
I/flutter ( 6737): /data/user/0/com.example.testdemo/cache
getApplicationDocumentsDirectory()
// 获取文档目录
void getDDir() {
// 放置目录的返回值
String tDir = '';
// 获取应用文档目录
Future<Directory> temp = getApplicationDocumentsDirectory();
temp.then((value) {
tDir = value.path;
print(tDir);
});
}
结果如下:
I/flutter ( 6737): /data/user/0/com.example.testdemo/app_flutter
getExternalStorageDirectory()
// 获取文档目录
void getESDir() {
// 放置目录的返回值
String? tDir = '';
// 获取外存目录
Future<Directory?> temp = getExternalStorageDirectory();
temp.then((value) {
tDir = value!.path;
print(tDir);
});
}
结果如下:
I/flutter ( 6737): /storage/emulated/0/Android/data/com.example.testdemo/files
注意,在判断目录是否存在的时候,!
表示没有
这个目录。
不加!
表示有这个目录。
// 创建目录
void createDir(String dirName) {
// 放置目录的返回值
String? tDir = '';
Future<Directory?> temp = getTemporaryDirectory();
temp.then((value) {
tDir = value!.path;
print(tDir);
// 判断目录是否存在,并得到他的值
Directory("$tDir/$dirName").exists().then((value){
// 如果目录不存在,则创建目录
if(!value){
Directory("$tDir/$dirName").create();
print("目录创建成功!");
}else{
print("目录已存在!");
}
});
});
}
结果如下:
注:因为我刚刚创建了该目录,所有已存在!
I/flutter ( 6737): /data/user/0/com.example.testdemo/cache
I/flutter ( 6737): 目录已存在!
注意,在判断目录是否存在的时候,!
表示没有
这个目录。
不加!
表示有这个目录。
// 删除目录
void deleteDir(String dirName) {
// 放置目录的返回值
String? tDir = '';
Future<Directory?> temp = getTemporaryDirectory();
temp.then((value) {
tDir = value!.path;
print(tDir);
// 判断目录是否存在,并得到他的值
Directory("$tDir/$dirName").exists().then((value){
// 如果目录存在,则删除目录
if(value){
Directory("$tDir/$dirName").delete();
print("目录删除成功!");
}else{
print("删除失败!");
}
});
});
}
结果如下:
I/flutter ( 6737): /data/user/0/com.example.testdemo/cache
I/flutter ( 6737): 目录删除成功!
I/flutter ( 6737): /data/user/0/com.example.testdemo/cache
I/flutter ( 6737): 删除失败!
注意:在程序运行的时候就需要调用这个方法,才能将路径放在列表之中。
// 显示目录列表
List lists = [];
void getFiles () async{
// 放置目录的返回值
String? tDir = '';
// 放置各个目录名称
Future<Directory?> temp = getTemporaryDirectory();
temp.then((value) {
tDir = value!.path;
// print(tDir);
// 将获取到的目录名以迭代的方式放进列表中
Directory("$tDir").list(recursive: true,followLinks: false).listen((event) {
lists.add(event.path);
});
});
}
// 在应用加载的时候进行调用
@override
void initState() {
getFiles();
}
结果如下:
I/flutter ( 6737): /data/user/0/com.example.testdemo/cache
I/flutter ( 6737): /data/user/0/com.example.testdemo/cache
I/flutter ( 6737): 目录创建成功!
I/flutter ( 6737): 目录创建成功!
I/flutter ( 6737): []
I/flutter ( 6737): [/data/user/0/com.example.testdemo/cache/voice, /data/user/0/com.example.testdemo/cache/ciy]
注:创建的是两个目录,所以有两个创建成功!
// 创建文件
void createFile() async{
// 放置目录的返回值
String? tDir = '';
// 放置各个目录名称
Future<Directory?> temp = getTemporaryDirectory();
temp.then((value) {
tDir = value!.path;
// print(tDir);
File file = File("$tDir/default.ini");
file.exists().then((value) {
// 如果没有此文件,则创建
if(!value){
file.create();
}else{
print("已有该文件");
}
});
});
}
结果如下:
I/flutter ( 6737): 创建成功!
I/flutter ( 6737): 已经创建成功了....
注意,当没有这个文件的时候才进行创建。
// 删除文件
void deleteFile() async{
// 放置目录的返回值
String? tDir = '';
// 放置各个目录名称
Future<Directory?> temp = getTemporaryDirectory();
temp.then((value) {
tDir = value!.path;
// print(tDir);
File file = File("$tDir/default.ini");
file.exists().then((value) {
// 如果没有此文件,则创建
if(value){
file.delete();
print("删除成功");
}else{
print("删除失败,可能已经被删除了....");
}
});
});
}
结果如下:
I/flutter ( 6737): 删除成功
I/flutter ( 6737): 删除失败,可能已经被删除了....
// 向文件写入内容
void saveFile(String contents){
// 放置目录的返回值
String tDir = "";
// 放置各个目录的名称
Future<Directory> temp = getTemporaryDirectory();
temp.then((value) {
tDir = value.path;
// 打开文件
File file = File("$tDir/default.ini");
// 向文件中写入内容
file.writeAsString(contents,mode: FileMode.append);
print("写入成功了!");
});
}
结果如下:
I/flutter ( 6737): 写入成功了!
注:写入有五种模式:
不可读意味着,写进去的数据就不能读取出来!
如果想将文件内容全部删除,可以将写入改为write
模式,然后输入一个空字符串即可!
但需要注意的是,全部为空之后的话,就不能按照行方式进行读取数据!
// 读取文件内容
void readFile(String fileName){
String tDir = "";
// 放置目录名称
Future<Directory> temp = getTemporaryDirectory();
temp.then((value){
tDir = value.path;
// 打开文件
File file = File("$tDir/$fileName");
// 全部读出
file.readAsString().then((value)=> print(value));
// 按行方式读
file.readAsLines().then((value) => print("行方式:第1行=》"+value[0]));
});
}
结果如下:
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 龙姐的大宝贝
I/flutter ( 6737): 行方式:第1行=》龙姐的大宝贝
小结:
Future
对象。最后附上此次代码:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
class Path_Provider_Demo extends StatefulWidget {
const Path_Provider_Demo({Key? key}) : super(key: key);
@override
_Path_Provider_DemoState createState() => _Path_Provider_DemoState();
}
class _Path_Provider_DemoState extends State<Path_Provider_Demo> {
void getTDir() {
// 放置目录的返回值
String tDir = '';
// 获取临时目录
Future<Directory> tempDir = getTemporaryDirectory();
// 所用them 来得到路径
tempDir.then((value) {
tDir = value.path;
print(tDir);
});
}
// 获取文档目录
void getDDir() {
// 放置目录的返回值
String tDir = '';
Future<Directory> temp = getApplicationDocumentsDirectory();
temp.then((value) {
tDir = value.path;
print(tDir);
});
}
// 获取文档目录
void getESDir() {
// 放置目录的返回值
String? tDir = '';
Future<Directory?> temp = getExternalStorageDirectory();
temp.then((value) {
tDir = value!.path;
print(tDir);
});
}
// 创建目录
void createDir(String dirName) {
// 放置目录的返回值
String? tDir = '';
Future<Directory?> temp = getTemporaryDirectory();
temp.then((value) {
tDir = value!.path;
print(tDir);
// 判断目录是否存在,并得到他的值
Directory("$tDir/$dirName").exists().then((value){
// 如果目录不存在,则创建目录
if(!value){
Directory("$tDir/$dirName").create();
print("目录创建成功!");
}else{
print("目录已存在!");
}
});
});
}
// 删除目录
void deleteDir(String dirName) {
// 放置目录的返回值
String? tDir = '';
Future<Directory?> temp = getTemporaryDirectory();
temp.then((value) {
tDir = value!.path;
print(tDir);
// 判断目录是否存在,并得到他的值
Directory("$tDir/$dirName").exists().then((value){
// 如果目录存在,则删除目录
if(value){
Directory("$tDir/$dirName").delete();
print("目录删除成功!");
}else{
print("删除失败!");
}
});
});
}
// 显示目录列表
List lists = [];
void getFiles () async{
// 放置目录的返回值
String? tDir = '';
// 放置各个目录名称
Future<Directory?> temp = getTemporaryDirectory();
temp.then((value) {
tDir = value!.path;
// print(tDir);
// 将获取到的目录名以迭代的方式放进列表中
Directory("$tDir").list(recursive: true,followLinks: false).listen((event) {
lists.add(event.path);
});
});
}
// 在应用加载的时候进行调用
@override
void initState() {
getFiles();
}
// 创建文件
void createFile() async{
// 放置目录的返回值
String? tDir = '';
// 放置各个目录名称
Future<Directory?> temp = getTemporaryDirectory();
temp.then((value) {
tDir = value!.path;
// print(tDir);
// 打开文件
File file = File("$tDir/default.ini");
file.exists().then((value) {
// 如果没有此文件,则创建
if(!value){
file.create();
print("创建成功!");
}else{
print("已经创建成功了....");
}
});
});
}
// 删除文件
void deleteFile() async{
// 放置目录的返回值
String? tDir = '';
// 放置各个目录名称
Future<Directory?> temp = getTemporaryDirectory();
temp.then((value) {
tDir = value!.path;
// print(tDir);
// 创建文件
File file = File("$tDir/default.ini");
file.exists().then((value) {
// 如果没有此文件,则创建
if(value){
file.delete();
print("删除成功");
}else{
print("删除失败,可能已经被删除了....");
}
});
});
}
// 向文件写入内容
void saveFile(String contents){
// 放置目录的返回值
String tDir = "";
// 放置各个目录的名称
Future<Directory> temp = getTemporaryDirectory();
temp.then((value) {
tDir = value.path;
// 打开文件
File file = File("$tDir/default.ini");
// 向文件中写入内容
file.writeAsString(contents,mode: FileMode.append);
print("写入成功了!");
});
}
// 读取文件内容
void readFile(String fileName){
String tDir = "";
// 放置目录名称
Future<Directory> temp = getTemporaryDirectory();
temp.then((value){
tDir = value.path;
// 打开文件
File file = File("$tDir/$fileName");
// 全部读出
file.readAsString().then((value)=> print(value));
// 按行方式读
file.readAsLines().then((value) => print("行方式:第1行=》"+value[0]));
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("文件路径"),
centerTitle: true,
),
body: Center(
child: Column(
children: [
// 获取临时目录
ElevatedButton(
onPressed: () {
getTDir();
},
child: Text("获取临时目录"),
),
// 获取文档目录
ElevatedButton(
onPressed: () {
getDDir();
},
child: Text("获取文档目录"),
),
// 获取外存目录
ElevatedButton(
onPressed: () {
getESDir();
},
child: Text("获取外存目录"),
),
// 创建目录
ElevatedButton(
onPressed: () {
createDir("voice");
createDir("ciy");
},
child: Text("创建目录"),
),
// 删除目录
ElevatedButton(
onPressed: () {
deleteDir("voice");
deleteDir("ciy");
},
child: Text("删除目录"),
),
// 删除目录
ElevatedButton(
onPressed: () {
getFiles();
print(lists);
},
child: Text("获取临时目录列表"),
),
// 创建文件
ElevatedButton(
onPressed: () {
createFile();
},
child: Text("创建文件"),
),
// 删除文件
ElevatedButton(
onPressed: () {
deleteFile();
},
child: Text("删除文件"),
),
// 写文件
ElevatedButton(
onPressed: () {
saveFile("龙姐的大宝贝\n"*10);
},
child: Text("写文件"),
),
// 读文件
ElevatedButton(
onPressed: () {
readFile("default.ini");
},
child: Text("读文件"),
),
],
),
),
);
}
}
效果如下:
StatelessWidget
是无状态组件,改变内部值,但不会实时刷新。
StatefulWidget
是有状态组件,改变内部值,但会实时刷新。
使用StatelessWidget
传参:
// 无状态
class Test4 extends StatelessWidget {
Test4({Key? key}) : super(key: key);
int a = 0;
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
title: const Text("无状态组件"),
),
body: Center(
child: Container(
alignment: Alignment.center,
width: size.width * 0.5,
height: size.width * 0.5,
color: Colors.amber,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 有状态
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 需要传递一个参数到另一个页面中
Demo04(text: a),
ElevatedButton(
onPressed: () {
a++;
print(a);
},
child: const Text("点击")),
],
),
],
),
),
),
);
}
}
class Demo04 extends StatelessWidget {
const Demo04({Key? key, required this.text}) : super(key: key);
// 传递一个必须的参数
final int text;
@override
Widget build(BuildContext context) {
return Text(text.toString());
}
}
使用StatefulWidget
传参:
// 有状态
class Test3 extends StatefulWidget {
const Test3({Key? key}) : super(key: key);
@override
_Test3State createState() => _Test3State();
}
class _Test3State extends State<Test3> {
//
int a = 0;
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
title: const Text("有状态组件"),
),
body: Center(
child: Container(
alignment: Alignment.center,
width: size.width * 0.5,
height: size.width * 0.5,
color: Colors.amber,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 有状态
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 需要传递一个参数到另一个页面中
Demo02(text: a),
ElevatedButton(
onPressed: () {
setState(() {
a++;
print(a);
});
},
child: const Text("点击")),
],
),
],
),
),
),
);
}
}
class Demo02 extends StatefulWidget {
const Demo02({Key? key, required this.text}) : super(key: key);
final int text;
@override
State<Demo02> createState() => _Demo02State();
}
class _Demo02State extends State<Demo02> {
@override
Widget build(BuildContext context) {
return Text(
// 使用 widget 获得传递的值
widget.text.toString(),
style: const TextStyle(fontSize: 28),
);
}
}
注意:
StatefulWidget
传递过来的参数,需要使用widget.参数名
进行获取。使用命令加载 第三方包
flutter pub add url_launcher
ElevatedButton(
onPressed: () async {
String url = "https://www.baidu.com";
await launch(url, forceSafariVC: false);
},
child: const Text("打开链接"))
ElevatedButton(onPressed: () async{
String phone = "2611525";
String url = "tel:$phone";
await launch(url, forceSafariVC: false);
}, child: Text("打开拨号页面")),
ElevatedButton(onPressed: () async{
String phone = "2611525";
String url = "sms:$phone";
await launch(url, forceSafariVC: false);
},
child: Text("打开短信页面")
),
WillPopScope
有回调函数,该回调需要返回一个Future对象,如果返回的Future最终值为false时,则当前路由不出栈(不会返回);最终值为true时,当前路由出栈退出。我们需要提供这个回调来决定是否退出。
点击两次之后才能退出,需要定义一个变量来存放上一次所点击的时间。
判断时间是否为空
或者 当前时间是否和上次点击时间相隔1秒
,如果符合,则不能返回,否则就可以退出。
class WillPopScopeDemoState extends State<WillPopScopeDemo> {
// 上次点击时间
DateTime? _lastPressedAt;
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (_lastPressedAt == null ||
DateTime.now().difference(_lastPressedAt!) > Duration(seconds: 1)) {
// 点击两次之后超过1秒从新计时
_lastPressedAt = DateTime.now();
return false;
}
return true;
},
child: Scaffold(
appBar: AppBar(
title: Text("WillPopScope"),
),
body: Container(
alignment: Alignment.center,
child: Text("1秒内连续按两次返回键退出"),
),
),
);
}
}
从根到叶实现数据共享,通常共享Theme
和Local
的数据,功能就是共享数据。
class User {
String name;
int age;
String info;
// 构造函数
User(this.name, this.age, this.info) {}
}
Android studio
中输入inh
,回车,即可创建一个InheritedWidget
模板。我们class SharedDateWidegetDemo extends InheritedWidget {
const SharedDateWidegetDemo({
Key? key,
// 设置为必须参数
required this.data,
required Widget child,
}) : super(key: key, child: child);
// 所需的数据,可以是 String、int、bool、List、实体类....
final User data;
// 定义一个便捷方法,方便子树中的widget获取共享数据
static SharedDateWidegetDemo of(BuildContext context) {
final SharedDateWidegetDemo? result =
context.dependOnInheritedWidgetOfExactType<SharedDateWidegetDemo>();
assert(result != null, 'No SharedDateWidegetDemo found in context');
return result!;
}
// 该回调决定当data发生变化时,是否通知子树中依赖data的Widget重新build
@override
bool updateShouldNotify(SharedDateWidegetDemo old) {
return old.data != data;
}
}
SharedDateWidegetDemo.of(context).data
进行获取数据,由于我们创建的是一个实体类,所以可以在build
方法中创建实例
,进行获取数据。// 创建一个子组件,用于显示SharedDateWidegetDemo 中定义的 Data
class ChildWidgetDemo extends StatefulWidget {
const ChildWidgetDemo({Key? key}) : super(key: key);
@override
State<ChildWidgetDemo> createState() => _ChildWidgetDemoState();
}
class _ChildWidgetDemoState extends State<ChildWidgetDemo> {
@override
Widget build(BuildContext context) {
// 创建一个实例进行获取父组件传递下来的数据
User a = SharedDateWidegetDemo.of(context).data;
return Text(
a.info,
style: TextStyle(fontSize: 24),
);
}
//父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
//如果build中没有依赖InheritedWidget,则此回调不会被调用。
@override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
super.didChangeDependencies();
print("依赖被改变");
}
}
StatefulWidget
作为父组件,// 创建一个页面进行测试
class TestDemo extends StatefulWidget {
const TestDemo({Key? key}) : super(key: key);
@override
State<TestDemo> createState() => _TestDemoState();
}
class _TestDemoState extends State<TestDemo> {
// 默认年龄
int age = 12;
@override
Widget build(BuildContext context) {
// 作为数据共享类的子组件就可以进行数据的传递
return SharedDateWidegetDemo(
// 传入一个类
data: User("张三", age, "劳改犯"),
child: Scaffold(
appBar: AppBar(),
body: Center(
child: Container(
width: 100,
// height: 100,
child: Column(
children: [
// 调用子组件1
const ChildWidgetDemo(),
// 调用子组件2
ChildWidgetDemo2(),
ElevatedButton(onPressed: (){
// 通过按钮可以动态的改变年龄
setState(() {
age++;
});
}, child: Text("点击"))
],
),
),
),
),
);
}
}
小结:
inh
创建一个共享数据类,在子组件中使用数据共享类.of(context).参数
就可以将父组件传递的参数获取进行显示,然后创建一个父组件作为数据共享组件
的子组件,就可以开始访问。数据共享类
是一个媒介
,父组件通过媒介
将数据传给子组件。Flutter 页面间数据传递(共享)的几种经常使用方式
https://www.shangmayuan.com/a/501467f3dc8d48538c9d1093.html
在使用之前需要知道,provider
包是基于InheritedWidget
进行实现的,大致流程都是换汤不换药
的,所以在用起来的时候多注意一下就行了。
下面是它的官方介绍:
https://github.com/rrousselGit/provider/blob/master/resources/translations/zh-CN/README.md
// 用于统计数字
class Counter with ChangeNotifier, DiagnosticableTreeMixin {
// 定义一个统计变量
int _count = 0;
// 定义一个 get 方法
int get count => _count;
// 定义一个增加方法
void increment() {
// _count 自增
_count++;
// 通知监听器
notifyListeners();
}
// 调用调试填充属性
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
// TODO: implement debugFillProperties
super.debugFillProperties(properties);
// 添加一个整形属性为 count
properties.add(IntProperty('count', count));
}
}
// 用于改变颜色
class ChangeColor with ChangeNotifier, DiagnosticableTreeMixin {
int _num = 0;
int get num => _num;
Color _color = Colors.red;
Color get color => _color;
void change_color() {
int a = _num++;
_color = Colors.primaries[a % 18];
// 调用通知者
notifyListeners();
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
// TODO: implement debugFillProperties
super.debugFillProperties(properties);
properties.add(ColorProperty('color', color));
// properties.add(IntProperty('num', num));
}
}
MultiProvider
组件,而providers
存放所需的介质
。void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
ChangeNotifierProvider(create: (_) => ChangeColor()),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<ColorThemeProvider>(
create: (context) => ColorThemeProvider(),
child: Consumer<ColorThemeProvider>(
builder: (context, colorThemeProvider, child) => MaterialApp(
home: Provider_Demo(),
theme: ThemeData(
primarySwatch: colorThemeProvider.color,
visualDensity: VisualDensity.adaptivePlatformDensity),
// home: url_launcher_Demo(),
),
),
);
}
}
context.watch().num
可获得在ChangeColor
中所定义的变量值。context.read().increment()
则获取Counter
类中的对应的方法,也可以理解为: 通过context 读取Counter类 中的increment 方法。context.watch().count.toString()
可以理解为:通过context 看到Counter 中的 count 的值// 父组件
class Provider_Demo extends StatefulWidget {
const Provider_Demo({Key? key}) : super(key: key);
@override
State<Provider_Demo> createState() => _Provider_DemoState();
}
class _Provider_DemoState extends State<Provider_Demo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Provider"),
),
body: Center(
child: Container(
width: 300,
height: 300,
color: context.watch<ChangeColor>().color,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Builder(builder: (context) {
String length = Colors
.primaries[context.watch<ChangeColor>().num % 18]
.toString();
return Text(
"当前颜色为:${length.substring(length.length - 12, length.length - 2)}",
style: TextStyle(fontSize: 18),
);
}),
// 子组件
Count(),
ElevatedButton(
onPressed: () {
/// 点击调用 Counter 中的 increment 方法
/// 也可以理解为: 通过context 读取Counter类 中的increment 方法
context.read<Counter>().increment();
context.read<ChangeColor>().change_color();
},
child: Text("点击"))
],
),
),
),
);
}
}
// 创建一个子组件
class Count extends StatefulWidget {
const Count({Key? key}) : super(key: key);
@override
State<Count> createState() => _CountState();
}
class _CountState extends State<Count> {
@override
Widget build(BuildContext context) {
return Text(
// 通过context 看到Counter 中的 count 的值
context.watch<Counter>().count.toString(),
style: Theme.of(context).textTheme.headline4,
);
}
}
效果如下:
注:如果需要实现不同颜色的切换,在这里我使用的是Colors.primaries[context.watch
,因为primaries
是一个颜色列表,里面有18
中MaterialColor
颜色,所以我用%18
就可实现循环效果。
小结:
flutter
的根部使用MultiProvider
加载所需的介质
类,然后将根继承该组件。介质
类的时候,需要用with
去混合ChangeNotifier、 DiagnosticableTreeMixin
这两个类,(with
起到可以直接调用它们两个类里面的任意东西的作用!),然后创建所需要的暴露
的属性,以及它的get
方法,并且需要创建一个可以实现通过按钮调用的context.read().increment();
方法,进行该方法对应的操作,并在该方法中调用notifyListeners();
(通知者)告诉盒子需要重新绘制,最后通过context.watch().color
就能得到改变之后的值并赋予给所需的组件
!provider
并不难,实现的方式和上面介绍的InheritedWidget
原理一致,都是通过将数据通过介质
进行更新,然后又将数据通过根拿给各个小组件
使用。参考:
《Flutter实战·第二版》
https://book.flutterchina.club/chapter7/provider.html (有点版本问题,需要自己把握)
Flutter之Widget层级介绍
https://blog.csdn.net/lmh_19941113/article/details/97566811
美团技术站
https://tech.meituan.com/2018/08/09/waimai-flutter-practice.html
Flutter’s Rendering Pipeline
https://www.youtube.com/watch?v=UUfXWzp0-DU
Flutter(Dart)中extends 、 implements 、 with的用法与区别
https://www.jianshu.com/p/04b896764f6e
官方列子
https://pub.flutter-io.cn/packages/provider/example
// main文件
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// 监听
return ChangeNotifierProvider<ColorThemeProvider>(
create: (context) => ColorThemeProvider(),
child: Consumer<ColorThemeProvider>(
builder: (context, colorThemeProvider, child) => MaterialApp(
home: Color_Theme(),
theme: ThemeData(
// 得到改变的颜色,默认为 yellow
primarySwatch: colorThemeProvider.color,
visualDensity: VisualDensity.adaptivePlatformDensity
),
),
),
);
}
}
// 颜色选择页面
class Color_Theme extends StatefulWidget {
const Color_Theme({Key? key}) : super(key: key);
@override
State<Color_Theme> createState() => _Color_ThemeState();
}
class _Color_ThemeState extends State<Color_Theme> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("颜色主题"),
),
body: ExpansionTile(
title: Text("选择颜色"),
leading: Icon(Icons.color_lens),
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: Wrap(
runSpacing: 5,
spacing: 5,
children: [
...Colors.primaries
.map((color) => Material(
color: color,
child: InkWell(
onTap: () {
context.read<ColorThemeProvider>().changeColor(color: color);
},
child: Container(
height: 40,
width: 40,
),
),
))
.toList()
],
),
),
],
),
);
}
}
// 设置颜色改变
class ColorThemeProvider extends ChangeNotifier {
late MaterialColor _color;
MaterialColor get color => _color;
ColorThemeProvider() {
_color = Colors.yellow;
}
// 改变颜色
void changeColor({MaterialColor color = Colors.yellow}) {
_color = color;
notifyListeners();
}
}
Flutter 提供了一个 ValueListenableBuilder 组件,它的功能是监听一个数据源,如果数据源发生变化,则会重新执行其 builder。
class ValueListenableBuilder_Demo extends StatefulWidget {
const ValueListenableBuilder_Demo({Key? key}) : super(key: key);
@override
State<ValueListenableBuilder_Demo> createState() =>
_ValueListenableBuilder_DemoState();
}
class _ValueListenableBuilder_DemoState
extends State<ValueListenableBuilder_Demo> {
ValueNotifier<int> valueNotifier = ValueNotifier<int>(1);
ValueNotifier<String> valueNotifier2 = ValueNotifier<String>("当前颜色为:");
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ValueListenableBuilder"),
),
body: Center(
child: ValueListenableBuilder(
valueListenable: valueNotifier,
builder: (BuildContext context, int value1, Widget? child) {
return Container(
width: 300,
height: 300,
color: Colors.primaries[value1.toInt() % 18],
child: Test1(
valueNotifier2: valueNotifier2,
valueNotifier: valueNotifier,
value1: value1,
));
},
)),
);
}
}
// 将文本提出为单独的组件
class Test1 extends StatefulWidget {
const Test1({
Key? key,
required this.valueNotifier2,
required this.valueNotifier,
required this.value1,
}) : super(key: key);
final ValueNotifier<String> valueNotifier2;
final ValueNotifier<int> valueNotifier;
final int value1;
@override
State<Test1> createState() => _Test1State();
}
class _Test1State extends State<Test1> {
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
ValueListenableBuilder(
builder: (BuildContext context, String value2, Widget? child) {
return Text(
value2 + widget.value1.toString(),
style: TextStyle(fontSize: 28),
);
},
valueListenable: widget.valueNotifier2,
),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
Colors.primaries[Random().nextInt(18)])),
onPressed: () {
widget.valueNotifier.value++;
},
child: Text("点一点"))
],
);
}
}
在开发过程中,我们需要从 网络上或者文件中获得一些数据,但是为了避免在还没有获得数据的时候,UI
组件以及将一些功能实现,导致出现一些错误,所以我们就可以使用FutureBuilder
组件和StreamBuilder
组件进行数据的加载处理,而它俩之间有着一些相同的关系,也有这不同的联系,就比如FutureBuilder
是在done
的时候才将数据返回,而StreamBuilder
是在active
的时候将数据实时返回。
比如在打开一个页面时我们需要先从互联网上获取数据,在获取数据的过程中我们显示一个加载框,等获取到数据时我们再渲染页面。
又比如我们想展示Stream(比如文件流、互联网数据接收流)的进度。
class _FutureBuilderDemoState extends State<FutureBuilderDemo> {
// 网络请求数据
_getData() async {
String url =
"http://ws.webxml.com.cn/WebServices/WeatherWS.asmx/getRegionCountry?";
var request = await HttpClient().getUrl(Uri.parse(url));
var response = await request.close();
var body = response.transform(utf8.decoder).join();
return body;
}
// 通过FutureBuilder得到数据
getFuture() {
return FutureBuilder(
// 调用 _getData方法得到数据
future: Future.delayed(Duration(seconds: 1), () => _getData()),
builder: (context, snapshot) {
// 判断如果有值的话,就将数据取出
if (snapshot.hasData) {
return Column(
children: [Text("${snapshot.data}")],
);
}
// 如果没有值,显示一个错误图标
if (snapshot.hasError) {
return Icon(Icons.error);
}
// 在加载数据的时候,展示进度条,告诉用户正在加载
return CircularProgressIndicator();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("FutureBuilder"),
),
body: SingleChildScrollView(
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: 300),
child: Container(
width: 300,
// height: 300,
color: Colors.amber,
child: getFuture(),
),
)),
),
);
}
}
在Dart中Stream
也是用于接收异步事件数据,和Future 不同的是,它可以接收多个异步操作的结果,它常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。
StreamBuilder
正是用于配合Stream
来展示流上事件(数据)变化的UI组件。
class _StreamBuilderDemoState extends State<StreamBuilderDemo> {
final stream = Stream.periodic(Duration(seconds: 3), (_) => 42);
final future = Future.delayed(Duration(seconds: 2), () => "这是数据");
// 监听数据流和FutureBuilder
@override
void initState() {
super.initState();
// 监听数据流,他会源源不断的打印 stream的值,但是只能监听一次,不能和下面的 StreamBuilder同时使用
// stream.listen((event) {
// print(event);
// });
future.then((value) => print(value));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("FutureBuilder"),
),
body: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: 300),
child: DefaultTextStyle(
style: const TextStyle(fontSize: 24),
child: Container(
width: 300,
color: Colors.amber,
child: StreamBuilder(
stream: stream,
builder:
(BuildContext context, AsyncSnapshot<dynamic> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return const Text("数据为空");
case ConnectionState.waiting:
return const Text("等待数据流");
case ConnectionState.active:
if (snapshot.hasError) {
return Text("错误:${snapshot.error}");
} else {
return Text("内容为:${snapshot.data}");
}
case ConnectionState.done:
return const Text("数据流已关闭");
}
return Container();
},
),
),
),
),
),
);
}
}
效果如下:
在Sream
中还可以通过final controller = StreamController.broadcast();
来控制数据的传入,它还提供了一个sink
(水池)存放数据,使用controller.sink.add(10);
可以手动将数据添加到水池当中,并在builder
中使用controller.stream
将数据存到stream
中,通过snapshot.data
可以将其取出。
注意:每次build
得到的值都不同,所以可以用它去实现相应的业务逻辑!
final controller = StreamController.broadcast();
getStreamDate() {
return StreamBuilder(
//
stream: controller.stream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return const Text("数据为空");
case ConnectionState.waiting:
return const Text("等待数据流");
case ConnectionState.active:
if (snapshot.hasError) {
return Text("错误:${snapshot.error}");
} else {
return Text("内容为:${snapshot.data}");
}
case ConnectionState.done:
return const Text("数据流已关闭");
}
return Container();
},
);
}
// 关闭数据流
@override
void dispose() {
super.dispose();
controller.close();
}
实现点击按钮即可更新数据,必须将其分为两个组件进行加载,才能进行达到这个效果。
// 得到数据
getFuture() {
return FutureBuilder(
future: Future.delayed(Duration(seconds: 1), () => _getData()),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: [Text("${snapshot.data}")],
);
}
if (snapshot.hasError) {
return Icon(Icons.error);
}
return CircularProgressIndicator();
});
}
// 网络请求数据
_getData() async {
String url =
"http://ws.webxml.com.cn/WebServices/WeatherWS.asmx/getRegionCountry?";
var request = await HttpClient().getUrl(Uri.parse(url));
var response = await request.close();
var body = response.transform(utf8.decoder).join();
return body;
}
class FutureBuilderDemo extends StatefulWidget {
const FutureBuilderDemo({Key? key}) : super(key: key);
@override
State<FutureBuilderDemo> createState() => _FutureBuilderDemoState();
}
// 父组件
class _FutureBuilderDemoState extends State<FutureBuilderDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("FutureBuilder"),
),
body: SingleChildScrollView(
child: Center(
child: Container(
width: 300,
// height: 300,
color: Colors.amber,
child: Test(),
)),
),
);
}
}
//子组件
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
@override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
bool flag = false;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: Text("点击获得数据"),
),
flag == false ? Text("") : getFuture(),
],
);
}
}
// 定义一个死循环,当while每次为true,就build一次
getTime() async* {
while (true) {
await Future.delayed(Duration(seconds: 1));
yield DateTime.now();
}
}
class _TimeDemoState extends State<TimeDemo> {
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: getTime(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
print('build');
switch (snapshot.connectionState) {
case ConnectionState.none:
return const Text("没有值");
case ConnectionState.waiting:
return const Text("没有值");
case ConnectionState.active:
if (snapshot.hasError) {
return const Text("出错了");
} else {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
snapshot.data.toString().substring(0, 10),
style: const TextStyle(fontSize: 28),
),
Text(snapshot.data.toString().substring(10, 19)),
],
);
}
case ConnectionState.done:
return Text("数据流已关闭");
}
},
);
}
}
王叔的StreamBuilder和FutureBilder
https://www.bilibili.com/video/BV1Di4y1V78M?spm_id_from=333.999.0.0
https://www.bilibili.com/video/BV165411V7PS/?spm_id_from=333.788
《Flutter实战·第二版》
https://book.flutterchina.club/chapter7/futurebuilder_and_streambuilder.html