1、风格
我们之前的风格都是material
风格
import 'package:flutter/material.dart';
其实flutter还自带一种风格cupertino
import 'package:flutter/cupertino.dart';
Icon(CupertinoIcons.home)//cupertino风格的图标
2、函数返回值不确定
使用Future
定义
Future _test(){
return ***;
}
3、解决内容越界
使用SingleChildScrollView
解决越界问题,该组件是越界滚动效果。
4、异步请求改变界面的组件
使用FutureBuilder
组件
FutureBuilder(builder: (context,snapshot){
if(snapshot.hasData){//判断异步返回有没有值
}else{
}
},future:getHomepageContent()),
使用FutureBuilder
组件会有一个问题,当里面的子组件有多个的时候,往往我们更新的数据只需要刷新一个子组件就好了,但是使用FutureBuilder
会导致所有的子组件都会刷新
解决办法Flutter开发性能提升之:如何避免Widget重复Build
5、屏幕适配
随着移动手机的屏幕尺寸越来越多,我们怎么能够适配
使用flutter_ScreenUtil
flutter_ScreenUtil
注意如果添加跟适配ScreenUtil.init
需要放在TabPage中
@override
Widget build(BuildContext context) {
ScreenUtil.init(context, width: 750, height: 1334, allowFontScaling: false);
return Container(
width:ScreenUtil.setWidth(250),
height:ScreenUtil.setHeight(250)
);
}
6、点击事件调用手机的拨打电话
使用url_launcher 调用拨打电话
他可以打开网页、手机拨号、邮箱、短信
dependencies:
url_launcher: ^5.4.10
import 'package:url_launcher/url_launcher.dart';
_launchURL() async {//打开网页
const url = 'https://flutter.dev';
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
void _makePhoneCall(String phone) async {
注意拨打手机多了'tel'
例如 拨打1888888888
里面的值应该是tel:1888888888
if (await canLaunch('tel:'+phone)) {
await launch('tel:'+phone);
} else {
throw 'Could not launch $url';
}
}
7、切换导航或者页面状态保持
类似前端vue
的keep-live
效果 页面缓存
使用继承AutomaticKeepAliveClientMixin
这个类
使用这个类有三点
1、必须是statefulwidget组件
在我们需要保持状态的页面中加入
class _HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin{}
2、必须重构wantKeepAlive方法
在我们需要保持状态的页面中加入
@override
bool get wantKeepAlive => true;
3、需要在tabpage页面中body中套上IndexedStack
这个页面是我们的导航切换页面
class TabsPage extends StatefulWidget {
TabsPage({Key key}) : super(key: key);
@override
_TabsPageState createState() => _TabsPageState();
}
class _TabsPageState extends State<TabsPage> {
int curIndex = 0;
List<BottomNavigationBarItem> _list_bottom_item = [
BottomNavigationBarItem(icon: Icon(CupertinoIcons.home), title: Text("首页")),
BottomNavigationBarItem(icon: Icon(CupertinoIcons.search), title: Text("分类")),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.shopping_cart), title: Text("购物车")),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.profile_circled), title: Text("会员中心"))
];
List<Widget> list_page = [HomePage(), ListPage(), ShopCardPage(), UserPage()];
_TabsPageState();
@override
Widget build(BuildContext context) {
ScreenUtil.init(context, width: 750, height: 1334, allowFontScaling: false);
return Scaffold(
backgroundColor: Color.fromRGBO(244, 245, 245, 1),
bottomNavigationBar: BottomNavigationBar(
items: this._list_bottom_item,
onTap: (i) {
setState(() {
this.curIndex = i;
});
},
currentIndex: this.curIndex,
type: BottomNavigationBarType.fixed,
),
body: IndexedStack(重点
index:this.curIndex,当前索引
children:this.list_page
),
);
}
}
8、使用GridView/ListView.builder的避坑
问题1、在使用GridView/ListView.builder
如果这两个组件是被放在Cloumn、Row
中作为子组件那么将会报错
解决1、设定高度
当确定GridView/ListView.builder
这个两个组件的高度时,我们可以在外面包裹一层Container
并设置高度
Column(
children:[
Container(
height:500.0,
child:GridView.count()
)
]
)
解决2、换一种布局实现
可以使用Wrap
流布局实现
Column(
children:[Wrap(
children:[]
)]
)
问题2、当listView.buider/GridView.count()
循环的数据为空的时候 会报错
遇到的情况,例如要从后台获取
list
数据使用listView.buider/GridView.count()
渲染,会报错一瞬间(取决于获取数据的时间)
解决方法
使用三木运算判断数据是否为空
List data;
body: new Center(
重点
child: data == null ? Container() : new ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int position) {
return new ListTile(
title: new Text('${data[position]['name']}'),
);
}),
),
9、上拉加载/下拉刷新
10、通过后台返回的数据我们可以建立模型
建立模型就类似于
java
中的数据模型
dynamic
未知类型
案例:例如后台返回的数据格式
{
"code" : 0,
"message" : "",
"result" : [
{
"userId" : "001",
"userName" : "十年之后",
"email" : "592885211@qq.com"
},
{
"userId" : "002",
"userName" : "十年之后2",
"email" : "5928852112@qq.com"
},
{
"userId" : "003",
"userName" : "十年之后3",
"email" : "5928852113@qq.com"
},
{
"userId" : "004",
"userName" : "十年之后4",
"email" : "5928852114@qq.com"
}
***
]
}
构建数据模型
//这是每个Map的模型
class UserModel{
String userId;
String userName;
String email;
UserModel({//构造函数
this.userId,
this.userName,
this.email
});
//工厂模式
factory UserModel.formJson(dynamic json){
return UserModel(//其实这一步是调用构造函数
userId:json['userId'],
userName:json['userName'],
email:json['email'],
)
}
}
//有了每个Map的模型,我就在建一个List Map模型
class UserModelList{
List<UserModel> modelList; //每一个List的元素 都是UserModel类型
UserModelList(this.modelList);//构造函数
//工程模式
factory UserModelList.formJson(List json){ //注意这里 是list 我们传进来的肯定是个数据
return UserModelList(
json.map((i)=>UserModel.formJson(i)).toList()//遍历每一项
);
}
}
使用数据模型
在需要模型的页面引入刚刚建立的模型
var data = await getDataApi()//假如这是借口返回的数据
UserModelList userList = UserModelList.formJson(data);
//需要保证data是个数组,因为我们在创建UserModelList 定义了传入List
userList.modelList;//就是我们的数据了 这个modelList就是上面的变量
11、 状态管理
类似于Vuex
用于共享数据的管理
使用provider
请注意谷歌官方的状态管理插件已被弃用
dependencies:
provider: ^4.1.3
我们在lib
目录下创建文件夹provide
,继续在provide
创建counter.dart
文件
我们不按照文档的例子来 这次我们改变Map中的数据
//count.dart
import 'package:flutter/material.dart';
class Counter with ChangeNotifier{
Map userInfo = {
"userName" : "十年之后"
};
//创建改变数据的函数通过调用该函数我们做到改变数据
void setUserName(String name){
userInfo['userName'] = name;
notifyListeners();//通知去监听视图改变
}
}
然后我们在main.dart
中改造
import 'package:provider/provider.dart';
import './provide/counter.dart';
void main() {
runApp(
MultiProvider(
providers:[
ChangeNotifierProvider(
create:(_)=>Counter())
],
child:MyApp()
)
);
}
最后一步 我们在需要用到数据的页面中使用
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../provide/count.dart';
class ListPage extends StatelessWidget {
const ListPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Column(
children: <Widget>[
//用法在这里
Consumer<Counter>(builder:(context,t,child){
return Text(t.userInfo["userName"])
})
//用法在这里
RaisedButton(onPressed: () => Provider.of<Counter>(context, listen:false).setUserName('十年之后V5');child: Text("改变用户名"),)
],
),
),
);
}
}
如果我们要监听某个存储的值,也就是:改了某个数据,我们视图也立即改变用
Provider.of<Counter>(context, listen:false).get_userInfo["userName"]
该方法并不能做到监听数据的改变,我们应该使用
context.watch<Counter>().get_userInfo["userName"];
结束语:我们在创建个页面 使用该状态管理 会发现 视图也会一起改变
12、分类页面点击左侧右侧内容相应改变
因为我们设计的内容结构是
Row(
chidren : [
Left(),
Right(),
]
)
很明显这两个组件没有通信,但是我们又有数据的交互,我们可以使用状态管理来实现
class ChildCategory with ChangeNotifier, DiagnosticableTreeMixin {
List childCategoryList = [];
List get get_childList =>childCategoryList;
void setChildCategoty(List list){
childCategoryList = list;
notifyListeners();
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(ObjectFlagProperty('get_childList', get_childList));
}
}
实现思路左侧点击改变状态管理中的数据
context.read<ChildCategory>().setChildCategoty(this.cetegorryLeftData[index].bxMallSubDto);
//this.cetegorryLeftData[index].bxMallSubDto这个就是我们需要重新复制的数据
右侧获取改变的数据
我们可以在全局定义一个变量
然后在
@override build 进行赋值
listRightData = context.watch<ChildCategory>().get_childList;
13、企业级路由
使用fluro
插件
dependencies:
fluro: "^1.6.3"
import 'package:fluro/fluro.dart';
然后我们在MyApp builder
函数中
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final router = Router();}
}
然后我们创建一个路由文件,所有的路由Handler
配置都放在这里面
import 'package:flutter/material.dart';
import 'package:fluro/fluro.dart';
import '../pages/details_page.dart';//这是我们需要跳转的页面
Handler detailsHanderl =Handler(
handlerFunc: (BuildContext context,Map<String,List<String>> params){
String goodsId = params['id'].first;
print('index>details goodsID is ${goodsId}');
return DetailsPage(goodsId);
}
);
然后我们在创建路由配置文件
import 'package:flutter/material.dart';
import 'package:fluro/fluro.dart';
import './route_handler.dart' //导入我们刚刚配置的Handler
class Routes{
static String rootPath = "/";
static String detailsPath = "/details";//商品详情的路由
static void configureRoutes(Router router){
//配置下404 空路由
router.notFoundHandler= Handler(
handlerFunc:(BuildContext context,Map<String,List<String>> params){
print("Not Found");
return TabsPage();//可以跳转到我们写的tabs页面中
}
)
router.define(detailsPath,Handler : detailsHanderl)
}
}
为了方便我们可以静态化路由一下(实例化)
创建一个文件application.dart
import 'package:fluro/fluro.dart';
class Application{
static Router router;
}
再然后我们在MyApp
builder中
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final router = Router();
Routes.configureRoutes(router);//重点
Application.router=router;//重点
return MaterialApp(
onGenerateRoute: Application.router.generator,//重点
);
}
}
路由跳转
Application.router.navigateTo(context, "/detail?id=456456&ab=a456");
如果我们跳转至导航页面即Tab
页面不希望他有返回页面按钮
Application.router.navigateTo(context, "/tab?curIndex=2",clearStack: true);
14、html转换widget
使用插件flutter_html
dependencies:
flutter_html: ^1.0.0
import 'package:flutter_html/flutter_html.dart';
Html( //Html组件
data: dataDic['information'], //html数据
)
15、数据持久化
类似于前端的localstoare
使用谷歌自带shared_preferences
注意此shared_preferences
不能缓存List<Map>
类型的数据
即:[{}]
不能被缓存我们可以toString
dependencies:
shared_preferences: ^0.5.7+3
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:shared_preferences/shared_preferences.dart';
class ShopCardPage extends StatefulWidget {
ShopCardPage({Key key}) : super(key: key);
@override
_ShopCardPageState createState() => _ShopCardPageState();
}
class _ShopCardPageState extends State<ShopCardPage> {
//重点
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
List<String> testList = [];
@override
Widget build(BuildContext context) {
getList();//重点
return Scaffold(
appBar: AppBar(title: Text("购物车"),centerTitle: true,),
body: Container(
child: Column(
children: <Widget>[
Container(
width: double.infinity,
height: ScreenUtil().setHeight(500.0),
child: ListView.builder(itemBuilder: (context,index){
return Container(
margin: EdgeInsets.only(bottom: 10.0),
child: Text(this.testList[index]),
);
},itemCount: testList.length,),
),
Row(
children: <Widget>[
RaisedButton(onPressed: ()=>this.setList(),child: Text("增加数据"),),
RaisedButton(onPressed: ()=>this.remList(),child: Text("删除数据"),),
],
)
],
),
),
);
}
//获取数据 查
void getList()async{
final SharedPreferences prefs = await _prefs;//重点
var infoData = prefs.getStringList("testInfo");
setState(() {
testList = infoData == null ? [] : infoData;
});
}
//增
void setList()async{
final SharedPreferences prefs = await _prefs;//重点
testList.add("十年之后_flutter");
prefs.setStringList("testInfo", testList);
this.getList();
}
//删
void remList()async{
final SharedPreferences prefs = await _prefs;//重点
prefs.remove("testInfo");
setState((){
testList=[];
});
}
}