赞
踩
Flutter Widget Snippets
Awesome Flutter Snippets
runApp()是入口方法
void main(){
runApp(Center( // 实例化的时候new可以省略 new Center(
child: Text( // new Text(
'你好',
textDirection: TextDirection.ltr,
),
));
}
void main(){
runApp(MyApp()); // 省略new new MyApp()
}
fontSize: 40.0,
// color: Colors.lightGreen
color: Color.fromRGBO(244, 233, 121, 0.8)
去掉右侧图标
MaterialApp(
debugShowCheckedModeBanner: false,
home: Tabs(),
);
Scaffold(
floatingActionButton: FloatingActionButton( //浮动按钮
child: Text('返回'),
onPressed: (){
Navigator.of(context).pop(); // 返回
},
),
appBar: AppBar(
title: Text('搜索页面'),
),
body: Text('搜索内容页'),
);
fim import 'package:flutter/material.dart';
Center( // 实例化的时候new可以省略 child: Container( child: Text('我是一个文本我是一个文本我是一个文本我是一个文本我是一个文本', textAlign: TextAlign.left, textDirection: TextDirection.ltr, overflow: TextOverflow.ellipsis, // clip裁断,没有后面点点点 maxLines: 2, // 显示多少行 textScaleFactor: 1.2, // 字体方法两倍 style:TextStyle( fontSize: 16.0, // 所有和数字相关的都是double类型(浮点类型),要加.0 // color: Color.fromRGBO(244, 233, 121, 0.8) color: Colors.red, fontWeight: FontWeight.w800, // 字体粗细 fontStyle: FontStyle.italic, // 倾斜 decoration: TextDecoration.lineThrough, decorationColor: Colors.blue, decorationStyle: TextDecorationStyle.dashed, letterSpacing: 15.0 // 间距 ) ), height: 300.0, width: 300.0, alignment: AlignmentDirectional.center, decoration: BoxDecoration( color: Colors.yellow, border:Border.all( color: Colors.blue, width: 2.0, ), borderRadius:BorderRadius.all(Radius.circular(20)) ), // padding: EdgeInsets.all(30), // 内边距 margin: EdgeInsets.fromLTRB(10, 30, 60, 90), // 外边距 // transform: Matrix4.translationValues(100, 0, 0) // x, y, z // transform: Matrix4.rotationZ(-0.3) // 旋转 // transform: Matrix4.skew(0, 0.2) // 倾斜 transform: Matrix4.diagonal3Values(1.2, 1, 1) // 缩放 把x轴拉伸了 ) );
image.asset 本地图片
image.network 远程图片
注意:建议把图片放在container组件里面,比较好控制
Container( child: Image.network( 'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', // alignment: Alignment.bottomLeft, //图片显示方位 // color: Colors.blue, // 图片颜色 // colorBlendMode: BlendMode.luminosity, // 图片混合模式 // fit: BoxFit.cover, // 充满整个容器,等比例拉伸图片,会裁剪 // fit: BoxFit.fill, // 充满整个容器,会变形 repeat: ImageRepeat.repeat, // 重复平铺 ), width: 300, height: 300, decoration: BoxDecoration( color: Colors.yellow ), )
圆形图片:
1)DecorationImage()组件
Container(
width: 300,
height: 300,
decoration: BoxDecoration(
color: Colors.yellow,
borderRadius: BorderRadius.circular(150),
image: DecorationImage(
image: NetworkImage('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'),
fit: BoxFit.cover
)
),
)
2)ClipOval()组件 (推荐使用)
Container(
child: ClipOval(
child: Image.network(
'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg',
height: 300,
width: 300,
fit:BoxFit.cover
),
),
)
然后,打开 pubspec.yaml 声明一下添加的图片文件,注意要配置对
Container(
child: ClipOval(
child: Image.asset(
'images/a.jpeg',
height: 300,
width: 300,
fit:BoxFit.cover
),
),
)
borderRadius: BorderRadius.all(
Radius.circular(150)
)
borderRadius: BorderRadius.circular(150)
ListView( padding: EdgeInsets.all(10), children: <Widget>[ ListTile( leading: Icon(Icons.settings, color: Colors.blue, size:30), title:Text('今日宜嗑 CP,宜秀恩爱', style: TextStyle(fontSize: 24), ), subtitle: Text('今年的情人节有点特殊,疫情之下,许多情侣硬是变成了「异地恋」。在这个不能出门不能见面的情人节,为各位知友贴心的准备了一种新过节方式'), // 二级标题 ), ListTile( title:Text('今日宜嗑 CP,宜秀恩爱', style: TextStyle(fontSize: 24), ), subtitle: Text('今年的情人节有点特殊,疫情之下,许多情侣硬是变成了「异地恋」。在这个不能出门不能见面的情人节,为各位知友贴心的准备了一种新过节方式'), // 二级标题 trailing: Icon(Icons.shop, color: Colors.blue,), ), ListTile( leading: Container( child: Image.network( 'https://pic2.zhimg.com/80/v2-b5476d31a5cb86912152ebe84386264c_qhd.jpg', fit: BoxFit.cover, ), width: 100, height: 100, decoration: BoxDecoration( color: Colors.blue ), ), title:Text('今日宜嗑 CP,宜秀恩爱', style: TextStyle(fontSize: 24), ), subtitle: Text('今年的情人节有点特殊,疫情之下,许多情侣硬是变成了「异地恋」。在这个不能出门不能见面的情人节,为各位知友贴心的准备了一种新过节方式'), // 二级标题 ), ListTile( leading: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg',), title:Text('今日宜嗑 CP,宜秀恩爱', style: TextStyle(fontSize: 24), ), subtitle: Text('今年的情人节有点特殊,疫情之下,许多情侣硬是变成了「异地恋」。在这个不能出门不能见面的情人节,为各位知友贴心的准备了一种新过节方式'), // 二级标题 ) ], );
注意:ListView没法再包含ListView
必须要有宽度,否则没生效
Container( height: 180, child: ListView( scrollDirection: Axis.horizontal, children: <Widget>[ Container( width: 180, // height: 180, color: Colors.blue, ), Container( width: 180, // height: 180, color: Colors.yellow, child: ListView( children: <Widget>[ Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'), Text('哈哈哈哈哈', textAlign:TextAlign.center) ], ), ), Container( width: 180, // height: 180, color: Colors.red, ), Container( width: 180, // height: 180, color: Colors.pink, ) ], ), );
循环动态数据的两种方法:
1)先遍历好数据,接下来放到ListView里面
2)使用ListView.builder方法
class HomeContent extends StatelessWidget{ // 要求List数据必须是Widget组件,表示List返回的每一项必须是组件 List<Widget> _getData(){ return [ ListTile( title: Text('我是一个列表') ), ListTile( title: Text('我是一个列表') ), ListTile( title: Text('我是一个列表') ), ListTile( title: Text('我是一个列表') ) ]; } @override Widget build(BuildContext context) { return ListView( children: this._getData(), ) } }
优化成:
class HomeContent extends StatelessWidget{ // 要求List数据必须是Widget组件,表示List返回的每一项必须是组件 List<Widget> _getData(){ List<Widget> list = new List(); for(var i=0;i<20;i++){ list.add(ListTile( title:Text('我是$i列表') )); } return list; } @override Widget build(BuildContext context) { return ListView( children: this._getData(), ); } }
itemCount // 数据的长度
itemBuilder // list的方法
class HomeContent extends StatelessWidget{ List list = new List(); HomeContent(){ // 类初始化的时候增加数据 for(var i=0;i<20;i++){ this.list.add('我是第$i条'); } } @override Widget build(BuildContext context) { return ListView.builder( itemCount: this.list.length, itemBuilder:(context,index){ return ListTile( title:Text(this.list[index]) ); } ); } }
例子2:
List listData=[ { 'title':'哈哈哈哈哈哈哈1', 'author':'xiaoming', 'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg' }, { 'title':'哈哈哈哈哈哈哈2', 'author':'xiaoming', 'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg' }, { 'title':'哈哈哈哈哈哈哈3', 'author':'xiaoming', 'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg' }, { 'title':'哈哈哈哈哈哈哈4', 'author':'xiaoming', 'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg' } ]; class HomeContent extends StatelessWidget{ // 自定义方法 Widget _getListData(context, index){ return ListTile( title: Text(listData[index]['title']), leading: Image.network(listData[index]['imageUrl']), subtitle: Text(listData[index]['author']), ); } @override Widget build(BuildContext context) { return ListView.builder( itemCount: listData.length, itemBuilder:this._getListData ); } }
ListView 可以实现新闻列表布局
GridView 可以用在商品列表布局
实现网格布局的两种实现方式:
1)GridView.count
2)GridView.builder
crossAxisCount 控制多少列
注意:没法设置高度(设置高度也没有反应),可以用childAspectRatio来调整x和y轴的比例(宽度和高度的比例),也可以用wrap来实现
class HomeContent extends StatelessWidget{ List<Widget> _getListData(){ List<Widget> list = new List(); for(var i=0;i<20;i++){ list.add(Container( alignment: Alignment.center, child: Text( '这是第$i条数据', style: TextStyle(color: Colors.white, fontSize: 20), ), color: Colors.blue, )); } return list; } @override Widget build(BuildContext context) { return GridView.count( crossAxisCount: 3, crossAxisSpacing: 20.0, // 水平子部件的间距 mainAxisSpacing: 20.0, // 垂直子部件的间距 padding:EdgeInsets.all(10), // 整个模块的边距 childAspectRatio: 0.7, // 用来设置高度,宽度和高度的比例 children: this._getListData(), ); } }
垂直布局:ListView里面的子部件会自适应宽度,column不会
List listData=[ { 'title':'哈哈哈哈哈哈哈1', 'author':'xiaoming', 'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg' }, { 'title':'哈哈哈哈哈哈哈2', 'author':'xiaoming', 'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg' }, { 'title':'哈哈哈哈哈哈哈3', 'author':'xiaoming', 'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg' }, { 'title':'哈哈哈哈哈哈哈4', 'author':'xiaoming', 'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg' } ]; class HomeContent extends StatelessWidget{ List<Widget> _getListData(){ var tempList = listData.map((value){ return Container( child: Column( children: <Widget>[ Image.network(value['imageUrl']), Text(value['title']) ], ), decoration: BoxDecoration( border:Border.all( color: Color.fromRGBO(233, 233, 233, 1), width: 1 ) ) ); }); return tempList.toList(); // 转换为数组 } @override Widget build(BuildContext context) { return GridView.count( crossAxisCount: 2, crossAxisSpacing: 20.0, // 水平子部件的间距 mainAxisSpacing: 10.0, // 垂直子部件的间距 padding:EdgeInsets.all(10), // 整个模块的边距 childAspectRatio: 1.2, // 用来设置高度,宽度和高度的比例 children: this._getListData(), ); } }
class HomeContent extends StatelessWidget{ Widget _getListData(context, index){ return Container( child: Column( children: <Widget>[ Image.network(listData[index]['imageUrl']), Text(listData[index]['title']) ], ), decoration: BoxDecoration( border:Border.all( color: Color.fromRGBO(233, 233, 233, 1), width: 1 ) ) ); } @override Widget build(BuildContext context) { return GridView.builder( itemCount: listData.length, itemBuilder: this._getListData, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( // 注意,要在这个组件里设置 crossAxisCount: 2, crossAxisSpacing: 10.0, mainAxisSpacing: 10.0 ), ); } }
Flutter 中很多 Widget 是没有 padding 属性,这时候就可以用padding组件
Padding( padding: EdgeInsets.fromLTRB(0, 0, 10, 0), child: GridView.count( crossAxisCount: 2, childAspectRatio: 1.7, children: <Widget>[ Padding( padding: EdgeInsets.fromLTRB(10, 10, 0, 0), child: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', fit:BoxFit.cover), ), Padding( padding: EdgeInsets.fromLTRB(10, 10, 0, 0), child: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', fit:BoxFit.cover), ), Padding( padding: EdgeInsets.fromLTRB(10, 10, 0, 0), child: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', fit:BoxFit.cover), ), Padding( padding: EdgeInsets.fromLTRB(10, 10, 0, 0), child: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', fit:BoxFit.cover), ), Padding( padding: EdgeInsets.fromLTRB(10, 10, 0, 0), child: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', fit:BoxFit.cover), ), Padding( padding: EdgeInsets.fromLTRB(10, 10, 0, 0), child: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', fit:BoxFit.cover), ), ], ), );
自定义按钮组件
class HomeContent extends StatelessWidget{ @override Widget build(BuildContext context) { return Container( height: 400.0, width: 400.0, color: Colors.pink, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ IconContainer(Icons.search, color: Colors.blue,), IconContainer(Icons.search, color: Colors.orange,), IconContainer(Icons.search, color: Colors.green,) ], ), ); } } // 自定义按钮组件 class IconContainer extends StatelessWidget{ // 哪些属性需要动态传入 double size=32.0; // 指定默认值 Color color=Colors.red; IconData icon; // 定义构造函数,可以传入实例化的时候传过来的数据 // this.size 表示把值赋值给this,小括号{}里面是可选的 IconContainer(this.icon,{this.color, this.size}); // 后面的括号里没内容,可以省略 @override Widget build(BuildContext context) { return Container( width: 100.0, height: 100.0, color: this.color, child: Center( // child: Icon(Icons.home, size:32, color:Colors.white), child: Icon(this.icon, size:this.size, color: Colors.white), // 换成this ), ); } }
ListView会铺满整个空间,column是多宽就多宽
class HomeContent extends StatelessWidget{ @override Widget build(BuildContext context) { return Container( height: 400.0, width: 400.0, color: Colors.pink, child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ IconContainer(Icons.search, color: Colors.blue,), IconContainer(Icons.search, color: Colors.orange,), IconContainer(Icons.search, color: Colors.green,) ], ), ); } } // 自定义按钮组件 class IconContainer extends StatelessWidget{ // 哪些属性需要动态传入 double size=32.0; // 指定默认值 Color color=Colors.red; IconData icon; // 定义构造函数,可以传入实例化的时候传过来的数据 // this.size 表示把值赋值给this,小括号{}里面是可选的 IconContainer(this.icon,{this.color, this.size}); // 后面的括号里没内容,可以省略 @override Widget build(BuildContext context) { return Container( width: 100.0, height: 100.0, color: this.color, child: Center( // child: Icon(Icons.home, size:32, color:Colors.white), child: Icon(this.icon, size:this.size, color: Colors.white), // 换成this ), ); } }
类似 Web 中的 Flex 布局,可以用在 Row 和 Column 布局中
Row( // mainAxisAlignment: MainAxisAlignment.spaceEvenly, // crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ // Expanded( // flex:1, // 占多少份 // child: IconContainer(Icons.search, color: Colors.blue,), // ), // 左侧固定宽度,右侧自适应 IconContainer(Icons.search, color: Colors.blue,), Expanded( flex:1, // 占多少份 child: IconContainer(Icons.search, color: Colors.orange,), ), ], );
Center( child: Stack( // alignment: Alignment.center, // 让里面所有的元素都居中 alignment: Alignment(-0.5, -1), // x, y轴 可以调整元素到任意位置 children: <Widget>[ Container( height: 400, width: 300, color: Colors.red, ), Text('我是一个文本',style: TextStyle( fontSize: 30, color: Colors.white ),) // 文本叠加在元素上面 ], ) );
多个元素需要定位:可以结合Align或者Positiond来实现页面的定位布局
Center( child: Container( height: 400, width: 300, color: Colors.red, child: Stack( children: <Widget>[ 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.send, size:60, color:Colors.white), ) ], ), ), );
Container( padding: EdgeInsets.all(ScreenAdapter.width(20)), child: Stack( children: <Widget>[ Align( alignment: Alignment.centerLeft, child: Text('忘记密码'), ), Align( alignment: Alignment.centerRight, child: InkWell( onTap: (){ Navigator.pushNamed(context, '/registerFirst'); }, child: Text('新用户注册'), ), ) ], ), ),
Center( child: Container( height: 400, width: 300, color: Colors.red, child: Stack( children: <Widget>[ 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.send, size:60, color:Colors.white), ) ], ), ), );
AspectRatio:设置调整子元素 child 的宽高比,是相对于父元素的,可以让子元素尽可能去铺满父元素
属性:aspectRatio(宽高比)、child(子组件)
AspectRatio(
aspectRatio: 3.0/1.0, // 高度是宽度的三分之一
child: Container(
color: Colors.pink,
),
);
属性:margin(外边距)、child(子组件)、Shape(阴影)
ListView( children: <Widget>[ Card( margin: EdgeInsets.all(10), child: Column( children: <Widget>[ ListTile( title: Text('张三', style: TextStyle(fontSize: 28),), subtitle: Text('高级工程师'), ), ListTile( title: Text('电话:13688888888'), ), ListTile( title: Text('地址:广州市天河区xxxx'), ), ], ), ) Card(...) ], );
例子2:
ListView( children: <Widget>[ Card( margin: EdgeInsets.all(10), child: Column( children: <Widget>[ AspectRatio( aspectRatio: 20/9, child: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg',fit: BoxFit.cover,), ), ListTile( leading:CircleAvatar( backgroundImage: NetworkImage('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'), ), title: Text('标题标题标题标题标题'), subtitle: Text('描述描述描述描述描述'), ) ], ), ) ], );
动态渲染卡片
List listData=[ { 'title':'哈哈哈哈哈哈哈1', 'des':'描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述', 'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', }, { 'title':'哈哈哈哈哈哈哈2', 'des':'描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述', 'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', }, { 'title':'哈哈哈哈哈哈哈3', 'des':'描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述', 'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', }, { 'title':'哈哈哈哈哈哈哈4', 'des':'描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述描述', 'imageUrl':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg', } ]; class HomeContent extends StatelessWidget{ @override Widget build(BuildContext context) { return ListView( children: listData.map((value){ print(value); return Card( margin: EdgeInsets.all(10), child: Column( children: <Widget>[ AspectRatio( aspectRatio: 20/9, child: Image.network(value['imageUrl'],fit: BoxFit.cover,), ), ListTile( leading:CircleAvatar( backgroundImage: NetworkImage(value['imageUrl']), ), title: Text(value['title']), subtitle: Text(value['des'], maxLines: 2,overflow: TextOverflow.ellipsis,), ) ], ), ); }).toList(), ); } }
跟Row、Column组件相似,但是Row和Column都是单行。
Wrap组件在mainAxis上空间不足时,则向crossAxis上去扩展显示。
class HomeContent extends StatelessWidget{ @override Widget build(BuildContext context) { return Wrap( spacing: 10, // 元素与元素之间的边距 runSpacing: 10, // 纵轴边距 // direction: Axis.vertical,// 改变方向 alignment: WrapAlignment.spaceAround, // 左右对齐 类似flex children: <Widget>[ MyButton('第一季'), MyButton('第一季11111'), MyButton('第一季'), MyButton('第一季'), MyButton('第一季'), MyButton('第一季'), MyButton('第一季'), MyButton('第一季'), MyButton('第一季'), MyButton('第一季'), MyButton('第一季'), ], ); } } class MyButton extends StatelessWidget{ final String text; //把当前key传过去 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: (){ }, ); } }
StatelessWidget 是无状态组件,状态不可变的widget (没法改变数据);
StatefulWidget 是有状态组件,持有的状态可能在 widget 生命周期改变。通俗的讲:如果我们想改变页面中的数据的话这个时候就需要用到 StatefulWidget
注意:
要在setState(() { });里面修改数据,跟react数据绑定有点相似
// 继承StatefulWidget抽象类,它里面有个抽象方法createState() class HomeContent extends StatefulWidget { @override _HomeContentState createState() => _HomeContentState(); } class _HomeContentState extends State<HomeContent> { int countNum=0; @override Widget build(BuildContext context) { return Column( children: <Widget>[ SizedBox(height: 200,), Chip( label: Text('${this.countNum}'), ), SizedBox(height: 20,), RaisedButton( child: Text('按钮'), onPressed: (){ setState(() { // 只有有状态组件里面才有,跟react数据绑定有点相似 this.countNum++ ; }); }, ) ], ); } }
class Tabs extends StatefulWidget{
final index;
Tabs({Key key, this.index=0}):super(key:key);
_TabsState createState()=> _TabsState(this.index);
}
class _TabsState extends State<Tabs>{
int _currentIndex;
_TabsState(index){
this._currentIndex = index ; // 赋值
}
}
Navigator.pushNamed(context, '/newscontent',arguments:{ "aid":this._list[index]["aid"] }); class NewsContent extends StatefulWidget { Map arguments; NewsContent({Key key,this.arguments}) : super(key: key); _NewsContentState createState() => _NewsContentState(this.arguments); } class _NewsContentState extends State<NewsContent> { Map arguments; List _list=[]; _NewsContentState(this.arguments); @override void initState() { // TODO: implement initState super.initState(); print(this.arguments); this._getData(); } }
或者简化
Navigator.pushNamed(context, '/newscontent',arguments:{ "aid":this._list[index]["aid"] }); class NewsContent extends StatefulWidget { Map arguments; NewsContent({Key key,this.arguments}) : super(key: key); _NewsContentState createState() => _NewsContentState(); } class _NewsContentState extends State<NewsContent> { var _content; @override void initState() { // TODO: implement initState super.initState(); print(widget.arguments); this._content = widget.arguments; // 间接获取上个类传过来的数据 } }
BottomNavigationBar是底部导航条,可以让我们定义底部Tab切换
1)点击的时候改变currentIndex
2)body来显示当前页面(根据currentIndex和页面列表,可以判断对应到哪个页面)
class Tabs extends StatefulWidget { @override _TabsState createState() => _TabsState(); } // 点击的时候改变currentIndex class _TabsState extends State<Tabs> { int _currentIndex = 0; List _pageList=[ HomePage(), CategoryPage(), MyPage(), ]; @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){ print(index); setState(() { this._currentIndex = index; }); }, iconSize: 36.0, fixedColor: Colors.orange, // 选中的颜色 type: BottomNavigationBarType.fixed, // 配置底部可以有多个按钮(按钮大于4个显示不了) items: [ BottomNavigationBarItem( icon: Icon(Icons.home), title: Text('首页') ), BottomNavigationBarItem( icon: Icon(Icons.category), title: Text('分类') ), BottomNavigationBarItem( icon: Icon(Icons.my_location), title: Text('我的') ) ], ), ); } }
Scaffold( appBar: AppBar( title:Text('Flutter Demo') ), floatingActionButton: Container( height: 80, width: 80, padding: EdgeInsets.all(8), margin: EdgeInsets.only(top: 10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(40), color:Colors.white ), child: FloatingActionButton( child: Icon(Icons.add), onPressed: (){ setState(() { this._currentIndex = 1; }); }, ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, }
通过 Navigator 组件管理路由导航,Navigator.push()跳转和 Navigator.pop()返回。
两种配置路由跳转的方式:1、基本路由 2、命名路由
传参跳转 Navigator.push()
class _HomePageState extends State<HomePage> { @override Widget build(BuildContext context) { return Column( children: <Widget>[ RaisedButton( child: Text('跳转到搜索页面'), onPressed: (){ // 路由跳转 Navigator.of(context).push( MaterialPageRoute( builder: (context)=>SearchPage(title:'哈哈哈',countNum:666) ) ); }, ) ], ); } } class SearchPage extends StatelessWidget { String title; int countNum; SearchPage({this.title='嘿嘿', this.countNum = 0}); @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( //浮动按钮 child: Text('返回'), onPressed: (){ Navigator.of(context).pop(); // 返回 }, ), appBar: AppBar( title: Text('搜索页面'), ), body: Text('搜索内容页${this.title}----->${this.countNum}'), ); } }
统一管理路由:
1)在根组件定义路由
2)使用Navigator.pushNamed(context, ‘/search’);
class MyApp extends StatelessWidget{ @override Widget build(BuildContext context) { return MaterialApp( home: Tabs(), routes: { // 定义命名路由(map类型) '/search':(context)=>SearchPage(), '/my':(context)=>MyPage() }, ); } } Column( children: <Widget>[ RaisedButton( child: Text('跳转到搜索页面'), onPressed: (){ Navigator.pushNamed(context, '/search'); // 路由跳转 }, ) ], );
命名路由传参
//固定写法 var onGenerateRoute=(RouteSettings settings) { // 统一处理 final String name = settings.name; final Function pageContentBuilder = routes[name]; if (pageContentBuilder != null) { if (settings.arguments != null) { final Route route = MaterialPageRoute( builder: (context) => pageContentBuilder(context, arguments: settings.arguments)); return route; }else{ final Route route = MaterialPageRoute( builder: (context) => pageContentBuilder(context)); return route; } } }; final routes={ '/':(context)=> Tabs(), '/search': (context, {arguments}) => SearchPage(arguments: arguments), '/my':(context)=>MyPage() }; class MyApp extends StatelessWidget{ @override Widget build(BuildContext context) { return MaterialApp( // home: Tabs(), // routes: { // map类型,命名路由 // '/search': (context) => SearchPage(), // '/my':(context)=>MyPage() // }, initialRoute: '/', //初始化的时候加载的路由 onGenerateRoute: onGenerateRoute ); } } class SearchPage extends StatelessWidget { String title; int countNum; final Map arguments; SearchPage({this.arguments,this.title='嘿嘿', this.countNum = 0}); @override Widget build(BuildContext context) { print(this.arguments); return Scaffold( floatingActionButton: FloatingActionButton( //浮动按钮 child: Text('返回'), onPressed: (){ Navigator.of(context).pop(); // 返回 }, ), appBar: AppBar( title: Text('搜索页面'), ), body: Text('搜索内容页${this.title}--->${this.countNum}--->${arguments!=null?arguments["id"]:"0"}'), // body: Text('搜索内容页${this.arguments}'), ); } } Column( children: <Widget>[ RaisedButton( child: Text('跳转到搜索页面'), onPressed: (){ Navigator.pushNamed(context, '/search',arguments: { "id":123 }); }, ) ], );
在有状态组件里面传参
Navigator.of(context).pop(); 返回到上一页
Navigator.of(context).pushReplacementNamed('/registerSecond'); 替换路由
// 返回到根路由
Navigator.of(context).pushAndRemoveUntil(
new MaterialPageRoute(builder: (context) => new Tabs(index:1)), // 要引入Tabs页面
(route) => route == null // 把前面所有路由都置为空
);
注意:加了leading就没有返回按钮,会替换掉返回按钮
Scaffold( appBar: AppBar( title:Text('appbar'), centerTitle: true, // 无论是安卓还是ios,标题都是居中显示 leading: IconButton( icon:Icon(Icons.menu), onPressed: (){ print('menu'); }, ), actions: <Widget>[ IconButton( icon: Icon(Icons.search), onPressed: (){ print('search'); }, ), IconButton( icon: Icon(Icons.settings), onPressed: (){ print('settings'); }, ) ], ), body: Text('sssssssss'), );
注意:TabBar和TabBarView的数量要对应起来
MaterialApp( home:DefaultTabController( length: 2, child: Scaffold( appBar: AppBar( title:Text('appbar'), centerTitle: true, // 无论是安卓还是ios,标题都是居中显示 leading: IconButton( icon:Icon(Icons.menu), onPressed: (){ print('menu'); }, ), bottom: TabBar( tabs: <Widget>[ Tab(text: '热门',), Tab(text: '推荐',), ], ), ), body: TabBarView( children: <Widget>[ ListView( children: <Widget>[ ListTile( title: Text('第一个tab'), ) ], ), ListView( children: <Widget>[ ListTile( title: Text('第二个tab'), ) ], ) ], ), ), ); )
把TabBar放appBar的title上
DefaultTabController( length: 2, child: Scaffold( appBar: AppBar( // title:Text('appbar'), title: Row( // 把tabBar放title里 children: <Widget>[ Expanded( child: TabBar( indicatorColor: Colors.yellow, // 指示器颜色 labelColor: Colors.yellow, unselectedLabelColor: Colors.white, // 未选中的颜色 indicatorSize: TabBarIndicatorSize.label, // 条变短 tabs: <Widget>[ Tab(text: '热门',), Tab(text: '推荐',), ], ), ) ], ), // bottom: centerTitle: true, // 无论是安卓还是ios,标题都是居中显示 leading: IconButton( icon:Icon(Icons.menu), onPressed: (){ print('menu'); }, ), ), body: TabBarView( // physics:NeverScrollableScrollPhysics(), 禁止pageView滑动 children: <Widget>[ ListView( children: <Widget>[ ListTile( title: Text('第一个tab'), ) ], ), ListView( children: <Widget>[ ListTile( title: Text('第二个tab'), ) ], ) ], ), ), );
1)多继承 with SingleTickerProviderStateMixin
2)定义 _tabController,初始化的时候赋值,传入两参数
3)在TabBar和TabBarView都要配置controller参数
这种方法的好处是可以调用一些方法,监听改变
class TabBarControllerPage extends StatefulWidget { @override _TabBarControllerPageState createState() => _TabBarControllerPageState(); } class _TabBarControllerPageState extends State<TabBarControllerPage> with SingleTickerProviderStateMixin { // 2)定义 _tabController,初始化的时候赋值,传入两参数 TabController _tabController; @override void initState() { super.initState(); _tabController = new TabController( vsync: this, length: 2 // 长度 ); _tabController.addListener((){ // 传入方法,监听改变 print(_tabController.index); // setState(() {}); // 来实现自定义操作 }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('TabBarController'), bottom: TabBar(// 3)在TabBar、TabBarView都要配置controller参数 controller: this._tabController, // 不要this也可以 tabs: <Widget>[ Tab(text: '热销',), Tab(text: '推荐',) ], ), ), body: TabBarView( // 3)在TabBar、TabBarView都要配置controller参数 controller: this._tabController, // 不要this也可以 children: <Widget>[ Center(child: Text('热销'),), Center(child: Text('推荐'),) ], ), ); } }
Scaffold(
appBar: AppBar( title: Text("Flutter App"), ),
drawer: Drawer( child: Text('左侧边栏'), ),
endDrawer: Drawer( child: Text('右侧侧边栏'), ),
);
Scaffold( appBar: AppBar( title:Text('Flutter Demo') ), drawer: Drawer( child: Column( children: <Widget>[ // DrawerHeader( // child: Text('哈哈哈哈'), // ), Row( children: <Widget>[ Expanded( child: DrawerHeader( child: Text('哈哈哈哈'), decoration: BoxDecoration( // color:Colors.yellow image: DecorationImage( image: NetworkImage('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'), fit: BoxFit.cover ) ), ), ) ], ), ListTile( leading: CircleAvatar( child: Icon(Icons.home), ), title: Text('我的'), ), Divider(), ListTile( leading: CircleAvatar( child: Icon(Icons.home), ), title: Text('我的'), ), Divider(), ListTile( leading: CircleAvatar( child: Icon(Icons.home), ), title: Text('我的'), ) ], ) ), endDrawer: Drawer( child: Text('右侧侧边栏'), ), }
上面的头部要自己去定义,UserAccountsDrawerHeader这个是给我们提供定义好的样式,格式比较固定
Scaffold( appBar: AppBar( title:Text('Flutter Demo') ), drawer: Drawer( child: Column( children: <Widget>[ // DrawerHeader( // child: Text('哈哈哈哈'), // ), Row( children: <Widget>[ Expanded( child: UserAccountsDrawerHeader( accountName:Text('哈哈'), accountEmail:Text('8888888@qq.com'), currentAccountPicture: CircleAvatar( backgroundImage: NetworkImage('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'), ), decoration: BoxDecoration( // color:Colors.yellow image: DecorationImage( image: NetworkImage('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'), fit: BoxFit.cover ) ), otherAccountsPictures: <Widget>[ Image.network('https://ucc.alicdn.com/pic/developer-ecology/de4c239d57ff4820811524c3860b1728.jpg'), Image.network('https://ucc.alicdn.com/pic/developer-ecology/91d2e73798c245a39650fa4476e785e0.jpg') ], ) ) ], ), ListTile( leading: CircleAvatar( child: Icon(Icons.home), ), title: Text('我的'), ), Divider(), ListTile( leading: CircleAvatar( child: Icon(Icons.home), ), title: Text('我的'), ), Divider(), ListTile( leading: CircleAvatar( child: Icon(Icons.home), ), title: Text('我的'), ) ], ) ), }
ListTile(
leading: CircleAvatar(
child: Icon(Icons.home),
),
title: Text('我的'),
onTap: (){
Navigator.of(context).pop(); // 隐藏侧边栏
Navigator.pushNamed(context, '/CategoryPage');
},
),
RaisedButton :凸起的按钮,其实就是 Material Design 风格的 Button FlatButton :扁平化的按钮
OutlineButton:线框按钮
IconButton :图标按钮 (用在导航栏)
ButtonBar:按钮组
FloatingActionButton:浮动按钮
1、如果要高度,要用Container组件包裹
2、onPressed:null 表示禁用,加颜色也是灰色
3、线框按钮加背景颜色无效
Scaffold( appBar: AppBar( title: Text('按钮演示'), actions: <Widget>[ IconButton( icon: Icon(Icons.settings), onPressed: (){}, ) ], ), body: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Row( children: <Widget>[ RaisedButton( child: Text('普通按钮'), onPressed: (){ print('普通按钮'); }, ), SizedBox(width: 10,), Container( height: 50, width: 100, child: RaisedButton( child: Text('有颜色有宽度按钮'), color: Colors.blue, textColor: Colors.white, onPressed: (){ print('有颜色有宽度按钮'); }, ), ), SizedBox(width: 10,), RaisedButton( child: Text('有阴影的按钮'), color: Colors.blue, textColor: Colors.white, elevation: 10, // 阴影 onPressed: (){ print('有阴影的按钮'); }, ), SizedBox(width: 10,), RaisedButton.icon( icon: Icon(Icons.search), label: Text('带图标按钮'), color: Colors.blue, textColor: Colors.white, onPressed: null, // 按钮禁用掉,所以为灰色 // onPressed: (){}, ) ], ), Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Expanded( // Expanded嵌套按钮,可以让它自适应 child: Container( height: 40, margin: EdgeInsets.all(10), child: RaisedButton( child: Text('自适应圆角按钮'), color: Colors.blue, textColor: Colors.white, shape: RoundedRectangleBorder( // 配置圆角 borderRadius: BorderRadius.circular(10) ), onPressed: (){ print('自适应圆角按钮'); }, ), ), ) ], ), Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Container( width: 100, height: 100, margin: EdgeInsets.all(10), child: RaisedButton( child: Text('有点击颜色的圆形按钮'), color: Colors.blue, textColor: Colors.white, splashColor: Colors.yellow, // 水波纹颜色 shape:CircleBorder( // 圆形按钮 side: BorderSide( color: Colors.white ) ), onPressed: (){ print('有点击颜色的圆形按钮'); }, ), ), FlatButton( child: Text('扁平化按钮'), color: Colors.blue, textColor: Colors.white, onPressed: (){ print('扁平化按钮'); }, ), SizedBox(width: 10,), OutlineButton( child: Text('线框按钮'), // color: Colors.blue, // 背景颜色没效果 textColor: Colors.blue, onPressed: (){ print('线框按钮'); }, ) ], ), Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ ButtonBar( children: <Widget>[ RaisedButton( child: Text('登录'), onPressed: (){ print('登录'); }, ), RaisedButton( child: Text('注册'), onPressed: (){ print('注册'); }, ), ], ) ], ) ], ), );
注意:double 必须指定会默认转换,不指定就要传double类型的数据(即80.0)
class MyButton extends StatelessWidget { final text; final pressed; // 方法 final double width; final double height; const MyButton({this.text='', this.pressed = null, this.width=80, this.height=80}); @override Widget build(BuildContext context) { return Container( height: this.height, width: this.width, child: RaisedButton( child: Text(this.text), onPressed: this.pressed, ), ); } } MyButton(text: '自定义按钮',height: 60, width: 100,pressed: (){ print('自定义按钮'); },)
Column( children: <Widget>[ TextField( decoration: InputDecoration( icon: Icon(Icons.people), hintText: '请输入用户名' // 类似placeholder ), ), SizedBox(height: 20,), TextField( obscureText:true, // 设置密码 decoration: InputDecoration( hintText: '请输入搜索的内容', border: OutlineInputBorder(), // 设置边框 labelText: '输入密码', // lable的名称 labelStyle: TextStyle(fontSize: 20) // 配置lable的样式 ), ), SizedBox(height: 20,), TextField( maxLines: 2, // 设置多行 decoration: InputDecoration( hintText: '多行文本框', border: OutlineInputBorder() ), ), ], ),
controller结合实例化的TextEditingController(),加上onChanged监听获取表单内容。
实例化TextEditingController()的作用是为了初始化的时候赋值,也可以不用,直接var _username;
class _TextFieldPageState extends State<TextFieldPage> { // 1.实例化(这个是赋初始值用的,如果不用赋初始值,可以直接var _username;) var _username=new TextEditingController(); @override void initState() { super.initState(); // 2.赋值 (可以加this,也可以不加) _username.text = '初始值'; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('TextField'), ), body: Padding( padding: EdgeInsets.all(20), child: Column( children: <Widget>[ TextField( decoration: InputDecoration( icon: Icon(Icons.people), hintText: '请输入用户名' // 类似placeholder ), // 3.把TextEditingController绑定在controller上 controller: _username, onChanged: (value){ // 4.监听赋值,_username.text就可以获取文本框的值 _username.text = value; }, ), SizedBox(height: 20,), Container( width: double.infinity, height: 40, child: RaisedButton( child: Text('登录'), onPressed: (){ print(_username.text); }, color: Colors.blue, textColor: Colors.white, ), ) ], ), ), ); } }
var flag=true; Column( children: <Widget>[ Checkbox( value: this.flag, onChanged: (v){ setState(() { this.flag = v; }); }, activeColor: Colors.green, ), Text(this.flag?'选中':'未选中'), SizedBox(height: 20,), CheckboxListTile( value: this.flag, title: Text("一级标题"), subtitle: Text("二级标题"), onChanged: (v){ setState(() { this.flag=v; }); }, activeColor: Colors.red, secondary: Image.network('https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg'), selected:this.flag ) ], ),
int sex=1; Row( children: <Widget>[ Text('男:'), Radio( value: 1, onChanged: (v){ setState(() { this.sex = v; }); }, groupValue: this.sex, ), Text('女:'), Radio( value: 2, onChanged: (v){ setState(() { this.sex = v; }); }, groupValue: this.sex, ) ], ), Row( children: <Widget>[ Text('${this.sex}'), Text(this.sex == 1?'男':'女') ], ), RadioListTile( value: 1, onChanged: (v){ setState(() { this.sex = v; }); }, groupValue: this.sex, title: Text('一级标题'), subtitle: Text('二级标题'), secondary: Icon(Icons.help), selected: this.sex == 1?true:false, ), RadioListTile( value: 2, onChanged: (v){ setState(() { this.sex = v; }); }, groupValue: this.sex, title: Text('一级标题'), subtitle: Text('二级标题'), secondary: Icon(Icons.help), selected: this.sex == 2?true:false, ), ])
属性:value(值)、onChanged(改变时触发)、activeColor(选中的颜色、背景颜色)
bool flag = true;
Switch(
value: this.flag,
onChanged: (v){
setState(() {
print(v);
this.flag = v;
});
},
)
找插件pub.dev,复制进去,ctrl+s保存(会有flutter packages get命令),没报错就是安装成功
// 引入库
import 'package:date_format/date_format.dart';
var now = DateTime.now();
@override
void initState() {
super.initState();
print(now); // 2020-02-15 14:20:00.823996
print(now.millisecondsSinceEpoch); // 转时间戳 1581747600823
print(DateTime.fromMillisecondsSinceEpoch(1581747600823)); // 转换为日期 2020-02-15 14:20:00.823
print(formatDate(DateTime.now(), [yyyy, '-', mm, '-', dd])); // 第三方插件转换日期 2020-02-15
}
showDatePicker、showTimePicker
import 'package:date_format/date_format.dart'; class DatePickerPage extends StatefulWidget { @override _DatePickerPageState createState() => _DatePickerPageState(); } class _DatePickerPageState extends State<DatePickerPage> { DateTime _nowDate = DateTime.now(); var _nowtime=TimeOfDay(hour: 9,minute: 20); _showDatePicker() async { /* showDatePicker( context: context, initialDate: _nowDate, firstDate: DateTime(1980), // 起始日期 lastDate: DateTime(2025) ).then((res){ print(res); }); */ var result = await showDatePicker( context: context, initialDate: _nowDate, // 初始值 firstDate: DateTime(1980), // 起始日期 lastDate: DateTime(2025) // 结束日期 ); // print(result); setState(() { this._nowDate = result; }); } _showTimePicker() async { var result = await showTimePicker( context: context, initialTime: _nowtime, ); setState(() { this._nowtime = result; }); } @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('DatePicker'), ), body: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ InkWell( child: Row( children: <Widget>[ Text('${formatDate(this._nowDate, [yyyy, '年', mm, '月', dd, '日'])}'), Icon(Icons.arrow_drop_down) ], ), onTap: _showDatePicker, ), InkWell( child: Row( children: <Widget>[ Text('${_nowtime.format(context)}'), Icon(Icons.arrow_drop_down) ], ), onTap: _showTimePicker, ) ], ) ], ), ); } }
// 引入 import 'package:flutter_cupertino_date_picker/flutter_cupertino_date_picker.dart'; class _DatePickerPageState extends State<DatePickerPage> { DateTime _dateTime=DateTime.now(); _showDatePicker(){ DatePicker.showDatePicker( context, pickerTheme: DateTimePickerTheme( showTitle: true, confirm: Text('确定', style: TextStyle(color: Colors.red)), cancel: Text('取消', style: TextStyle(color: Colors.cyan)), ), minDateTime: DateTime.parse("1980-05-12"), maxDateTime: DateTime.parse("2100-05-12"), initialDateTime: _dateTime, // dateFormat: "yyyy-MMMM-dd", dateFormat:'yyyy年M月d日 EEE,H时:m分', // show TimePicker pickerMode: DateTimePickerMode.datetime, // show TimePicker locale: DateTimePickerLocale.zh_cn, onCancel: () { debugPrint('onCancel'); }, onConfirm: (dateTime, List<int> index) { setState(() { _dateTime = dateTime; }); }, ); } @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('DatePicker'), ), body: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ InkWell( child: Row( children: <Widget>[ Text("${formatDate(_dateTime, [yyyy, '年', mm, '月', dd,' ',HH, ':', nn])}"), Icon(Icons.arrow_drop_down) ], ), onTap: _showDatePicker, ) ], ) ], ), ); } }
class _SwiperPageState extends State<SwiperPage> { List<Map> imgList = [ { 'url':'https://pic1.zhimg.com/70/v2-7d1401e35e2d544e7c38e4ec86f2b62c.jpg' }, { 'url':'https://image3.suning.cn/uimg/aps/material/157529162928370083.jpg' }, { 'url':'https://image.suning.cn/uimg/aps/material/158167381752182833.jpg' } ]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('轮播图'), ), body: Column( children: <Widget>[ Container( width: double.infinity, // height: 200, child: AspectRatio( // 自适应 aspectRatio: 16/9, // 宽度和高度是16比9 child: new Swiper( itemBuilder: (BuildContext context,int index){ return new Image.network( imgList[index]['url'], fit: BoxFit.fill, ); }, itemCount: imgList.length, pagination: new SwiperPagination(), // 小圆点 control: new SwiperControl(), // 左右箭头 loop: true, // 自动轮播 ), ) ) ], ) ); } }
AlertDialog、SimpleDialog、showModalBottomSheet、第三方库showToast
// 引入库 import 'package:fluttertoast/fluttertoast.dart'; class _DialogPageState extends State<DialogPage> { _alertDialog() async{ var result = await showDialog( context: context, builder: (context){ return AlertDialog( title: Text('提示信息!'), content: Text('您确定要删除吗?'), actions: <Widget>[ FlatButton( child: Text('取消'), onPressed: (){ Navigator.pop(context,'cancel'); // 把值返回出去 }, ), FlatButton( child: Text('确定'), onPressed: (){ Navigator.pop(context,'ok'); // 把值返回出去 }, ) ], ); } ); print(result); } // 选择弹窗 _simpleDialog() async{ var result =await showDialog( context: context, builder: (context){ return SimpleDialog( title: Text('选择内容'), children: <Widget>[ SimpleDialogOption( child: Text('option a'), onPressed: (){ Navigator.pop(context,'a'); }, ), Divider(), SimpleDialogOption( child: Text('option b'), onPressed: (){ Navigator.pop(context,'b'); }, ), ], ); } ); print(result); } // 底部弹出 _modalBottomSheet() async{ var result =await showModalBottomSheet( context: context, builder: (context){ return Container( height: 200, child: Column( children: <Widget>[ ListTile( title: Text('分享A'), onTap: (){ Navigator.pop(context,'分享A'); }, ), ListTile( title: Text('分享B'), onTap: (){ Navigator.pop(context,'分享B'); }, ) ], ), ); } ); print(result); } _toast(){ Fluttertoast.showToast( msg: "错误信息", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM, timeInSecForIos: 1, // 只在ios有效果 backgroundColor: Colors.black, textColor: Colors.white, fontSize: 16.0 ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('弹窗'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ RaisedButton( child: Text('alertDialog'), onPressed: _alertDialog, ), SizedBox(height: 20,), RaisedButton( child: Text('simpleDialog'), onPressed: _simpleDialog, ), SizedBox(height: 20,), RaisedButton( child: Text('showModalBottomSheet'), onPressed: _modalBottomSheet, ), SizedBox(height: 20,), RaisedButton( child: Text('toast-fluttertoast第三方库'), onPressed: _toast, ), SizedBox(height: 20,), ], ), ) ); } }
自定义 Dialog 对象,需要继承 Dialog 类,重写 build 函数。(默认的 Dialog 背景框是满屏的)
import 'dart:async'; RaisedButton( child: Text('alertDialog'), onPressed: (){ showDialog( context: context, builder: (context){ return MyDialog(title:'关于我们', content:'内容内容内容内容内容内容'); } ); }, ), // 继承Dialog,然后重写 class MyDialog extends Dialog{ String title; String content; MyDialog({this.title='', this.content=''}); // 定时器 _showTimer(context){ var timer; timer = Timer.periodic( // 要引入dart:async,才能用Timer Duration(milliseconds: 1000), (t) { // 触发回调函数 print('关闭'); Navigator.pop(context); t.cancel(); //取消定时器 timer.cancel(); }); } @override Widget build(BuildContext context) { _showTimer(context); return Material( type: MaterialType.transparency, child: Center( child: Container( width: 200, height: 200, color: Colors.white, child: Column( children: <Widget>[ Padding( padding: EdgeInsets.all(10), child: Stack( children: <Widget>[ Align( alignment: Alignment.center, child: Text('${this.title}'), ), Align( alignment: Alignment.topRight, child: InkWell( child: Icon(Icons.close), onTap: (){ Navigator.pop(context); }, ), ) ], ) ), Divider(), Container( width: double.infinity, child: Text('${this.content}', textAlign: TextAlign.left,), ) ], ), ), ) ); } }
1、JSON 字符串和 Map 类型的 转换
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 类型
2、使用 http 库进行网络请求
http
渲染的两种方法: 1)通过map来渲染 2)通过ListView.builder来渲染
{
"result": [{
"_id": "5ac0896ca880f20358495508",
"title": "精选热菜",
"pid": "0",
}, {
"_id": "5ac089e4a880f20358495509",
"title": "特色菜",
"pid": "0",
}
]
}
// 通过map来渲染数据 import 'package:http/http.dart' as http; import 'dart:convert'; class _HttpDemoState extends State<HttpDemo> { List _list=[]; @override void initState() { // 1. 通过initState获取数据 super.initState(); this._getData(); } _getData() async{ var apiUrl="http://localhost:4000/productlist"; var result=await http.get(apiUrl); // 2.请求数据 if(result.statusCode==200){ print(result.body); setState(() { this._list=json.decode(result.body)["result"]; // 3.赋值 }); }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){ return ListTile( title: Text(value["title"]), ); }).toList(), ):Text("加载中...") ); } }
// 通过ListView.builder来渲染 import 'package:http/http.dart' as http; import 'dart:convert'; class _HttpDemoState extends State<HttpDemo> { List _list=[]; @override void initState() { // 1. 通过initState获取数据 super.initState(); this._getData(); } _getData() async{ var apiUrl="http://localhost:4000/productlist"; var result=await http.get(apiUrl); // 2.请求数据 if(result.statusCode==200){ print(result.body); setState(() { this._list=json.decode(result.body)["result"]; // 3.赋值 }); }else{ print("失败${result.statusCode}"); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("请求数据Demo"), ), body: this._list.length>0?ListView.builder( //4.渲染数据 itemCount:this._list.length , itemBuilder: (context,index){ return ListTile( title: Text("${this._list[index]["title"]}"), ); }, ):Text("加载中...") ); } }
dio 是一个强大的 Dart Http 请求库,支持 Restful API、FormData、拦截器、请 求取消、Cookie 管理、文件上传/下载、超时、自定义适配器等
dio
// 对比
http.get(apiUrl);
Dio().get(apiUrl)
// post
Map jsonData={
'username':'哈哈哈哈',
'age':20
}
Response response = await Dio().post(apiUrl, data:jsonData)
import 'dart:convert'; import 'package:dio/dio.dart'; class _HttpDemoState extends State<HttpDemo> { List _list=[]; @override void initState() { super.initState(); this._getData(); } void _getData() async{ // 如果函数没有返回值,那么应声明为void类型,可加,可不加 var apiUrl="http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1"; Response result=await Dio().get(apiUrl); // print(json.decode(result.data)["result"]); setState(() { this._list=json.decode(result.data)["result"]; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("请求数据Dio Demo"), ), body: this._list.length>0?ListView( children: this._list.map((value){ return ListTile( title: Text(value["title"]), ); }).toList(), ):Text("加载中...") ); } }
Future<void> _onRefresh() async{ print('下拉刷新'); await Future.delayed(Duration(milliseconds:2000),(){ // 2秒后消失 print('请求数据完成') }) } @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("加载中...") ); }
ListView里的属性,通过ScrollController来监听滚动事件。
1)_scrollController.position.pixels 滚动的距离 (滚动条距离顶部的距离,下拉时值变大)
_scrollController.position.maxScrollExtent 总距离 (整个页面的高度,值不变)
两个值相等说明到底部了(一般让还没到底部就开始加载)
2)判断是否最后一页,是就不请求
ScrollController _scrollController = ScrollController(); //listview 的控制器 void initState() { super.initState(); this._getData(); _scrollController.addListener(() { if (_scrollController.position.pixels > _scrollController.position.maxScrollExtent-20) { print('滑动到了最底部'); _getData(); } }); } ListView.builder( itemCount: this._list.length, controller: _scrollController, itemBuilder: (context,index){ } )
import 'package:flutter/material.dart'; import 'dart:convert'; import 'package:dio/dio.dart'; class NewsPage extends StatefulWidget { NewsPage({Key key}) : super(key: key); _NewsPageState createState() => _NewsPageState(); } class _NewsPageState extends State<NewsPage> { List _list = []; int _page = 1; bool hasMore = true; //判断有没有数据 ScrollController _scrollController = new ScrollController(); @override void initState() { // TODO: implement initState super.initState(); this._getData(); //监听滚动条事件 _scrollController.addListener(() { print(_scrollController.position.pixels); //获取滚动条下拉的距离 print(_scrollController.position.maxScrollExtent); //获取整个页面的高度 if (_scrollController.position.pixels > _scrollController.position.maxScrollExtent - 40) { this._getData(); } }); } void _getData() async { if (this.hasMore) { var apiUrl = "http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=${_page}"; var response = await Dio().get(apiUrl); var res = json.decode(response.data)["result"]; setState(() { this._list.addAll(res); //拼接 this._page++; }); //判断是否是最后一页 if (res.length < 20) { setState(() { this.hasMore = false; }); } } } //下拉刷新 Future<void> _onRefresh() async { await Future.delayed(Duration(milliseconds: 2000), () { print('请求数据完成'); _getData(); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("新闻列表"), ), body: this._list.length > 0 ? RefreshIndicator( onRefresh: _onRefresh, child: ListView.builder( controller: _scrollController, itemCount: this._list.length, //20 itemBuilder: (context, index) {//19 if (index == this._list.length-1) { //列表渲染到最后一条的时候加一个圈圈 //拉到底 return Column( children: <Widget>[ ListTile( title: Text("${this._list[index]["title"]}", maxLines: 1), ), Divider(), _getMoreWidget() ], ); } else { return Column( children: <Widget>[ ListTile( title: Text("${this._list[index]["title"]}", maxLines: 1), ), Divider() ], ); } }, )) : _getMoreWidget(), ); } //加载中的圈圈 Widget _getMoreWidget() { if(hasMore){ return Center( child: Padding( padding: EdgeInsets.all(10.0), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Text( '加载中...', style: TextStyle(fontSize: 16.0), ), CircularProgressIndicator( strokeWidth: 1.0, ) ], ), ), ); }else{ return Center( child: Text("--我是有底线的--"), ); } } }
import 'package:flutter_html/flutter_html.dart'; Html( data: """ ${this._list.length>0?this._list[0]["content"]:''} """, //Optional parameters: padding: EdgeInsets.all(8.0), backgroundColor: Colors.white70, defaultTextStyle: TextStyle(fontFamily: 'serif'), linkStyle: const TextStyle( color: Colors.redAccent, ), onLinkTap: (url) { // open url in a webview } )
flutter_inappbrowser
在app里面打开一个浏览器来加载数据
flutter_inappbrowser: ^1.2.1
import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
Expanded(
child: InAppWebView(
initialUrl: "http://www.phonegap100.com/newscontent.php?aid=${this.arguments["aid"]}",
onProgressChanged: (InAppWebViewController controller, int progress) {
print(progress/100);
if((progress/100)>0.999){
setState(() {
this._flag=false;
});
}
},
),
)
import 'package:device_info/device_info.dart';
@override
void initState() {
super.initState();
this._getDevice();
}
_getDevice() async{
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
print('Running on ${androidInfo.model}'); // e.g. "Moto G (4)"
}
获取当前经度纬度,然后去数据库匹配,看看当前经度纬度最近的一些餐馆,显示出来
amap_location
1)获取PackageName
2)获取调式安全码
3)获取完key值后输入
import 'package:amap_location/amap_location.dart'; class _LocationPageState extends State<LocationPage> { double _longitude=0; double _latitude=0; @override void initState() { // TODO: implement initState super.initState(); this._getLocation(); } _getLocation() async{ //启动一下 await AMapLocationClient.startup(new AMapLocationOption( desiredAccuracy:CLLocationAccuracy.kCLLocationAccuracyHundredMeters )); //获取地理位置 var result = await AMapLocationClient.getLocation(true); print("经度:${result.longitude}"); print("纬度:${result.latitude}"); setState(() { this._longitude=result.longitude; this._latitude=result.latitude; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("地理定位演示"), ), body:Column( children: <Widget>[ Text("经度:${this._longitude}"), Text("纬度:${this._latitude}"), ], ), ); } }
import 'dart:io'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:dio/dio.dart'; class ImagePickerPage extends StatefulWidget { @override State<StatefulWidget> createState() { return _ImagePickerState(); } } class _ImagePickerState extends State<ImagePickerPage> { File _image; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("ImagePicker"), ), body: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ RaisedButton( onPressed: _takePhoto, child: Text("拍照"), ), RaisedButton( onPressed: _openGallery, child: Text("选择图库照片"), ), _buildImage() ], ), ) ); } /*拍照*/ _takePhoto() async { var image = await ImagePicker.pickImage(source: ImageSource.camera,maxWidth:400); setState(() { this._image= image; }); this._uploadImage(image); } /*相册*/ _openGallery() async { var image = await ImagePicker.pickImage(source: ImageSource.gallery,maxWidth:400); setState(() { this._image= image; }); } //定义一个组件显示图片 Widget _buildImage(){ if(this._image==null){ return Text("请选择图片..."); } return Image.file(this._image); } //上传图片 _uploadImage(_imageDir) async{ FormData formData = new FormData.from({ "name": "zhangsna 6666666666", "age": 20, "sex":"男", "file":new UploadFileInfo(_imageDir, "xxx.jpg"), }); var response = await Dio().post("http://jd.itying.com/imgupload", data: formData); print(response); } }
// node.js var express = require("express"); var router=express.Router(); var multiparty = require('multiparty'); //nodejs教程 40 router.get('/',function(req,res){ res.send('前台首页') }) //给编辑器提供的上传图片的接口 router.post('/imgupload',function(req,res){ console.log('imgupload'); var form = new multiparty.Form(); form.uploadDir='public/upload' /*设置图片上传的路径*/ form.parse(req, function(err, fields, files) { console.log(files); //文件 console.log(fields); //post数据 var path="/"+files.file[0].path; res.json({"success":"true","path":path}) /*给编辑器返回地址信息*/ }); }) module.exports=router;
video_playe
chewie
注意:页面销毁时,要结束视频播放
import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; import 'package:chewie/chewie.dart'; class ChewieVideoDemo extends StatefulWidget { ChewieVideoDemo({Key key}) : super(key: key); _ChewieVideoDemoState createState() => _ChewieVideoDemoState(); } class _ChewieVideoDemoState extends State<ChewieVideoDemo> { 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() { videoPlayerController.dispose(); chewieController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('在线视频播放'), ), body: Center( child: Chewie( controller: chewieController, ) ), ); } }
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<NetworkPage> { var subscription; String _stateText; @override void initState() { // TODO: implement initState super.initState(); subscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) { if(result==ConnectivityResult.wifi){ setState(() { _stateText="处于wifi"; }); }else if(result==ConnectivityResult.mobile){ setState(() { _stateText="处于手机网络"; }); }else{ setState(() { _stateText="没有网络"; }); } // Got a new connectivity status! }); } @override dispose() { super.dispose(); subscription.cancel(); //注意 } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("检测网络变化"), ), body:Center( child: Text("${_stateText}") ), ); } }
//设置值 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) // 获取 SharedPreferences prefs = await SharedPreferences.getInstance(); var data=prefs.getString("name"); // 删除值 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<StoragePage> { _saveData() async{ SharedPreferences sp=await SharedPreferences.getInstance(); sp.setString("username", "张三111"); sp.setString("age", "26"); } _getData() async{ SharedPreferences sp=await SharedPreferences.getInstance(); print(sp.getString("username")); print(sp.getString("age")); } _removeData() async{ SharedPreferences sp=await SharedPreferences.getInstance(); print(sp.remove("age")); } @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, ), SizedBox(height: 10), RaisedButton( child: Text('清除数据'), onPressed:_removeData, ) ]), ), ); } }
// util.js import 'package:shared_preferences/shared_preferences.dart'; class Storage{ static Future<void> setString(key,value) async{ SharedPreferences sp=await SharedPreferences.getInstance(); sp.setString(key, value); } static Future<String> getString(key) async{ SharedPreferences sp=await SharedPreferences.getInstance(); return sp.getString(key); } static Future<void> remove(key) async{ SharedPreferences sp=await SharedPreferences.getInstance(); sp.remove(key); } }
// 引用
import '../../common/Storage.dart';
onPressed: () async{
var username=await Storage.getString('username');
print(username);
},
barcode_scan
在android studio配置,如果错误有提示
// 配置权限
<uses-permission android:name="android.permission.CAMERA" />
<activity android:name="com.apptreesoftware.barcodescan.BarcodeScannerActivity"/>
从远程下载包
import 'package:flutter/material.dart'; import 'package:barcode_scan/barcode_scan.dart'; import 'package:flutter/services.dart'; class ScanPage extends StatefulWidget { ScanPage({Key key}) : super(key: key); _ScanPageState createState() => _ScanPageState(); } class _ScanPageState extends State<ScanPage> { String barcode; Future _scan() async { try { String barcode = await BarcodeScanner.scan(); setState(() { return this.barcode = barcode; }); } on PlatformException catch (e) { if (e.code == BarcodeScanner.CameraAccessDenied) { setState(() { return this.barcode = 'The user did not grant the camera permission!'; }); } else { setState(() { return this.barcode = 'Unknown error: $e'; }); } } on FormatException { setState(() => this.barcode = 'null (User returned using the "back"-button before scanning anything. Result)'); } catch (e) { setState(() => this.barcode = 'Unknown error: $e'); } } @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( child: Icon(Icons.photo_camera), onPressed: _scan, ), appBar: AppBar( title: Text("扫码"), ), body: Text("${barcode}") ); } }
1、Android App 升级执行流程
1)获取本地版本号
2)请求服务器获取服务器版本号
3)本地版本和服务器版本不一致提示升级,弹窗提示用户是否更新
4)用户确定升级,调用文件传输方法下载 apk 文件
5)监听下载进度
6)下载完成打开 Apk 进行安装
注意:在 Ios 中没法直接下载安装,如果版本不一致直接跳转到 Ios 应用对应的应用市场就 可以了。
2、配置 AndroidMenifest.xml 文件:
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
3、Android 升级 app 涉及的 API 库
package_info 检测版本号
path_provider 获取文件存储路径 (保存目录)
flutter_downloader 下载文件
open_file 打开文件插件
如何获取版本号(package_info),如何从服务器下载文件(flutter_downloader),获取本地存储路径,把它保存起来(path_provider),调用open_file打开文件
服务器的包替换本地的包,要对应用进行签名打包
配置版本号:
<manifest android:hardwareAccelerated="true" android:versionCode="1" android:versionName="0.0.1" package="io.jdshop.demo" xmlns:android="http://schemas.android.com/apk/res/android">
如果有key就用以前的key,没有就创建一个
这样就会对应用做正式打包,打包后就可以上传到应用市场
注意:这里要和第一次打包的路径一样,这样新版本就可以和老版本一样
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<AppVersionPage> { @override void 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: <Widget>[ 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升级演示"), ); } }
注意:
1、服务器的 App 版本必须大于本地 App 版本
2、本地 App 和服务器 App 的包名称 签名必须一致,这样的话服务器的包才可以替换本地 的包
3、安卓9.0以后要求https协议请求
import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; class UrlLauncher extends StatefulWidget { UrlLauncher({Key key}) : super(key: key); _UrlLauncherState createState() => _UrlLauncherState(); } class _UrlLauncherState extends State<UrlLauncher> { @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{ var tel = 'tel:10086'; if (await canLaunch(tel)) { await launch(tel); } else { throw 'Could not launch $tel'; } }, ), SizedBox(height: 10), RaisedButton( child: Text('发送短信'), onPressed: () async{ var 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:// alipays:// */ var url = 'alipays://'; if (await canLaunch(url)) { await launch(url); } else { throw 'Could not launch $url'; } }, ) ]), ))); } }
打开ios应用市场,获取应用市场地址,调用外部浏览器方式打开launch(url)
1)微信公众号(认证300、申请微信支付)、商户平台(生成商户平台账户)、开放平台(认证300,用于:第三方平台开发)的准备
2)开放平台创建应用
应用签名:
1、
// int countNum = 0; // 构造函数是常量,属性不是常量会报错
final int countNum = 0;
const HomeContent({key key}):super(key:key);
调用原生库的时候,可能会下载第三方库,下载可能会失败,可以打开android studio让它下载
(111,222,333).toList() // 转成数组[111, 222, 333]
double.Parse() // 可以将字符串string转换为double类型
https://www.jianshu.com/p/df225c203e96
Future 就是相当于promise异步语法
Future<DateTime> showDatePicker({})
如果函数没有返回值,那么应声明为void类型
1、线 Divider()
2、设置宽高空隙 SizeBox(height:10)
3、列表
ListTile(
leading:Container()
title:Text('标题')
subtitle:Text('描述~~~')
)
4、头像
ClipOval( child: Image.network('xxx',fit: BoxFit.cover,width: 60,height: 60,), )
处理圆形图片:把图片转成头像(推荐这种)
CircleAvatar(backgroundImage: NetworkImage('xxx'),)
5、循环
ListView(
children:listData.map((value){
return Card(...)
}).toList()
)
6、按钮组件RaisedButton
RaisedButton(
child: Text('女装'),
textColor: Theme.of(context).accentColor,
onPressed: (){ },
color: Theme.of(context).accentColor,
textTheme: ButtonTextTheme.primary
);
7、水墨点击组件
可以当做按钮组件,但是它没有样式
InkWell(
child: ,onTap:(){}
)
GestureDetector( // InkWell有水墨,GestureDetector没有
child: Container(
height: 400,
child: Text("底部"),
),
onTap: (){ return false ;}, // 阻止showModalBottomSheet弹窗点击消失
);
8、宽度100% width: double.infinity,
9、渐变颜色
Container(
width: ScreenAdapter.width(750),
height: ScreenAdapter.height(100),
child: Text('ssss'),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Color.fromRGBO(255, 170, 134, 1), Color.fromRGBO(255, 57, 78, 1)],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
)),
)
1)安装 ADB Commanads for VSCode扩展 (vscode要高版本,比如1.42.1版本)
2)真机用usb连接电脑 (终端输入adb devices可以看到有没有找到)
3)在vscode中按快捷键 Ctrl + Shift + P,输入Disconnect from any devices(切断所有连接),Reset connected devices port to :5555,最后选择Connect to device IP
4)输入flutter run,如果提示选择设备,输入flutter run -d DU3ADH14CB024315
参考了
另一种方案:
使用数据线连接手机和电脑,在ide端能显示移动设备名称
输入命令行adb tcpip 5555
输入命令行adb connect 你的手机ip地址(例: adb connect 192.168.1.2)
拔掉线,ide端若还能显示设备名称,即OK
参考
注意事项:手机要有足够内存,打开usb调试
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。