child: Container(
child: Image.network(
“http://pic.baike.soso.com/p/20130828/20130828161137-1346445960.jpg”,
alignment: Alignment.topLeft,
color: Colors.red,
colorBlendMode: BlendMode.colorDodge,
// repeat: ImageRepeat.repeatX,
fit: BoxFit.cover,
),
width: 300.0,
height: 400.0,
decoration: BoxDecoration(
color: Colors.yellow
),
),
);
=================================================================
emmm…不记了
裁剪布局之 ClipRect、ClipRRect、ClipOval、ClipPath、CustomClipper
===================================================================================================================
widget 作用
ClipRect 将 child 剪裁为给定的矩形大小
ClipRRect 将 child 剪裁为圆角矩形
ClipOval 如果 child 为正方形时剪裁之后是圆形,如果 child 为矩形时,剪裁之后为椭圆形
ClipPath 将 child 按照给定的路径进行裁剪
CustomClipper 并不是一个widget,但是使用CustomClipper可以绘制出任何我们想要的形状
=======================================================================
实现圆角图片
return Center(
child: Container(
width: 300.0,
height: 300.0,
decoration: BoxDecoration(
color: Colors.yellow,
borderRadius: BorderRadius.circular(150),
image: DecorationImage(
image: NetworkImage(
“http://pic.baike.soso.com/p/20130828/20130828161137-1346445960.jpg”,
),
fit: BoxFit.cover
)
),
),
);
实现圆形图片
return Center(
child: Container(
child: ClipOval(
child: Image.network(
“https://www.itying.com/images/201905/thumb_img/1101_thumb_G_1557845381862.jpg”,
width: 150.0,
height: 150.0,
),
),
),
);
ClipOval
new ClipOval(
child: new Image.asset(Utils.getImgPath(‘ali_connors’)),
)
② CircleAvatar
new CircleAvatar(
radius: 36.0,
backgroundImage: AssetImage(
Utils.getImgPath(‘ali_connors’),
),
)
③ BoxDecoration BoxShape.circle
new Container(
width: 72.0,
height: 72.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: AssetImage(
Utils.getImgPath(‘ali_connors’),
),
),
),
)
① ClipRRect
new ClipRRect(
borderRadius: BorderRadius.circular(6.0),
child: new Image.asset(Utils.getImgPath(‘ali_connors’)),
)
② BoxDecoration BoxShape.rectangle
new Container(
width: 88.0,
height: 88.0,
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(6.0),
image: DecorationImage(
image: AssetImage(
Utils.getImgPath(‘ali_connors’),
),
),
),
=================================================================
列表布局是我们项目开发中最常用的一种布局方式。Flutter 中我们可以通过 ListView 来定义 列表项,支持垂直和水平方向展示。通过一个属性就可以控制列表的显示方向。列表有一下 分类:
**1、垂直列表(宽度自动扩展,设置宽度无效)**可以在外层包Container控制
2、垂直图文列表
**3、水平列表(高度自动扩展,设置高度无效)**可以在外层包Container控制
4、动态列表
5、矩阵式列表(网格布局)
===============================================================
| 名称 | 类型 | 说明 |
| :-: | :-: | :-: |
| scrollDirection | Axis | Axis.horizontal 水平列表
Axis.vertical 垂直列表 |
| padding | EdgeInsetsGeometry | 内边距 |
| resolve | bool | 组件反向排序 |
| children | List | 列表元素 |
===============================================================
class HomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: ListView(
children: [
ListTile(
leading: Icon(Icons.phone),
title: Text(“this is list”, style: TextStyle(fontSize: 28.0)),
subtitle: Text(‘this is list this is list’),
),
ListTile(
title: Text(“this is list”),
subtitle: Text(‘this is list this is list’),
trailing: Icon(Icons.phone),
),
ListTile(
title: Text(“this is list”),
subtitle: Text(‘this is list this is list’),
),
ListTile(
title: Text(“this is list”),
subtitle: Text(‘this is list this is list’),
),
ListTile(
title: Text(“this is list”),
subtitle: Text(‘this is list this is list’),
)
],
),
);
}
}
===============================================================
class HomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 200.0,
margin: EdgeInsets.all(5),
child: ListView(
scrollDirection: Axis.horizontal,
children: [
Container(
width: 180.0,
color: Colors.lightBlue,
),
Container(
width: 180,
color: Colors.amber,
child: ListView(
children: [
Image.network(
“https://resources.ninghao.org/images/childhood-in-a-picture.jpg”),
SizedBox(height: 16.0),
Text(“这是一个文本信息”,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16.0)),
],
),
)
],
),
);
}
}
=======================================================================
import ‘package:flutter/material.dart’;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text(“Hello Flutter”)),
body: HomeContent(),
),
);
}
}
class HomeContent extends StatelessWidget {
List list = List();
HomeContent() {
for (int i = 0; i < 20; i++) {
list.add(“这是第$i条数据”);
}
print(list);
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: this.list.length,
itemBuilder: (context, index) {
// print(context);
return ListTile(
leading: Icon(Icons.phone),
title: Text(“${list[index]}”),
);
});
}
}
===========================================================================
当数据量很大的时候用矩阵方式排列比较清晰。此时我们可以用网格列表组件 GridView 实现布局。
GridView 创建网格列表有多种方式,下面我们主要介绍两种。
1、可以通过 GridView.count 实现网格布局
2、通过 GridView.builder 实现网格布局
常用属性:
| 名称 | 类型 | 说明 |
| :-: | :-: | :-: |
| scrollDirection | Axis | 滚动方法 |
| padding | EdgeInsetsGeometry | 内边距 |
| resolve | bool | 组件反向排序 |
| crossAxisSpacing | double | 水平子 Widget 之间间距 |
| mainAxisSpacing | double | 垂直子 Widget 之间间距 |
| crossAxisCount | int | 一行的 Widget 数量 |
| childAspectRatio | double | 子 Widget 宽高比例 |
| children | | [ ] |
| gridDelegate | **SliverGridDelegateWithFix
edCrossAxisCount(常用)
SliverGridDelegateWithMax
CrossAxisExtent** | 控制布局主要用在GridView.builder 里面 |
================================================================================
import ‘package:cc/res/listData.dart’;
import ‘package:flutter/material.dart’;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text(“Hello Flutter”)),
body: LayoutContent(),
),
);
}
}
class LayoutContent extends StatelessWidget {
List _getListData() {
var tempList = listData.map((value) {
return Container(
child: Column(
children: [
Image.network(value[“imageUrl”]),
SizedBox(height: 12),
Text(value[“title”],
textAlign: TextAlign.center, style: TextStyle(fontSize: 20))
],
),
decoration: BoxDecoration(
border: Border.all(
color: Color.fromRGBO(230, 230, 230, 0.9), width: 1.0)),
);
});
// (‘124124’,‘124214’)
return tempList.toList();
}
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 20,
mainAxisSpacing: 20,
// childAspectRatio:0.7,
children: this._getListData(),
);
}
}
==================================================================================
import ‘package:cc/res/listData.dart’;
import ‘package:flutter/material.dart’;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text(“Hello Flutter”)),
body: LayoutContent(),
),
);
}
}
class LayoutContent extends StatelessWidget {
Widget _getListData(context, index) {
return Container(
child: Column(
children: [
Image.network(listData[index][“imageUrl”]),
SizedBox(height: 12),
Text(listData[index][“title”],
textAlign: TextAlign.center, style: TextStyle(fontSize: 20))
],
),
decoration: BoxDecoration(
border: Border.all(
color: Color.fromRGBO(230, 230, 230, 0.9), width: 1.0)),
);
}
@override
Widget build(BuildContext context) {
return GridView.builder(
itemCount: listData.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
//横轴元素个数
crossAxisCount: 2,
//纵轴间距
mainAxisSpacing: 20.0,
//横轴间距
crossAxisSpacing: 10.0,
//子组件宽高长度比例
childAspectRatio: 1.0),
itemBuilder: this._getListData,
);
}
}
======================================================================
在 html 中常见的布局标签都有 padding 属性,但是 Flutter 中很多 Widget 是没有 padding 属性。这个时候我们可以用 Padding 组件处理容器与子元素直接的间距。
| 属性 | 说明 |
| :-: | :-: |
| padding | padding 值, EdgeInsetss 设置填充的值 |
| child | 子组件 |
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.fromLTRB(0, 0, 10, 0),
child: GridView.count(
crossAxisCount: 2,
childAspectRatio: 1.5,
children: [
Padding(
padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Image.network(“https://www.itying.com/images/flutter/1.png”,
fit: BoxFit.cover),
),
Padding(
padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Image.network(“https://www.itying.com/images/flutter/2.png”,
fit: BoxFit.cover),
),
Padding(
padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Image.network(“https://www.itying.com/images/flutter/3.png”,
fit: BoxFit.cover),
),
Padding(
padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Image.network(“https://www.itying.com/images/flutter/4.png”,
fit: BoxFit.cover),
),
Padding(
padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Image.network(“https://www.itying.com/images/flutter/5.png”,
fit: BoxFit.cover),
),
Padding(
padding: EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Image.network(“https://www.itying.com/images/flutter/6.png”,
fit: BoxFit.cover),
),
],
),
);
}
}
=====================================================================
| 属性 | 说明 |
| :-: | :-: |
| mainAxisAlignment | 主轴的排序方式 |
| crossAxisAlignment | 次轴的排序方式 |
| children | 组件子元素 |
import ‘package:flutter/material.dart’;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text(“Hello Flutter”)),
body: LayoutDemo(),
),
);
}
}
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 700,
width: 500,
color: Colors.black26,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconContainer(Icons.home, color: Colors.red),
IconContainer(Icons.home, color: Colors.blue),
IconContainer(Icons.home, color: Colors.orange),
],
),
);
}
}
class IconContainer extends StatelessWidget {
double size;
IconData icon;
Color color;
IconContainer(this.icon, {this.size, this.color = Colors.blue}) {
this.size = 32.0;
}
@override
Widget build(BuildContext context) {
return Container(
width: this.size + 60,
height: this.size + 60,
color: this.color,
child: Center(
child: Icon(
this.icon,
color: Colors.white,
size: this.size,
)),
);
}
}
========================================================================
| 属性 | 说明 |
| :-: | :-: |
| mainAxisAlignment | 主轴的排序方式 |
| crossAxisAlignment | 次轴的排序方式 |
| children | 组件子元素 |
import ‘package:flutter/material.dart’;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text(“Hello Flutter”)),
body: LayoutDemo(),
),
);
}
}
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 700,
width: 500,
color: Colors.black26,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconContainer(Icons.home, color: Colors.red),
IconContainer(Icons.home, color: Colors.blue),
IconContainer(Icons.home, color: Colors.orange),
],
),
);
}
}
class IconContainer extends StatelessWidget {
double size;
IconData icon;
Color color;
IconContainer(this.icon, {this.size, this.color = Colors.blue}) {
this.size = 32.0;
}
@override
Widget build(BuildContext context) {
return Container(
width: this.size + 60,
height: this.size + 60,
color: this.color,
child: Center(
child: Icon(
this.icon,
color: Colors.white,
size: this.size,
)),
);
}
}
=====================================================================================
Expanded 可以用在 Row 和 Column 布局中
| 属性 | 说明 |
| :-: | :-: |
| flex | 元素站整个父 Row /Column 的比例 |
| child | 子元素 |
import ‘package:flutter/material.dart’;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text(“Hello Flutter”)),
body: LayoutDemo(),
),
);
}
}
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10),
child: Row(
// crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(flex: 2, child: IconContainer(Icons.home)),
SizedBox(width: 10),
Expanded(flex: 3, child: IconContainer(Icons.search)),
// SizedBox(width: 10),
// Expanded(child: IconContainer(Icons.send))
],
),
);
}
}
class IconContainer extends StatelessWidget {
double size;
IconData icon;
IconContainer(this.icon, {this.size}) {
this.size = 32.0;
}
@override
Widget build(BuildContext context) {
return Container(
width: 100.0,
height: 100.0,
color: Colors.blue,
child:
Center(child: Icon(this.icon, color: Colors.white, size: this.size)),
);
}
}
===================================================================
Stack 表示堆的意思,我们可以用 Stack 或者 Stack 结合 Align 或者 Stack 结合 Positiond 来实现页面的定位布局
| 属性 | 说明 |
| :-: | :-: |
| alignment | 配置所有子元素的显示位置 |
| children | 子组件 |
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Center(
child: Stack(
alignment: Alignment.topLeft,
children: [
Container(
height: 400,
width: 300,
color: Colors.red,
),
Text(‘我是一个文本’,style: TextStyle(
fontSize: 40,
color: Colors.white
))
],
),
);
}
}
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Center(
child: Stack(
alignment: Alignment(1,0.3),
children: [
Container(
height: 400,
width: 300,
color: Colors.red,
),
Text(‘我是一个文本’,style: TextStyle(
fontSize: 20,
color: Colors.white
))
],
),
);
}
}
======================================================================
Stack 组件中结合 Align 组件可以控制每个子元素的显示位置
| 属性 | 说明 |
| :-: | :-: |
| alignment | 配置所有子元素的显示位置 |
| child | 子组件 |
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Center(
child: Container(
height: 400,
width: 300,
color: Colors.red,
child: Stack(
// alignment: Alignment.center,
children: [
Align(
alignment: Alignment(1,-0.2),
child: Icon(Icons.home,size: 40,color: Colors.white),
),
Align(
alignment: Alignment.center,
child: Icon(Icons.search,size: 30,color: Colors.white),
),
Align(
alignment: Alignment.bottomRight,
child: Icon(Icons.settings_applications,size: 30,color: Colors.white),
)
],
),
),
);
}
}
===========================================================================
Stack 组件中结合 Positioned 组件也可以控制每个子元素的显示位置
| 属性 | 说明 |
| :-: | :-: |
| top | 子元素距离顶部的距离 |
| bottom | 子元素距离底部的距离 |
| left | 子元素距离左侧距离 |
| right | 子元素距离右侧距离 |
| child | 子组件 |
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Center(
child: Container(
height: 400,
width: 300,
color: Colors.red,
child: Stack(
// alignment: Alignment.center,
children: [
Positioned(
// left: 10,
child: Icon(Icons.home,size: 40,color: Colors.white),
),
Positioned(
bottom: 0,
left: 100,
child: Icon(Icons.search,size: 30,color: Colors.white),
),
Positioned(
right: 0,
child: Icon(Icons.settings_applications,size: 30,color: Colors.white),
)
],
),
),
);
}
}
=========================================================================
AspectRatio 的作用是根据设置调整子元素 child 的宽高比。
AspectRatio 首先会在布局限制条件允许的范围内尽可能的扩展,widget 的高度是由宽度和比率决定的,类似于 BoxFit 中的 contain,按照固定比率去尽量占满区域。
如果在满足所有限制条件过后无法找到一个可行的尺寸,AspectRatio 最终将会去优先适应布局限制条件,而忽略所设置的比率。
| 属性 | 说明 |
| :-: | :-: |
| aspectRatio | 宽高比,最终可能不会根据这个值去布局,具体则要看综合因素,外层是否允许按照这种比率进行布局,这只是一个参考值 |
| child | 子组件 |
return Center(
child: Container(
width: 200,
child: AspectRatio(
aspectRatio: 2.0 / 1.0,
child: Container(
color: Colors.red,
),
),
),
);
==================================================================
Card 是卡片组件块,内容可以由大多数类型的 Widget 构成,Card 具有圆角和阴影,这让它看起来有立体感。
| 属性 | 说明 |
| :-: | :-: |
| margin | 外边距 |
| child | 子组件 |
| Shape | Card 的阴影效果,默认的阴影效果为圆角的长方形边。 |
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
children: [
Card(
margin: EdgeInsets.all(10),
child: Column(
children: [
ListTile(
title: Text(
“张三”,
style: TextStyle(fontSize: 28),
),
subtitle: Text(“高级软件工程师”),
),
Divider(),
ListTile(
title: Text(“电话:123123123”),
),
ListTile(
title: Text(“地址:北京市海淀区”),
)
],
),
),
Card(
margin: EdgeInsets.all(10),
child: Column(
children: [
ListTile(
title: Text(
“李四”,
style: TextStyle(fontSize: 28),
),
subtitle: Text(“高级软件工程师”),
),
Divider(),
ListTile(
title: Text(“电话:123123123”),
),
ListTile(
title: Text(“地址:北京市海淀区”),
)
],
),
),
],
);
}
}
============================================================================
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
children: listData.map((value) {
return Card(
margin: EdgeInsets.all(10),
child: Column(
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: Image.network(value[“imageUrl”], fit: BoxFit.cover),
),
ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(value[“imageUrl”]),
),
title: Text(value[“description”]),
subtitle: Text(
value[“description”],
overflow: TextOverflow.ellipsis,
),
)
],
),
);
}).toList());
}
}
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: listData.length,
itemBuilder: (context,index){
return Card(
margin: EdgeInsets.all(10),
child: Column(
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: Image.network(listData[index][“imageUrl”], fit: BoxFit.cover),
),
ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(listData[index][“imageUrl”]),
),
title: Text(listData[index][“description”]),
subtitle: Text(
listData[index][“description”],
overflow: TextOverflow.ellipsis,
),
)
],
),
);
},
);
}
}
==============================================================================
Flutter 中通过 RaisedButton 定义一个按钮。RaisedButton 里面有很多的参数,这一讲我们只是简单的进行使用。
return RaisedButton(
child: Text(“Flutter”),
textColor: Theme.of(context).accentColor,
onPressed: (){
});
==================================================================
Wrap 可以实现流布局,单行的 Wrap 跟 Row 表现几乎一致,单列的 Wrap 则跟 Row 表 现几乎一致。但 Row 与 Column 都是单行单列的,Wrap 则突破了这个限制,mainAxis 上空 间不足时,则向 crossAxis 上去扩展显示。
| 属性 | 说明 |
| :-: | :-: |
| direction | 主轴的方向,默认水平 |
| alignment | 主轴的对其方式 |
| spacing | 主轴方向上的间距 |
| textDirection | 文本方向 |
| verticalDirection | 定义了 children 摆放顺序,默认是 down,见Flex 相关属性介绍。 |
| runAlignment | run 的对齐方式。run 可以理解为新的行或者列,如果是水平方向布局的话,run 可以理解为新的一行 |
| runSpacing | run 的间距 |
import ‘package:flutter/material.dart’;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text(“Hello Flutter”)),
body: LayoutDemo(),
),
);
}
}
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Wrap(
spacing: 10,
runSpacing: 10,
alignment: WrapAlignment.spaceEvenly,
children: [
MyButton(“第 1 集”),
MyButton(“第 2 集”),
MyButton(“第 3 集”),
MyButton(“第 4 集”),
MyButton(“第 5 集”),
MyButton(“第 6 集第 6 集”),
MyButton(“第 7 集”),
MyButton(“第 8 集第 6 集”),
MyButton(“第 9 集”),
MyButton(“第 10 集”),
MyButton(“第 11 集”),
],
);
}
}
class MyButton extends StatelessWidget {
final String text;
const MyButton(this.text, {Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return RaisedButton(
child: Text(this.text),
textColor: Theme.of(context).accentColor,
onPressed: () {});
}
}
===================================================================
在 Flutter 中自定义组件其实就是一个类,这个类需要继承 StatelessWidget/StatefulWidget。
StatelessWidget 是无状态组件,状态不可变的 widget
StatefulWidget 是有状态组件,持有的状态可能在 widget 生命周期改变。通俗的讲:如果我们想改变页面中的数据的话这个时候就需要用到 StatefulWidget
import ‘package:flutter/material.dart’;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text(“Hello Flutter”)),
body: HomePage(),
),
);
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State {
int count = 0;
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Chip(label: Text(“${this.count}”)),
RaisedButton(
child: Text(“增加”),
onPressed: () {
// print(this.count);
setState(() {
this.count++;
});
})
],
),
);
}
}
=================================================================================
BottomNavigationBar 是底部导航条,可以让我们定义底部 Tab 切换,bottomNavigationBar是 Scaffold 组件的参数。
BottomNavigationBar 常见的属性
| 属性名 | 说明 |
| :-: | :-: |
| items | List 底 部 导 航条按钮集合 |
| iconSize | icon |
| currentIndex | 默认选中第几个 |
| fixedColor | 选中的颜色 |
| type | BottomNavigationBarType.fixed
BottomNavigationBarType.shifting |
| | (上面解决4个底部导航显示出错) |
class Tabs extends StatefulWidget {
Tabs({Key key}) : super(key: key);
_TabsState createState() => _TabsState();
}
class _TabsState extends State {
int _currentIndex=0;
List _pageList=[
HomePage(),
CategoryPage(),
SettingPage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(“Flutter Demo”),
),
body: this._pageList[this._currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: this._currentIndex, //配置对应的索引值选中
onTap: (int index){
setState(() { //改变状态
this._currentIndex=index;
});
},
iconSize:36.0, //icon的大小
fixedColor:Colors.red, //选中的颜色
type:BottomNavigationBarType.fixed, //配置底部tabs可以有多个按钮
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text(“首页”)
),
BottomNavigationBarItem(
icon: Icon(Icons.category),
title: Text(“分类”)
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Text(“设置”)
)
],
),
);
}
}
=============================================================
Flutter 中的路由通俗的讲就是页面跳转。在 Flutter 中通过 Navigator 组件管理路由导航。
并提供了管理堆栈的方法。如:Navigator.push 和 Navigator.pop
Flutter 中给我们提供了两种配置路由跳转的方式:1、基本路由 2、命名路由
=================================================================
比如我们现在想从 HomePage 组件跳转到 SearchPage 组件。
1、需要在 HomPage 中引入 SearchPage.dart
import ‘…/SearchPage.dart’;
2、在 HomePage 中通过下面方法跳转
RaisedButton(
child: Text(“跳转到搜索页面”),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SerachPage()
)
);
},
color: Theme.of(context).accentColor,
textTheme: ButtonTextTheme.primary,
)
===================================================================
比如我们现在想从 HomePage 组件跳转到 SearchPage 组件传值。
1、需要在 HomPage 中引入 SearchPage.dart
import ‘…/SearchPage.dart’;
2、在 HomePage 中通过下面方法跳转
RaisedButton(
child: Text(“跳转到搜索页面”),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SerachPage(title:“表单”) //传值 SerachPage加构造函数并传参数
)
);
},
color: Theme.of(context).accentColor,
textTheme: ButtonTextTheme.primary,
)
===============================================================
1、配置路由
return MaterialApp(
// home:Tabs(),
initialRoute: ‘/’, //初始化的时候加载的路由
routes: {
‘/’:(contxt)=>Tabs(),
‘/search’:(contxt) =>SearchPage(),
‘/form’: (context) => FormPage(),
},
);
2、路由跳转
RaisedButton(
child: Text(“跳转到搜索页面”),
onPressed: () {
Navigator.pushNamed(context, ‘/search’);
},
color: Theme.of(context).accentColor,
textTheme: ButtonTextTheme.primary
)d
===================================================================
花里胡哨
========================================================================
抽个鸡儿
===================================================================
Navigator.of(context).pop();
===============================================================
比如我们从用户中心页面跳转到了 registerFirst 页面,然后从 registerFirst 页面通过pushReplacementNamed 跳转到了 registerSecond 页面。这个时候当我们点击 registerSecond的返回按钮的时候它会直接返回到用户中心。
Navigator.of(context).pushReplacementNamed(‘/registerSecond’); //命名路由替换
// 普通路由替换
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context)=>Second())
);
=================================================================
比如我们从用户中心跳转到 registerFirst 页面,然后从 registerFirst 页面跳转到 registerSecond页面,然后从 registerSecond 跳转到了 registerThird 页面。这个时候我们想的是 registerThird注册成功后返回到用户中心。 这个时候就用到了返回到根路由的方法。
Navigator.of(context).pushAndRemoveUntil(
new MaterialPageRoute(builder: (context) => new Tabs(index:1)),
(route) => route == null
);
==============================================================================
| 属性 | 描述 |
| :-: | :-: |
| leading | 在标题前面显示的一个控件,在首页通常显示应用的 logo;在其他界面通常显示为返回按钮 |
| title | 标题,通常显示为当前界面的标题文字,可以放组件 |
| actions | 在标题后面显示的一个控件,通常使用 IconButton 来表示,可以放按钮组 |
| bottom | 通常放 tabBar,标题下面显示一个 Tab 导航栏 |
| backgroundColor | 导航背景颜色 |
| iconTheme | 图标样式 |
| textTheme | 文字样式 |
| centerTitle | 标题是否居中显示 |
class AppBardemoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.red,
leading: IconButton(
icon: Icon(Icons.menu),
tooltip: “Search”,
onPressed: () {
print(‘Menu Pressed’);
}),
title: Text(‘FlutterDemo’),
actions: [
IconButton(
icon: Icon(Icons.search),
tooltip: “Search”,
onPressed: () {
print(‘Search Pressed’);
}),
IconButton(
icon: Icon(Icons.more_horiz),
tooltip: “more_horiz”,
onPressed: () {
print(‘more_horiz Pressed’);
})
],
),
body: Text(‘这是Appbar’),
);
}
}
AppBar 中自定义 TabBar 实现顶部 Tab 切换
=========================================================================================
TabBar 常见属性:
| 属性 | 描述 |
| :-: | :-: |
| tabs | 显示的标签内容,一般使用 Tab 对象,也可以是其他的 Widget |
| controller | TabController 对象 |
| isScrollable | 是否可滚动(是指有很多个appbar时滚动appbar,左右滚动appbar。不是滚动内容) |
| indicatorColor | 指示器颜色 |
| indicatorWeight | 指示器高度 |
| indicatorPadding | 底部指示器的 Padding |
| indicator | 指示器 decoration,例如边框等 |
| indicatorSize | 指示器大小计算方式,TabBarIndicatorSize.label 跟文字等宽,TabBarIndicatorSize.tab 跟每个 tab 等宽 |
| labelColor | 选中 label 颜色 |
| labelStyle | 选中 label 的 Style |
| labelPadding | 每个 label 的 padding 值 |
| unselectedLabelColor | 未选中 label 颜色 |
| unselectedLabelStyle | 未选中 label 的 Style |
import ‘package:flutter/material.dart’;
class AppBardemoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController( //DefaultTabController在MaterialApp之后,Scaffold之前
length: 2, //如果页面通过路由挂载,直接return DefaultTabController
child: Scaffold(
appBar: AppBar(
title: TabBar(
tabs: [
Tab(text: ‘热门’),
Tab(text: “123”),
],
),
),
body: TabBarView(
children: [
ListView(
children: [
ListTile(title: Text(“这是第一个 tab”)),
ListTile(title: Text(“这是第一个 tab”)),
ListTile(title: Text(“这是第一个 tab”))
],
),
ListView(
children: [
ListTile(title: Text(“这是第一个 tab”)),
ListTile(title: Text(“这是第一个 tab”)),
ListTile(title: Text(“这是第一个 tab”))
],
),
],
),
),
),
);
}
}
===========================================================================
把TabBar放在titile里面
import ‘package:flutter/material.dart’;
class AppBardemoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
isScrollable: true, //如果多个按钮的话可以滑动
// backgroundColor: Colors.red,
leading: IconButton(
icon: Icon(Icons.arrow_back),
tooltip: “Search”,
onPressed: () {
Navigator.of(context).pop();
}),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
flex: 1,
child: TabBar(
tabs: [Tab(text: “热门”), Tab(text: “推荐”)],
))
],
),
),
body: TabBarView(
children: [
ListView(
children: [
ListTile(title: Text(“这是第一个 tab”)),
ListTile(title: Text(“这是第一个 tab”)),
ListTile(title: Text(“这是第一个 tab”))
],
),
ListView(
children: [
ListTile(title: Text(“这是第一个 tab”)),
ListTile(title: Text(“这是第一个 tab”)),
ListTile(title: Text(“这是第一个 tab”))
],
),
],
),
),
),
);
}
}
AppBar 中自定义 TabBar 实现 Tabs 的另一种方法。
=============================================================================================
TabController需要继承有状态组件
import ‘package:flutter/material.dart’;
class AppBardemoPage extends StatefulWidget {
@override
_AppBardemoPageState createState() => _AppBardemoPageState();
}
class _AppBardemoPageState extends State
with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState() {
// TODO: implement initState
super.initState();
_tabController = new TabController(
vsync: this,
length: 2
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(“AppBardemoPage”),
bottom: TabBar(
controller: this._tabController, //注意
tabs: [
Tab(text: “热销”), Tab(text: “推荐”),
],
),
),
body: TabBarView(
controller: this._tabController, //注意
children: [
Center(child: Text(“热销”)),
Center(child: Text(“推荐”))
],
),
);
}
}
=============================================================================
在 Scaffold 组件里面传入 drawer 参数可以定义左侧边栏,传入 endDrawer 可以定义右侧边栏。侧边栏默认是隐藏的,我们可以通过手指滑动显示侧边栏,也可以通过点击按钮显示侧边栏。
return Scaffold(
appBar: AppBar(
title: Text(“Hello Flutter”),
),
drawer: Drawer(
child: Text(“左侧边栏”),
),
endDrawer: Drawer(
child: Text(“右侧边栏”),
),
);
=======================================================================
常见属性:
| 属性 | 描述 |
| :-: | :-: |
| decoration | 设置顶部背景颜色 |
| child | 配置子元素 |
| padding | 内边距 |
| margin | 外边距 |
drawer: Drawer(
child: Column(
children: [
DrawerHeader(
decoration: BoxDecoration(
color: Colors.yellow,
image: DecorationImage(
image: NetworkImage(
“https://www.itying.com/images/flutter/2.png”),
fit: BoxFit.cover)),
child: ListView(
children: [Text(“我是一个头部”)],
),
),
ListTile(
title: Text(“个人中心”),
leading: CircleAvatar(
child: Icon(Icons.people),
),
),
Divider(),
ListTile(
title: Text(“系统设置”),
leading: CircleAvatar(
child: Icon(Icons.settings),
),
),
Divider(),
],
),
),
===================================================================================
| 属性 | 描述 |
| :-: | :-: |
| decoration | 设置顶部背景颜色 |
| accountName | 账户名称 |
| accountEmail | 账户邮箱 |
| currentAccountPicture | 用户头像 |
| otherAccountsPictures | 用来设置当前账户其他账户头像 |
| margin | |
drawer: Drawer(
child: Column(
children: [
UserAccountsDrawerHeader(
accountName: Text(“喵喵喵?”),
accountEmail: Text(“xxx@xxx.com”),
currentAccountPicture: CircleAvatar(// 自动处理成圆形,不需要再设置图片fit
backgroundImage:
NetworkImage(“https://www.itying.com/images/flutter/3.png”),
),
decoration: BoxDecoration(
color: Colors.yellow,
image: DecorationImage(
image: NetworkImage(
“https://www.itying.com/images/flutter/2.png”),
fit: BoxFit.cover)),
otherAccountsPictures: [
Image.network(“https://www.itying.com/images/flutter/4.png”),
Image.network(“https://www.itying.com/images/flutter/5.png”),
Image.network(“https://www.itying.com/images/flutter/6.png”),
],
),
ListTile(
title: Text(“个人中心”),
leading: CircleAvatar(
child: Icon(Icons.people),
),
),
Divider(),
ListTile(
title: Text(“系统设置”),
leading: CircleAvatar(
child: Icon(Icons.settings),
),
),
Divider(),
],
),
),
==================================================================
onTap: () {
Navigator.of(context).pop();
Navigator.pushNamed(context, “/tabBarController”);
},
=================================================================
Flutter 里有很多的 Button 组件很多,常见的按钮组件有:RaisedButton、FlatButton、IconButton、OutlineButton、ButtonBar、FloatingActionButton 等。
RaisedButton :凸起的按钮,其实就是 Material Design 风格的 Button
FlatButton :扁平化的按钮
OutlineButton:线框按钮
IconButton :图标按钮
ButtonBar:按钮组
FloatingActionButton:浮动按钮
=====================================================================
| 属性名称 | 值类型 | 属性值 |
| :-: | :-: | :-: |
| onPressed | VoidCallback,一般接收一个方法 | 必填参数,按下按钮时触发的回调,接收一个方法,传 null 表示按钮禁用,会显示禁用相关样式 |
| child | Widget | 文本控件 |
| textColor | Color | 文本颜色 |
| color | Color | 按钮的颜色 |
| disabledColor | Color | 按钮禁用时的颜色 |
| disabledTextColor | Color | 按钮禁用时的文本颜色 |
| splashColor | Color | 点击按钮时水波纹的颜色 |
| highlightColor | Color | 点击(长按)按钮后按钮的颜色 |
| elevation | double | 阴影的范围,值越大阴影范围越大 |
| padding | | 内边距 |
| shape | | 设置按钮的形状
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(10),
) //圆角按钮
shape: CircleBorder(
side: BorderSide(
color: Colors.white,
)
) //圆形按钮 |
class ButtonDemoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(“按钮演示页面”),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RaisedButton(
child: Text(‘普通按钮’),
onPressed: () {
print(‘点击了’);
},
),
SizedBox(width: 20),
RaisedButton(
child: Text(‘有颜色的按钮’),
textColor: Colors.white,
color: Colors.blue,
onPressed: () {
print(‘点击了’);
},
),
SizedBox(width: 20),
RaisedButton(
child: Text(‘阴影按钮’),
textColor: Colors.white,
color: Colors.blue,
elevation: 10,
onPressed: () {
print(‘点击了’);
},
)
],
),
SizedBox(height: 40),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 60,
width: 200,
child: RaisedButton(
child: Text(‘有宽高的按钮’),
textColor: Colors.white,
color: Colors.blue,
elevation: 10,
onPressed: () {
print(‘点击了’);
},
))
],
),
SizedBox(height: 40),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Container(
height: 60,
margin: EdgeInsets.all(20),
child: RaisedButton(
child: Text(‘全屏按钮’),
textColor: Colors.white,
color: Colors.blue,
elevation: 10,
onPressed: () {
print(‘点击了’);
},
),
))
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Container(
height: 60,
margin: EdgeInsets.all(20),
child: RaisedButton(
child: Text(‘带圆角的按钮’),
textColor: Colors.white,
color: Colors.blue,
elevation: 10,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
onPressed: () {
print(‘点击了’);
},
),
))
],
)
],
),
),
);
}
}
==================================================================================
FloatingActionButton简称FAB ,可以实现浮动按钮,也可以实现类似闲鱼app的地步凸起导航
| 属性名称 | 属性值 |
| :-: | :-: |
| child | 子视图,一般为 Icon,不推荐使用文字 |
| tooltip | FAB 被长按时显示,也是无障碍功能 |
| backgroundColor | 背景颜色 |
| elevation | 未点击的时候的阴影 |
| hignlightElevation | 点击时阴影值,默认 12.0 |
| onPressed | 点击事件回调 |
| shape | 可以定义 FAB 的形状等 |
| mini | 是否是 mini 类型默认 false |
=================================================================
Flutter 中常见的表单有 TextField 单行文本框,TextField 多行文本框、CheckBox、Radio、SwitchCheckboxListTile、RadioListTile、SwitchListTile、Slide.
==========================================================================
TextField 表单常见属性:
| 属性 | 描述 |
| :-: | :-- |
| maxLines | 设置此参数可以把文本框改为多行文本框 |
| onChanged | 文本框改变的时候触发的事件 |
| decoration | hintText 类似 html 中的 placeholder
border 配置文本框边框 OutlineInputBorder 配合使用
labelText lable 的名称
labelStyle 配置 lable 的样式 |
| obscureText | obscureText |
| controller | controller 结合 TextEditingController()可以配置表单默认显示的内容 |
TextField(
maxLines: 10,
// obscureText: true,
decoration:
InputDecoration(
hintText: “密码框”,
border: OutlineInputBorder()
),
)d
var _username = TextEditingController();
@override
void initState() {
// TODO: implement initState
super.initState();
_username.text = ‘这是文本框初始值’;
}
TextField(
controller: _username,
onChanged: (value) {
// print(value);
setState(() {
this._username.text = value;
});
},
decoration: InputDecoration(
hintText: “请输入你的内容”,
),
)
Checkbox、CheckboxListTile 多选框组件
==========================================================================================
Checkbox 常见属性:
| 属性 | 描述 |
| :-: | :-: |
| value | true 或者 false |
| onChanged | 改变的时候触发的事件 |
| activeColor | 选中的颜色、背景颜色 |
| checkColor | 选中的颜色、Checkbox 里面对号的颜色 |
CheckboxListTile 常见属性:
| 属性 | 描述 |
| :-: | :-: |
| value | true 或者 false |
| onChanged | 改变的时候触发的事件 |
| activeColor | 选中的颜色、背景颜色 |
| title | 标题 |
| subtitle | 二级标题 |
| secondary | 配置图标或者图片 |
| selected | 选中的时候文字颜色是否跟着改变 |
Checkbox(
value: _isSelected,
onChanged: (v) {
print(v);
setState(() {
this._isSelected = v;
});
},
activeColor: Colors.red,
checkColor: Colors.blue,
)d
CheckboxListTile(
value: _isSelected,
title: Text(“这是一个标题”),
subtitle: Text(“这是二级标题”),
onChanged: (v) {
setState(() {
this._isSelected = v;
});
},
activeColor: Colors.red,
secondary:
Image.network(“https://www.itying.com/images/flutter/1.png”),
selected: _isSelected,
)
=====================================================================================
Radio 常用属性:
| 属性 | 描述 |
| :-: | :-: |
| value | 单选的值 |
| onChanged | 改变时触发 |
| activeColor | 选中的颜色、背景颜色 |
| groupValue | 选择组的值 |
RadioListTile 常用属性:
| 属性 | 描述 |
| :-: | :-: |
| value | true 或者 false |
| onChanged | 改变的时候触发的事件 |
| activeColor | 选中的颜色、背景颜色 |
| title | 标题 |
| subtitle | 二级标题 |
| secondary | 配置图标或者图片 |
| groupValue | 选择组的值 |
int _groupValue=1;
Radio(
value: 0,
onChanged: (v) {
setState(() {
this._groupValue = v;
});
},
activeColor: Colors.red,
groupValue: _groupValue,
),
Radio(
value: 1,
onChanged: (v) {
setState(() {
this._groupValue = v;
});
},
activeColor: Colors.red,
groupValue: _groupValue,
)
int _groupValue = 1;
_handelChange(v) {
setState(() {
_groupValue = v;
});
}
RadioListTile(
value: 1,
title: Text(“nodejs 视频教程”),
subtitle: Text(“egg.js 视频教程”),
secondary:
Image.network(“https://www.itying.com/images/flutter/1.png”),
groupValue: _groupValue,
onChanged: _handelChange,
),
Divider(),
RadioListTile(
value: 0,
title: Container(
height: 60,
child: Text(“这是文本”),
color: Colors.red,
),
subtitle: Text(“egg.js 视频教程”),
secondary:
Image.network(“https://www.itying.com/images/flutter/1.png”),
groupValue: _groupValue,
onChanged: _handelChange,
)
====================================================================
| 属性 | 描述 |
| :-: | :-: |
| value | 单选的值 |
| onChanged | 改变时触发 |
| activeColor | 选中的颜色、背景颜色 |
=================================================================
日期转化成时间戳:
var now = new DateTime.now();
print(now.millisecondsSinceEpoch);//单位毫秒,13 位时间戳
时间戳转化成日期:
var now = new DateTime.now();
var a=now.millisecondsSinceEpoch; //时间戳
print(DateTime.fromMillisecondsSinceEpoch(a));
================================================================================
文档:https://pub.dev/packages/date_format
========================================================================
日期组件:
var _datetime = DateTime.now();
_showDatePicker() async {
var date = await showDatePicker(
context: context,
initialDate: _datetime,
firstDate: DateTime(1900),
lastDate: DateTime(2050));
if (date == null) return;
print(date);
setState(() {
_datetime = date;
});
}
时间组件:
var _time = TimeOfDay(hour: 9, minute: 20);
_showTimePicker() async {
var time = await showTimePicker(context: context, initialTime: _time);
if (time == null) return;
print(time);
setState(() {
this._time = time;
});
}
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
child: Row(
children: [
Text(“${formatDate(_datetime, [yyyy, ‘-’, mm, ‘-’, dd])}”),
Icon(Icons.arrow_drop_down)
],
),
onTap: _showDatePicker,
),
InkWell(
child: Row(
children: [
Text(“${this._time.format(context)}”),
Icon(Icons.arrow_drop_down)
],
),
onTap: _showTimePicker,
)
],
)
],
)
============================================================================
http://bbs.itying.com/topic/5cfb2a12f322340b2c90e764
====================================================================
https://pub.dev/packages/flutter_cupertino_date_picker
懒得记了。。。
================================================================
地址:https://pub.dev/packages/flutter_swiper
import ‘package:flutter/material.dart’;
import ‘package:flutter_swiper/flutter_swiper.dart’;
class SwiperPage extends StatefulWidget {
SwiperPage({Key key}) : super(key: key);
_SwiperPageState createState() => _SwiperPageState();
}
class _SwiperPageState extends State {
List
{“url”: “https://www.itying.com/images/flutter/1.png”},
{“url”: “https://www.itying.com/images/flutter/2.png”},
{“url”: “https://www.itying.com/images/flutter/3.png”}
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(‘轮播图组件演示’),
),
body: Column(
children: [
Container(
width: double.infinity,
child: AspectRatio(
aspectRatio: 16 / 9,
child: new Swiper(
itemBuilder: (BuildContext context, int index) {
return new Image.network(
this.list[index][“url”],
fit: BoxFit.fill,
);
},
itemCount: list.length,
pagination: new SwiperPagination(),
autoplay: true,
// control: new SwiperControl(),
),
),
)
],
),
);
}
}
========================================================================
var alertRel = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(“提示!”),
content: Text(“确定要删除吗”),
actions: [
FlatButton(
child: Text(“取消”),
onPressed: () {
Navigator.pop(context, ‘Cancle’);
},
),
FlatButton(
child: Text(“确定”),
onPressed: () {
Navigator.pop(context, ‘Ok’);
},
)
],
);
});
=========================================================================
var simpleRel = await showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: Text(“select 单选按钮框”),
children: [
SimpleDialogOption(
child: Text(“Option A”),
onPressed: () {
Navigator.pop(context, ‘Option A’);
},
),
Divider(),
SimpleDialogOption(
child: Text(“Option B”),
onPressed: () {
Navigator.pop(context, ‘Option B’);
},
),
Divider(),
SimpleDialogOption(
child: Text(“Option C”),
onPressed: () {
Navigator.pop(context, ‘Option C’);
},
)
],
);
});
=================================================================================
var actionSheet = await showModalBottomSheet(
context: context,
builder: (builder) {
return Container(
height: 200, //高度不设置显示一半
child: Column(
children: [
ListTile(
title: Text(“分享 A”),
onTap: () {
Navigator.pop(context, ‘A’);
},
),
ListTile(
title: Text(“分享 B”),
onTap: () {
Navigator.pop(context, ‘B’);
},
),
ListTile(
title: Text(“分享 C”),
onTap: () {
Navigator.pop(context, ‘C’);
},
)
],
),
);
});
======================================================================
https://pub.dev/packages/fluttertoast
Fluttertoast.showToast(
msg: “This is Short Toast”,
toastLength: Toast.LENGTH_SHORT,
timeInSecForIos: 1);
====================================================================
自定义 Dialog 对象,需要继承 Dialog 类,尽管 Dialog 提供了 child 参数可以用来写视图界面,但是往往会达不到我们想要的效果,因为默认的 Dialog 背景框是满屏的。如果我们想完全定义界面,就需要重写 build 函数。
import ‘package:flutter/material.dart’;
class LoadingDialog extends Dialog {
final String text;
LoadingDialog(this.text);
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Material(
//创建透明层
type: MaterialType.transparency, //透明类型 child: new Center(
child: Container(
width: 300,
height: 200,
color: Colors.white,
child: Column(
children: [
Padding(
padding: EdgeInsets.all(10),
child: Stack(
children: [
Align(
alignment: Alignment.center,
child: Text(“关于我们”),
),
Align(
alignment: Alignment.centerRight,
child: InkWell(
child: Icon(Icons.close),
onTap: () {
Navigator.pop(context);
},
),
)
],
),
),
Divider(),
Column(
children: [
Container(
height: 40,
child: Text(this.text),
)
],
)
],
),
),
);
}
}
==============================================================
import ‘dart:async’;
_showTimer(context) {
var timer;
timer = Timer.periodic(Duration(milliseconds: 1500), (t) {
print(‘执行’);
Navigator.pop(context);
t.cancel();
});
}
=======================================================================
import ‘dart:async’;
import ‘package:flutter/material.dart’;
class LoadingDialog extends Dialog {
final String text;
LoadingDialog(this.text);
_showTimer(context) {
var timer;
timer = Timer.periodic(Duration(milliseconds: 1500), (t) {
print(‘执行’);
Navigator.pop(context);
t.cancel();
});
}
@override
Widget build(BuildContext context) {
_showTimer(context);
return Material(
//创建透明层
type: MaterialType.transparency, //透明类型
child: Center(
child: Container(
width: 300,
height: 200,
color: Colors.white,
child: Column(
children: [
Padding(
padding: EdgeInsets.all(10),
child: Stack(
children: [
Align(
alignment: Alignment.center,
child: Text(“关于我们”),
),
Align(
alignment: Alignment.centerRight,
child: InkWell(
child: Icon(Icons.close),
onTap: () {
Navigator.pop(context);
},
),
)
],
),
),
Divider(),
Column(
children: [
Container(
height: 40,
child: Text(this.text),
)
],
)
],
),
),
),
);
}
}
===================================================================================
(小项目直接转,大项目用模型类,笔记在下面)
import ‘dart:convert’;
var mapData = {“name”: “张三”, “age”: “20”};
var strData = ‘{“name”:“张三”,“age”:“20”}’;
print(json.encode(mapData)); //Map转换成Json字符串
print(json.decode(strData)); //Json 字符串转化成 Map 类型
==========================================================================
请参考官方文档:https://pub.dev/packages/http
import ‘package:flutter/material.dart’;
import ‘package:http/http.dart’ as http;
import ‘dart:convert’;
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State {
String _news = ‘’;
//请求数据
_getData() async {
var apiUrl = “http://127.0.0.1:8080/”;
var result = await http.get(apiUrl);
if (result.statusCode == 200) {
// print(json.decode(result.body));
setState(() {
this._news = json.decode(result.body)[“msg”];
});
} else {
print(result.statusCode);
}
}
//提交数据
_postData() async {
var apiUrl = “http://127.0.0.1:8080/x”;
var result = await http.post(apiUrl, body: {‘username’: ‘张三’, ‘age’: ‘20’});
if (result.statusCode == 200) {
print(json.decode(result.body));
} else {
print(result.statusCode);
}
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(this._news),
RaisedButton(
child: Text(‘Get请求数据’),
onPressed: _getData,
),
SizedBox(height: 20),
RaisedButton(
child: Text(‘Post提交数据’),
onPressed: _postData,
),
SizedBox(height: 20),
RaisedButton(
child: Text(‘Get请求数据、渲染数据演示demo’),
onPressed: () {
Navigator.pushNamed(context, ‘/http’);
},
),
SizedBox(height: 20),
],
),
);
}
}
map遍历,外层套ListView组件
import ‘package:flutter/material.dart’;
import ‘package:http/http.dart’ as http;
import ‘dart:convert’;
class HttpDemo extends StatefulWidget {
HttpDemo({Key key}) : super(key: key);
_HttpDemoState createState() => _HttpDemoState();
}
class _HttpDemoState extends State {
List _list = [];
@override
void initState() {
// TODO: implement initState
super.initState();
this._getData();
}
_getData() async {
var apiUrl = “http://a.itying.com/api/productlist”;
var result = await http.get(apiUrl);
if (result.statusCode == 200) {
print(result.body);
setState(() {
this._list = json.decode(result.body)[“result”];
/*
{
“result”: [{
“_id”: “5ac0896ca880f20358495508”,
“title”: “精选热菜”,
“pid”: “0”,
}, {
“_id”: “5ac089e4a880f20358495509”,
“title”: “特色菜”,
“pid”: “0”,
}
]
}
*/
});
} else {
print(“失败${result.statusCode}”);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(“请求数据Demo”),
),
body: this._list.length > 0
? ListView(
children: this._list.map((value) { //map遍历,外层套ListView组件
return ListTile(
title: Text(value[“title”]),
);
}).toList(),
-
)
- Text(“加载中…”));
}
}
ListView.builder
import ‘package:flutter/material.dart’;
import ‘package:http/http.dart’ as http;
import ‘dart:convert’;
class HttpDemo extends StatefulWidget {
HttpDemo({Key key}) : super(key: key);
_HttpDemoState createState() => _HttpDemoState();
}
class _HttpDemoState extends State {
List _list = [];
@override
void initState() {
// TODO: implement initState
super.initState();
this._getData();
}
_getData() async {
var apiUrl = “http://a.itying.com/api/productlist”;
var result = await http.get(apiUrl);
if (result.statusCode == 200) {
print(result.body);
setState(() {
this._list = json.decode(result.body)[“result”];
/*
{
“result”: [{
“_id”: “5ac0896ca880f20358495508”,
“title”: “精选热菜”,
“pid”: “0”,
}, {
“_id”: “5ac089e4a880f20358495509”,
“title”: “特色菜”,
“pid”: “0”,
}
]
}
*/
});
} else {
print(“失败${result.statusCode}”);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(“请求数据Demo”),
),
body: this._list.length > 0
? ListView.builder(
itemCount: this._lidst.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(“${this._list[index][“title”]}”),
);
},
-
)
- Text(“加载中…”));
}
}
================================================================
dio 是一个强大的 Dart Http 请求库,支持 Restful API、FormData、拦截器、请求取消、Cookie 管理、文件上传/下载、超时、自定义适配器等…
https://pub.dev/packages/dio
https://github.com/flutterchina/dio/blob/master/README-ZH.md
不做栗子,依官方最新文档栗子为准
====================================================================
在 Flutter 官方 sdk 中给我们提供了下拉刷新的组件 RefreshIndicator。但是没有提供上拉分页加载更多的组件。但是在 Flutter ListView 中有一个ScrollController 属性,它就是专门来控制 ListView 滑动事件,在这里我们可以根据 ListView 的位置来判断是否滑动到了底部来做加载更多的处理。
Api 接口:http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1
===============================================================
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(“请求数据 Dio Demo”),
),
body: this._list.length > 0
? RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
itemCount: this._list.length,
itemBuilder: (context, index) {
return ListTile(title: Text(this._list[index][“title”]));
-
}))
- Text(“加载中…”));
}
Future _onRefresh() async {
print(‘执行刷新’);
}
===================================================================
_scrollController.position.pixels 滚动的距离
_scrollController.position.maxScrollExtent 总距离
ScrollController _scrollController = ScrollController(); //listview 的控制器
@override
void initState() {
// TODO: implement initState
super.initState();
this._getData();
//监听滚动条事件
_scrollController.addListener(() {
if (_scrollController.position.pixels >
_scrollController.position.maxScrollExtent - 20) {
print(“滚动到了最底部”);
_getData();
}
});
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_scrollController.dispose(); //不用了砍掉,提高性能
}
ListView.builder(
itemCount: this._list.length,
controller: _scrollController, //注意
itemBuilder: (context, index) {
}
)
import ‘package:dio/dio.dart’;
import ‘package:flutter/material.dart’;
import ‘dart:convert’;
class NewsPage extends StatefulWidget {
@override
_NewsPageState createState() => _NewsPageState();
}
class _NewsPageState extends State {
ScrollController _scrollController = ScrollController(); //listview 的控制器
List _list = [];
int _page = 1;
bool isLoading = true; //是否正在加载数据
@override
void initState() {
// TODO: implement initState
super.initState();
this._getData();
//监听滚动条事件
_scrollController.addListener(() {
if (_scrollController.position.pixels >
_scrollController.position.maxScrollExtent - 20) {
print(“滑动到了最底部”);
_getData();
}
});
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_scrollController.dispose(); //不用了砍掉,提高性能
}
_getData() async {
String apiUrl =
“http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=${this._page}”;
Response result = await Dio().get(apiUrl);
var res = json.decode(result.data)[“result”];
// print(json.decode(result.data)[“result”]);
setState(() {
this._list.addAll(res);
this._page++;
});
//判断是否是最后一页
if (res.length < 20) {
setState(() {
this.isLoading = false;
});
}
}
Widget _getMoreWidget() {
if (isLoading) {
return Center(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
‘加载中…’,
style: TextStyle(fontSize: 16.0),
),
CircularProgressIndicator(
strokeWidth: 1.0,
)
],
),
),
);
} else {
return Center(
child: Text(“–我是有底线的–”),
);
}
}
//下拉刷新
Future _onRefresh() async {
print(“执行刷新”);
this._getData();
await Future.delayed(Duration(seconds: 3), () {
print(“refresh”);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(“请求数据 Dio Demo”),
),
body: this._list.length > 0
? RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
controller: _scrollController,
itemCount: this._list.length,
itemBuilder: (context, index) {
if (index == this._list.length - 1) {
return Column(
children: [
ListTile(
title: Text(this._list[index][“title”], maxLines: 1),
onTap: () {
Navigator.pushNamed(context, ‘/newsContent’);
},
),
Divider(),
_getMoreWidget()
],
);
} else {
return Column(
children: [
ListTile(
title: Text(this._list[index][“title”], maxLines: 1),
onTap: () {
Navigator.pushNamed(context, ‘/newsContent’);
},
),
Divider()
],
);
}
},
),
-
)
- _getMoreWidget(),
);
}
}
//解决重复请求的问题
bool flag=true;
@override
void initState() {
super.initState();
_getData();
//监听滚动条滚动事件
_scrollController.addListener((){
//_scrollController.position.pixels //获取滚动条滚动的高度
//_scrollController.position.maxScrollExtent //获取页面高度
if(_scrollController.position.pixels>_scrollController.position.maxScrollExtent-20){
if(this.flag && this._hasMore){ //如果已经请求就不再获取数据
_getData();
}
}
});
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_scrollController.dispose(); //不用了砍掉,提高性能
}
//获取数据
_getData() async {
// 请求数据之前置为false
setState(() {
this.flag=false;
});
var api =‘url‘;
var result = await Dio().get(api);
。。。
// 数据请求完成之后置为true
setState(() {
。。。
this.flag=true;
});
}
//回到顶部
_scrollController.jumpTo(0);
//导航改变的时候触发
_subHeaderChange(id) {
if (id == 4) {
_scaffoldKey.currentState.openEndDrawer();
setState(() {
this._selectHeaderId = id;
});
} else {
setState(() {
this._selectHeaderId = id;
this._sort =“KaTeX parse error: Expected group after '_' at position 40: …- 1]["fileds"]}_̲{this._subHeaderList[id - 1][“sort”]}”;
//重置分页
this._page = 1;
//重置数据
this._productList = [];
//改变sort排序
this._subHeaderList[id - 1][‘sort’] =
this._subHeaderList[id - 1][‘sort’] * -1;
//回到顶部
_scrollController.jumpTo(0);
//重置_hasMore
this._hasMore = true;
//重新请求
this._getProductListData();
});
}
}
========================================================================
涉及的 api 接口:
新闻列表: http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1
新闻详情:http://www.phonegap100.com/appapi.php?a=getPortalArticle&aid=20
列表页
import ‘package:cc/pages/NewsContent.dart’;
import ‘package:dio/dio.dart’;
import ‘package:flutter/material.dart’;
import ‘dart:convert’;
class NewsPage extends StatefulWidget {
@override
_NewsPageState createState() => _NewsPageState();
}
class _NewsPageState extends State {
ScrollController _scrollController = ScrollController(); //listview 的控制器
List _list = [];
int _page = 1;
bool isLoading = true; //是否正在加载数据
@override
void initState() {
// TODO: implement initState
super.initState();
this._getData();
//监听滚动条事件
_scrollController.addListener(() {
if (_scrollController.position.pixels >
_scrollController.position.maxScrollExtent - 20) {
print(“滑动到了最底部”);
_getData();
}
});
}
_getData() async {
String apiUrl =
“http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=${this._page}”;
Response result = await Dio().get(apiUrl);
var res = json.decode(result.data)[“result”];
// print(json.decode(result.data)[“result”]);
setState(() {
this._list.addAll(res);
this._page++;
});
//判断是否是最后一页
if (res.length < 20) {
setState(() {
this.isLoading = false;
});
}
}
Widget _getMoreWidget() {
if (isLoading) {
return Center(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
‘加载中…’,
style: TextStyle(fontSize: 16.0),
),
CircularProgressIndicator(
strokeWidth: 1.0,
)
],
),
),
);
} else {
return Center(
child: Text(“–我是有底线的–”),
);
}
}
//下拉刷新
Future _onRefresh() async {
print(“执行刷新”);
this._getData();
await Future.delayed(Duration(seconds: 3), () {
print(“refresh”);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(“请求数据 Dio Demo”),
),
body: this._list.length > 0
? RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
controller: _scrollController,
itemCount: this._list.length,
itemBuilder: (context, index) {
if (index == this._list.length - 1) {
return Column(
children: [
ListTile(
title: Text(this._list[index][“title”], maxLines: 1),
onTap: () {
Navigator.pushNamed(context, ‘/newsContent’);
},
),
Divider(),
_getMoreWidget()
],
);
} else {
return Column(
children: [
ListTile(
title: Text(this._list[index][“title”], maxLines: 1),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
NewsContent(this._list[index][“aid”])));
},
),
Divider()
],
);
}
},
),
-
)
- _getMoreWidget(),
);
}
}
详情页
import ‘package:flutter/material.dart’;
import ‘dart:convert’;
import ‘package:dio/dio.dart’;
class NewsContent extends StatefulWidget {
var aid;
NewsContent(this.aid);
createState() => _NewsContentState();
}
class _NewsContentState extends State {
List list = [];
_getData() async {
String apiUrl =
“http://www.phonegap100.com/appapi.php?a=getPortalArticle&aid=${this.widget.aid}”;
Response response = await Dio().get(apiUrl);
setState(() {
this.list = json.decode(response.data)[“result”];
});
}
@override
void initState() {
// TODO: implement initState
super.initState();
this._getData();
}
@override
Widget build(BuildContext context) {
return Container(
child: Scaffold(
appBar: AppBar(
title: Text(this.list.length > 0 ? this.list[0][“title”] : “”),
),
body: ListView(
children: [
Text(this.list.length > 0 ? this.list[0][“content”] : “”)
],
),
),
);
}
}
==================================================================
https://pub.dev/packages/flutter_html
鸡肋。。。只能解析部分HTML标签
=====================================================================================
建议使用WebView_flutter官方库,inappbrowser翘辫子了
https://pub.dev/packages/flutter_inappbrowser
ios模拟器测试失败。。。
=================================================================
https://pub.dev/packages/device_info
===========================================================================
1、申请成为开发者
2、创建应用配置获取 Key (参考教程演示)
https://lbs.amap.com/api/android-sdk/guide/create-project/get-key
==================================================================
https://pub.dev/packages/amap_location
====================================================================================
https://pub.dev/packages/image_picker
/拍照/
_takePhoto() async {
var image = await ImagePicker.pickImage(source: ImageSource.camera);
setState(() {
_imgPath = image;
});
}
/相册/
_openGallery() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
_imgPath = image;
});
}
===================================================================
https://pub.dev/packages/dio
Dio2.x和3.x代码不同,依Dio官方文档为准
//上传图片
_uploadImage(File _imageDir) async {
//注意:dio3.x版本为了兼容web做了一些修改,上传图片的时候需要把File类型转换成String类型,具体代码如下
var fileDir = _imageDir.path;
FormData formData = FormData.fromMap({
“name”: “zhangsna 6666666666”,
“age”: 20,
“sex”: “男”,
“file”: await MultipartFile.fromFile(fileDir, filename: “xxx.jpg”)
});
var response =
await Dio().post(“http://jd.itying.com/imgupload”, data: formData);
print(response);
}
===============================================================
video_player 官方库,支持flutter web
在 Flutter 里官方提供了一个 video_player 插件可以播放视频。但是 video_player 有一些局限性。没法控制底部播放进度等。 所以我们主要给大家讲解一个第三方的视频播放库chewie。chewie 是一个非官方的第三方视频播放组件,看起来好像是基于 HTML5 播放的组件。chewie 相对 video_player 来说,有控制栏和全屏的功能。Chewie 使用 video_player 引擎并将其包裹在友好的 Material 或 Cupertino UI 中!
https://pub.dev/packages/video_player
https://pub.dev/packages/chewie
iOS警告
chewie使用的视频播放器插件在iOS模拟器上不起作用。开发/测试期间必须使用iOS设备。
coffee 21:44:59
安利一下 flutter_ijkplayer 视频播放器,这两天试了很多个,还是这个解决了目前的问题
coffee 22:31:46
flutter_tencentplayer 腾讯云的ios无法播放rtmp协议的直播流
coffee 22:33:13
官方提供的video_player 没有ui,需要自己实现
coffee 22:34:47
chewie 包装了一层video_player提供了ui,我视频回放用的这个,费了好大力气才搞定使用原视频尺寸播放,今儿发现 flutter_ijkplayer 很好用,不过回放功能暂时不打算改了,好不容易调完
=============================================================================
import ‘package:flutter/material.dart’;
import ‘package:chewie/chewie.dart’;
import ‘package:video_player/video_player.dart’;
class ChewieVideoDemo extends StatefulWidget {
ChewieVideoDemo({Key key}) : super(key: key);
_ChewieVideoDemoState createState() => _ChewieVideoDemoState();
}
class _ChewieVideoDemoState extends State {
VideoPlayerController videoPlayerController;
ChewieController chewieController;
@override
void initState() {
// TODO: implement initState super.initState();
videoPlayerController = VideoPlayerController.network(
‘http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4’);
chewieController = ChewieController(
videoPlayerController: videoPlayerController,
aspectRatio: 3 / 2,
autoPlay: true,
looping: true,
);
}
@override
void dispose() {
// TODO: implement dispose super.dispose();
videoPlayerController.dispose();
chewieController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(‘标题’),
),
body: Center(
child: Chewie(
controller: chewieController,
),
),
);
}
}
===============================================================
https://pub.dev/packages/connectivity
======================================================================
import ‘package:flutter/material.dart’;
import ‘package:connectivity/connectivity.dart’;
class NetworkPage extends StatefulWidget {
NetworkPage({Key key}) : super(key: key);
_NetworkPageState createState() => _NetworkPageState();
}
class _NetworkPageState extends State {
String _state;
var _subscription;
@override
initState() {
super.initState();
_subscription = Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) {
// Got a new connectivity status!
if (result == ConnectivityResult.mobile) {
setState(() {
_state = “手机网络”;
});
// I am connected to a mobile network. } else if (result == ConnectivityResult.wifi) {
setState(() {
_state = “Wifi 网络”;
});
// I am connected to a wifi network. }else{
setState(() {
_state = “没有网络”;
});
}
});
}
@override
dispose() {
super.dispose();
_subscription.cancel();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(“检测网络变化”),
),
body: Text(“${_state}”),
);
}
}
===============================================================
https://pub.dev/packages/shared_preferences
注意:
如果SharedPreferences prefs = await SharedPreferences.getInstance();写在runapp()的外层,
要加上
========================================================================
1、设置值
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(key, value);
prefs.setBool(key, value)
prefs.setDouble(key, value)
prefs.setInt(key, value)
prefs.setStringList(key, value)
2、获取值
SharedPreferences prefs = await SharedPreferences.getInstance();
var data=prefs.getString(“name”);
3、删除值
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.remove(key); //删除指定键
prefs.clear();//清空键值对
======================================================================
import ‘package:flutter/material.dart’;
import ‘package:shared_preferences/shared_preferences.dart’;
class StoragePage extends StatefulWidget {
StoragePage({Key key}) : super(key: key);
_StoragePageState createState() => _StoragePageState();
}
class _StoragePageState extends State {
_saveData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(“name”, “张三”);
// prefs.setBool(key, value)
// prefs.setDouble(key, value)
// prefs.setInt(key, value)
// prefs.setStringList(key, value)
}
_getData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var data = prefs.getString(“name”);
print(data);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(“本地存储”),
),
body: Center(
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
RaisedButton(
child: Text(‘保存数据’),
onPressed: _saveData,
),
SizedBox(height: 10),
RaisedButton(
child: Text(‘获取数据’),
onPressed: _getData,
)
]),
),
);
}
}
=====================================================================
https://pub.dev/packages/barcode_scan
错误多多,修改大大,不做记录。。。用到再说
检测应用版本号、服务器下载文件以及实现 App 自动升级、安装
==========================================================================================
1、获取本地版本号
2、请求服务器获取服务器版本号
3、本地版本和服务器版本不一致提示升级,弹窗提示用户是否更新
4、用户确定升级,调用文件传输方法下载 apk 文件
5、监听下载进度
6、下载完成打开 Apk 进行安装
注意:在 Ios 中没法直接下载安装,如果版本不一致直接跳转到 Ios 应用对应的应用市场就可以了。
配置版本号: (Flutter应用获取的不是这里的版本号,在pubspec.yaml文件)
配置 AndroidMenifest.xml 文件
| 插件名称 | 描述 | 插件地址 |
| :-: | :-: | :-- |
| package_info | 检测版本号 | https://pub.dev/packages/package_info |
| path_provider | 获取文件存储路径 | https://pub.dev/packages/path_provider |
| flutter_downloader | flutter_downloader | https://pub.dev/packages/flutter_downloader |
| open_file | 打开文件插件 | https://pub.dev/packages/open_file |
https://pub.dev/packages/package_info
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String appName = packageInfo.appName;
String packageName = packageInfo.packageName;
String version = packageInfo.version;
String buildNumber = packageInfo.buildNumber;
print(“appName:${appName}”);
print(“packageName:${packageName}”);
print(“version:${version}”);
print(“buildNumber:${buildNumber}”);
https://pub.dev/packages/path_provider
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
Directory appDocDir = await getApplicationDocumentsDirectory();
String appDocPath = appDocDir.path;
var directory = await getExternalStorageDirectory();
String storageDirectory=directory.path;
print(“tempPath:${tempPath}”);
print(“appDocDir:${appDocPath}”);
print(“StorageDirectory:${storageDirectory}”);
https://pub.dev/packages/flutter_downloader
final directory = await getExternalStorageDirectory();
String _localPath = directory.path;
final taskId = await FlutterDownloader.enqueue(
url: “http://www.ionic.wang/jdshop.apk”,
savedDir: _localPath,
showNotification:
true, // show download progress in status bar (for Android)
openFileFromNotification:
true, // click on notification to open downloaded file (for Android)
https://pub.dev/packages/open_file
OpenFile.open(“${_localPath}/jdshop.apk”);
1、服务器的 App 版本必须大于本地 App 版本
2、本地 App 和服务器 App 的包名称 签名必须一致,这样的话服务器的包才可以替换本地的包。
import ‘package:flutter/material.dart’;
import ‘package:package_info/package_info.dart’;
import ‘package:path_provider/path_provider.dart’;
import ‘dart:io’;
import ‘package:open_file/open_file.dart’;
import ‘package:flutter_downloader/flutter_downloader.dart’;
class AppVersionPage extends StatefulWidget {
AppVersionPage({Key key}) : super(key: key);
_AppVersionPageState createState() => _AppVersionPageState();
}
class _AppVersionPageState extends State {
@override
void initState() {
// TODO: implement initState
super.initState();
this._getPackageInfo();
this._getAppPath();
}
//弹出Dialog
_showDialog() async {
var alertRel = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(“更新APP提示!”),
content: Text(“发现新的版本,新版本修复了如下bug 是否更新!”),
actions: [
FlatButton(
child: Text(“否”),
onPressed: () {
Navigator.pop(context, ‘Cancle’);
},
),
FlatButton(
child: Text(“是”),
onPressed: () {
Navigator.pop(context, ‘Ok’);
},
)
],
);
});
}
//获取版本号
_getPackageInfo() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String appName = packageInfo.appName;
String packageName = packageInfo.packageName;
String version = packageInfo.version;
String buildNumber = packageInfo.buildNumber;
print(“appName:${appName}”);
print(“packageName:${packageName}”);
print(“version:${version}”);
print(“buildNumber:${buildNumber}”);
}
//获取路径
_getAppPath() async {
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
Directory appDocDir = await getApplicationDocumentsDirectory();
String appDocPath = appDocDir.path;
var directory = await getExternalStorageDirectory();
String storageDirectory = directory.path;
print(“tempPath:${tempPath}”);
print(“appDocDir:${appDocPath}”);
print(“StorageDirectory:${storageDirectory}”);
}
//下载打开文件
_downLoad() async {
final directory = await getExternalStorageDirectory();
String _localPath = directory.path;
final taskId = await FlutterDownloader.enqueue(
url: “http://www.ionic.wang/jdshop.apk”,
savedDir: _localPath,
showNotification:
true, // show download progress in status bar (for Android)
openFileFromNotification:
true, // click on notification to open downloaded file (for Android)
);
FlutterDownloader.registerCallback((id, status, progress) {
print(status);
// code to update your UI
print(‘1111111’);
print(progress);
});
//打开文件
OpenFile.open(“${_localPath}/jdshop.apk”);
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.arrow_downward),
onPressed: _downLoad,
),
appBar: AppBar(
title: Text(“app升级演示”),
),
body: Text(“app升级演示”),
);
}
}
调用 url_launcher 模块打开外部浏览器 打开外部应用 拨打电话 发送短信
======================================================================================================
1、Flutter url_launcher 模块
Flutter url_launcher 模块可以让我们实现打开外部浏览器、打开外部应用、发送短信、拨打电话等功能。
https://pub.dev/packages/url_launcher
2、Flutter url_launcher 模块的使用
import ‘package:flutter/material.dart’;
import ‘package:url_launcher/url_launcher.dart’;
class _UrlLauncherState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(‘UrlLauncher’),
),
body: Center(
child: Padding(
padding: EdgeInsets.all(20),
child: ListView(
children: [
RaisedButton(
child: Text(‘打开外部浏览器’),
onPressed: () async {
const url = ‘https://cflutter.com’;
if (await canLaunch(url)) {
await launch(url);
} else {
throw ‘Could not launch $url’;
}
},
),
SizedBox(
height: 10,
),
RaisedButton(
child: Text(‘拨打电话’),
onPressed: () async {
const tel = ‘tel:10086’;
if (await canLaunch(tel)) {
await launch(tel);
} else {
throw ‘Could not launch $tel’;
}
},
),
SizedBox(height: 10),
RaisedButton(
child: Text(‘发送短信’),
onPressed: () async {
const tel = ‘sms:10086’;
if (await canLaunch(tel)) {
await launch(tel);
} else {
throw ‘Could not launch $tel’;
}
},
),
SizedBox(height: 10),
RaisedButton(
child: Text(‘打开外部应用’),
onPressed: () async {
// weixin://
const app = ‘alipays://’;
if (await canLaunch(app)) {
await launch(app);
} else {
throw ‘Could not launch $app’;
}
},
)
],
),
),
),
);
}
}
3、关于打开其他 app 请参考这个帖子
https://www.cflutter.com/topic/5d0853733b57e317a4d0af01
=====================================================================================
Android 是在 android ▸ app ▸ src ▸ main▸ AndroidManifest.xml 中修改 android:label=“XXX”;
Android 在 android ▸ app ▸ src ▸ res ▸ mipmap 下面对应的文件夹中替换相应图片
Android 添加启动界面
打开文件 android/app/src/main/res/drawable/launch_background.xml
修改内容,打开注释了的代码 launch_image 那段。
里面的ic_launch.png是图标,启动画面添加launch_image.png,格式要求png
注意@mipmap/launch_image 就是你要设置的启动界面的图片资源名字,你要放置到对应的文件夹里面
| 密度 | 代表分辨率 |
| :-: | :-: |
| ldpi | 240 x 320 |
| mdpi | 320 x 480 |
| hdpi | 480 x 800 |
| xhdpi | 720 x 1280 |
| xxhdpi | 1080 x 1920 |
| xxxhdpi | 3840×2160 |
竖向 ListView 嵌套横向 ListView ,以及ListView 嵌套 GridView
============================================================================================================
1、竖向 ListView 嵌套横向 ListView 注意事项:
在竖向 ListView 中嵌套横向 ListView 的时候要注意给横向 ListView 外层加一个容器,然后外层这个容器要设置高度,外层这个容器可以是 SizedBox ,也可以是 Container。
2、ListView 嵌套 GridView 注意事项:
由于 GridView 和 ListView 都是可以滚动的组件,所以嵌套的时候要注意把里面的组件改为不可滚动组件。
重要属性:
shrinkWrap: true, //解决无限高度问题
physics:NeverScrollableScrollPhysics(), //禁用滑动事件
=====================================================================
我写的代码还用适配???
============================================================================
1、使用 dart:convert 手动序列化 JSON
2、模型类中序列化 JSON
小项目中使用 dart:convert 手动序列化 JSON 非常好,也非常快速。但是随着项目的增大,dart:convert 手动序列化 JSON 的话失去了大部分静态类型语言特性:类型安全、自动补全和最重要的编译时异常。这样一来,我们的代码可能会变得非常容易出错。
当我们访问 name 或 email 字段时,我们输入的很快,导致字段名打错了。但由于这个 JSON 在 map 结构中,所以编译器不知道这个错误的字段名。
为了解决上面的问题在大型项目中使用的更多的是在模型类中序列化 JSON。
JSON字符串和Map类型的转换 dart:convert手动序列化 JSON
==================================================================================================
import ‘dart:convert’;
var mapData = {“name”: “张三”, “age”: “20”};
var strData = ‘{“name”:“张三”,“age”:“20”}’;
print(json.encode(mapData)); //Map转换成Json字符串
print(json.decode(strData)); //Json 字符串转化成 Map 类型
========================================================================
class FocusModel {
String sId;
String title;
String status;
String pic;
String url;
FocusModel({this.sId, this.title, this.status, this.pic, this.url});
FocusModel.fromJson(Map<String, dynamic> json) {
sId = json[‘_id’];
title = json[‘title’];
status = json[‘status’];
pic = json[‘pic’];
url = json[‘url’];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data[‘_id’] = this.sId;
data[‘title’] = this.title;
data[‘status’] = this.status;
data[‘pic’] = this.pic;
data[‘url’] = this.url;
return data;
}
}
var strData=‘{“_id”:“59f6ef443ce1fb0fb02c7a43”,“title”:“笔记本电脑”,“status”:“1”,“pic”:“public\upload\UObZahqPYzFvx_C9CQjU8KiX.png”,“url”:“12”}’;
var data=FocusModel.fromJson(strData);
可参考:https://flutterchina.club/json/
=================================================================================
https://javiercbk.github.io/json_to_dart/
==============================================================================
IndexedStack 和 Stack 一样,都是层布局控件, 可以在一个控件上面放置另一个控件,但唯一不同的是 IndexedStack 在同一时刻只能显示子控件中的一个控件,通过 Index 属性来设置显示的控件。
IndexedStack 来保持页面状态的优点就是配置简单。IndexedStack 保持页面状态的缺点就是不方便单独控制每个页面的状态。
body: IndexedStack(
index: this._currentIndex,
children: [],
),
AutomaticKeepAliveClientMixin 保持页面状态
===============================================================================================
花里胡哨。。。
====================================================================
final GlobalKey _scaffoldKey = new GlobalKey();
return Scaffold( key:_scaffoldKey,
appBar: AppBar(
title: Text(“商品列表”),
) )
Expanded(
flex: 1,
child: InkWell( onTap: () {
_scaffoldKey.currentState.openEndDrawer(); },
child: Text(“筛选”, textAlign: TextAlign.center), ),
)
=================================================================
return MaterialApp(
debugShowCheckedModeBanner: false, // home: Tabs(),
initialRoute: ‘/’, onGenerateRoute:onGenerateRoute, theme: ThemeData(
primaryColor: Colors.white ),
);
========================================================================
IconButton(
icon: Icon(Icons.more_horiz),
onPressed: () {
showMenu(
context: context,
position: RelativeRect.fromLTRB(500, 76, 10, 0),
items: [
PopupMenuItem(
child: Row(
children: [
Icon(Icons.home),
Container(
padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
child: Text(“首页”),
)
],
),
),
PopupMenuItem(
child: Row(
children: [
Icon(Icons.search),
Container(
padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
child: Text(“搜索”),
)
],
),
)
]);
})
=================================================================
实际就是点击事件后,弹出showModalBottomSheet,参考diolog笔记
StatefulBuilder更新Flutter showDialog 、showModalBottomSheet 中的状态
=========================================================================================================================
参考:https://www.cflutter.com/topic/5d202202403aa10564178c65
===============================================================
通俗的讲:当我们想在多个页面(组件/Widget)之间共享状态(数据),或者一个页面(组件/Widget)中的多个子组件之间共享状态(数据),这个时候我们就可以用 Flutter 中的状态管理来管理统一的状态(数据),实现不同组件直接的传值和数据共享。
现在 Flutter 的状态管理方案很多,redux、bloc、state、provide、provider。
目前我们推荐使用 provider,这个是官方提供的状态管理解决方案。相比其他状态管理库使用起来比较方便。
=====================================================================================
provider 是 Flutter 团队推出的状态管理模式。
官方地址为:https://pub.dev/packages/provider
注意:provider 和 provide 是两个库哦。Flutter 官方推荐使用的是 provider 哦,provider 是flutter 官方出的。provide 不是 Flutter 官方写的哦。
=======================================================================
(官方文档为准,builder关键字变creat)
1、配置依赖 provider: ^4.3.3
2、新建一个文件夹叫 provider,在 provider 文件夹里面放我们对于的状态管理类
3、在 provider 里面新建 Counter.dart
4、Counter.dart 里面新建一个类继承 minxins 的 ChangeNotifier 代码如下
import ‘package:flutter/material.dart’;
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
5、找到 main.dart 修改代码如下
import ‘package:flutter/material.dart’;
import ‘routers/router.dart’;
import ‘package:provider/provider.dart’;
import ‘provider/Counter.dart’;
void main() => runApp(MyApp());
// void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// Provider.value(value: foo),
ChangeNotifierProvider(create: (_) => Counter()), // provider4.x的写法全局监听
//ChangeNotifierProvider(builder: (_) => Counter()),
],
child: MaterialApp(
// home: Tabs(),
debugShowCheckedModeBanner: false,
initialRoute: ‘/productContent’,
onGenerateRoute: onGenerateRoute,
theme: ThemeData(
// primaryColor: Colors.yellow
primaryColor: Colors.white),
));
}
}
6、获取值、以及设置值
import ‘package:provider/provider.dart’;
import ‘…/…/provider/Counter.dart’;
Widget build(BuildContext context) {
final counter = Provider.of(context); // counter.init();//在build里面
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
counter.increment();
},
),
body: Text(“counter 的值:${counter.count}”));
}
扩展栗子
import ‘package:flutter/material.dart’;
class Cart with ChangeNotifier {
List _cartList = []; //状态
// int _cartNum=0; //数量直接从数组中获取,不用在定义一个获取数量的方法
int get cartNum => this._cartList.length; //数量直接从数组中获取,不用在定义一个获取数量的方法
List get cartList => this._cartList;
addData(value) {
this._cartList.add(value);
notifyListeners();
}
deleteData(value) {
this._cartList.remove(value);
notifyListeners();
}
}
===============================================================================
花里胡哨。。。
MediaQuery.removePadding移除元素的pandding
================================================================================================
通过MediaQuery.removePadding
可以移除元素的pandding,需要注意要指定移除哪个方向的padding,例如移除上面的padding
MediaQuery.removePadding(
removeTop: true,
context: context,
child: ,
)
================================================================
https://pub.dev/packages/flutter_staggered_grid_view
new StaggeredGridView.countBuilder(
crossAxisCount: 4,
itemCount: 8,
itemBuilder: (BuildContext context, int index) => new Container(
color: Colors.green,
child: new Center(
child: new CircleAvatar(
backgroundColor: Colors.white,
child: new Text(‘$index’),
),
)),
staggeredTileBuilder: (int index) =>
new StaggeredTile.count(2, index.isEven ? 2 : 1), //固定个数修改count()为fit(2)
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
)
======================================================================
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
// title: Text(“SliverAppBar”),
// pinned: true,
floating: true,
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
title: Text(“Hello Flutter”.toUpperCase()),
background: Image.network(
最后
小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
资料⬅专栏获取
= json[‘status’];
pic = json[‘pic’];
url = json[‘url’];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data[‘_id’] = this.sId;
data[‘title’] = this.title;
data[‘status’] = this.status;
data[‘pic’] = this.pic;
data[‘url’] = this.url;
return data;
}
}
var strData=‘{“_id”:“59f6ef443ce1fb0fb02c7a43”,“title”:“笔记本电脑”,“status”:“1”,“pic”:“public\upload\UObZahqPYzFvx_C9CQjU8KiX.png”,“url”:“12”}’;
var data=FocusModel.fromJson(strData);
可参考:https://flutterchina.club/json/
=================================================================================
https://javiercbk.github.io/json_to_dart/
==============================================================================
IndexedStack 和 Stack 一样,都是层布局控件, 可以在一个控件上面放置另一个控件,但唯一不同的是 IndexedStack 在同一时刻只能显示子控件中的一个控件,通过 Index 属性来设置显示的控件。
IndexedStack 来保持页面状态的优点就是配置简单。IndexedStack 保持页面状态的缺点就是不方便单独控制每个页面的状态。
body: IndexedStack(
index: this._currentIndex,
children: [],
),
AutomaticKeepAliveClientMixin 保持页面状态
===============================================================================================
花里胡哨。。。
====================================================================
final GlobalKey _scaffoldKey = new GlobalKey();
return Scaffold( key:_scaffoldKey,
appBar: AppBar(
title: Text(“商品列表”),
) )
Expanded(
flex: 1,
child: InkWell( onTap: () {
_scaffoldKey.currentState.openEndDrawer(); },
child: Text(“筛选”, textAlign: TextAlign.center), ),
)
=================================================================
return MaterialApp(
debugShowCheckedModeBanner: false, // home: Tabs(),
initialRoute: ‘/’, onGenerateRoute:onGenerateRoute, theme: ThemeData(
primaryColor: Colors.white ),
);
========================================================================
IconButton(
icon: Icon(Icons.more_horiz),
onPressed: () {
showMenu(
context: context,
position: RelativeRect.fromLTRB(500, 76, 10, 0),
items: [
PopupMenuItem(
child: Row(
children: [
Icon(Icons.home),
Container(
padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
child: Text(“首页”),
)
],
),
),
PopupMenuItem(
child: Row(
children: [
Icon(Icons.search),
Container(
padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
child: Text(“搜索”),
)
],
),
)
]);
})
=================================================================
实际就是点击事件后,弹出showModalBottomSheet,参考diolog笔记
StatefulBuilder更新Flutter showDialog 、showModalBottomSheet 中的状态
=========================================================================================================================
参考:https://www.cflutter.com/topic/5d202202403aa10564178c65
===============================================================
通俗的讲:当我们想在多个页面(组件/Widget)之间共享状态(数据),或者一个页面(组件/Widget)中的多个子组件之间共享状态(数据),这个时候我们就可以用 Flutter 中的状态管理来管理统一的状态(数据),实现不同组件直接的传值和数据共享。
现在 Flutter 的状态管理方案很多,redux、bloc、state、provide、provider。
目前我们推荐使用 provider,这个是官方提供的状态管理解决方案。相比其他状态管理库使用起来比较方便。
=====================================================================================
provider 是 Flutter 团队推出的状态管理模式。
官方地址为:https://pub.dev/packages/provider
注意:provider 和 provide 是两个库哦。Flutter 官方推荐使用的是 provider 哦,provider 是flutter 官方出的。provide 不是 Flutter 官方写的哦。
=======================================================================
(官方文档为准,builder关键字变creat)
1、配置依赖 provider: ^4.3.3
2、新建一个文件夹叫 provider,在 provider 文件夹里面放我们对于的状态管理类
3、在 provider 里面新建 Counter.dart
4、Counter.dart 里面新建一个类继承 minxins 的 ChangeNotifier 代码如下
import ‘package:flutter/material.dart’;
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
5、找到 main.dart 修改代码如下
import ‘package:flutter/material.dart’;
import ‘routers/router.dart’;
import ‘package:provider/provider.dart’;
import ‘provider/Counter.dart’;
void main() => runApp(MyApp());
// void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// Provider.value(value: foo),
ChangeNotifierProvider(create: (_) => Counter()), // provider4.x的写法全局监听
//ChangeNotifierProvider(builder: (_) => Counter()),
],
child: MaterialApp(
// home: Tabs(),
debugShowCheckedModeBanner: false,
initialRoute: ‘/productContent’,
onGenerateRoute: onGenerateRoute,
theme: ThemeData(
// primaryColor: Colors.yellow
primaryColor: Colors.white),
));
}
}
6、获取值、以及设置值
import ‘package:provider/provider.dart’;
import ‘…/…/provider/Counter.dart’;
Widget build(BuildContext context) {
final counter = Provider.of(context); // counter.init();//在build里面
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
counter.increment();
},
),
body: Text(“counter 的值:${counter.count}”));
}
扩展栗子
import ‘package:flutter/material.dart’;
class Cart with ChangeNotifier {
List _cartList = []; //状态
// int _cartNum=0; //数量直接从数组中获取,不用在定义一个获取数量的方法
int get cartNum => this._cartList.length; //数量直接从数组中获取,不用在定义一个获取数量的方法
List get cartList => this._cartList;
addData(value) {
this._cartList.add(value);
notifyListeners();
}
deleteData(value) {
this._cartList.remove(value);
notifyListeners();
}
}
===============================================================================
花里胡哨。。。
MediaQuery.removePadding移除元素的pandding
================================================================================================
通过MediaQuery.removePadding
可以移除元素的pandding,需要注意要指定移除哪个方向的padding,例如移除上面的padding
MediaQuery.removePadding(
removeTop: true,
context: context,
child: ,
)
================================================================
https://pub.dev/packages/flutter_staggered_grid_view
new StaggeredGridView.countBuilder(
crossAxisCount: 4,
itemCount: 8,
itemBuilder: (BuildContext context, int index) => new Container(
color: Colors.green,
child: new Center(
child: new CircleAvatar(
backgroundColor: Colors.white,
child: new Text(‘$index’),
),
)),
staggeredTileBuilder: (int index) =>
new StaggeredTile.count(2, index.isEven ? 2 : 1), //固定个数修改count()为fit(2)
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
)
======================================================================
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
// title: Text(“SliverAppBar”),
// pinned: true,
floating: true,
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
title: Text(“Hello Flutter”.toUpperCase()),
background: Image.network(
最后
小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
[外链图片转存中…(img-iiQBXaDR-1719088811028)]一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
资料⬅专栏获取