Flutter开发笔记
1. 目录结构
文件夹 | 作用 |
---|---|
android | Android平台相关代码 |
ios | iOS平台相关代码 |
lib | Flutter相关代码,我们主要编写的代码就在这个文件夹 |
test | 用于存放测试代码 |
pubspec.yaml | 配置文件,一般存放第三方库的依赖 |
2. 入口文件和入口方法
每一个flutter项目的lib目录里都有一个main.dart入口文件
引入material.dart
import 'package:flutter/material.dart';
void main() {
runApp(...);
}
简写:
void main() => runApp(...);
main方法是dart的入口方法,runApp方法是flutter的入口方法。
3. 自定义组件
MyApp是一个自定义组件。
// 自定义组件
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context) {
// TODO: implement build
return Center(
child: Text(
'Hello 112',
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 40.0,
// color: Colors.yellow,
color: Color.fromRGBO(244, 233, 121, 0.5),
),
)
);
}
}
StatelessWidget
无状态组件,状态不可变的widget(快捷键stless)
StatefulWidget
有状态组件,持有的状态可能在widget的生命周期改变(快捷键stful)
在flutter中所有和数字相关的参数都是double类型
4. 使用MaterialApp和Scaffold两个组件装饰App
-
MaterialApp
MaterialApp是一个方便的Widget,它封装了应用程序实现Material Design所需要的
一些Widget。一般作为顶层 widget使用。常用属性:
home(主页)title(标题)color(颜色)theme(主题)routes(路由)…
-
Scaffold
Scaffold是 Material Design布局结构的基本实现。此类提供了用于显示 drawer.
snackbar和底部sheet的 API。Scaffold有下面几个主要属性:
appBar -显示在界面顶部的一个 AppBar。body -当前界面所显示的主要内容 Widget。
drawer -抽屉菜单控件。…
使用o在Android和iOS模式之间进行切换
在Android Studio中,点击右侧Flutter Inspector -> Platform 可在Android和iOS之间切换
class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Demo'),
),
body: HomeContent(),
),
theme: ThemeData(
primarySwatch: Colors.yellow
),
);
}
}
5. 有状态组件
构造方法:使用快捷键stful
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
// TODO: implement build
return ...;
}
}
setState()
改变State的方法
BottomNavigationBarItem
现在使用label来替代title
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: '首页',
),
6. 普通路由跳转
RaisedButton(
child: Text("跳转到搜索页面"),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SearchPage()
)
);
};
);
Search.dart
import 'package:flutter/material.dart';
class SearchPage extends StatelessWidget {
const SearchPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.home),
onPressed: () {
Navigator.of(context).pop();
},
),
appBar:AppBar(
title: Text("搜索页面"),
) ,
body: Text("搜索页面内容区域"),
);
}
}
7. 命名路由传参
-
配置路由(main.dart)
import 'package:flutter_test_app/pages/tabs/Search.dart';
final routes = { '/search': (context, {arguments}) => SearchPage(arguments: arguments), };
-
通过onGenerateRoute属性统一处理(main.dart)
// ignore: missing_return onGenerateRoute:(RouteSettings settings) { // 统一处理 final String name = settings.name; final Function pageContentBuilder = this.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; } } }
-
发送参数(Home.dart)
Navigator.pushNamed(context, '/search', arguments: { "id": 123 });
不需要传参时正常调用即可
Navigator.pushNamed(context, '/product');
-
接收参数(Search.dart)
final arguments; SearchPage({this.arguments});
... body: Text("搜索页面内容区域${arguments != null ? arguments['id'] : 0}"), ...
8. 封装路由
将上面的路由配置全部封装到/lib/routes/Routes.dart中
import 'package:flutter/material.dart';
import 'package:flutter_test_app/pages/Tabs.dart';
import 'package:flutter_test_app/pages/tabs/ProductInfo.dart';
import 'package:flutter_test_app/pages/tabs/Search.dart';
final routes = {
'/': (context, {arguments}) => Tabs(),
'/search': (context, {arguments}) => SearchPage(arguments: arguments),
'/productInfo': (context, {arguments}) => ProductInfoPage(arguments: arguments),
};
// ignore: missing_return, top_level_function_literal_block
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;
}
}
};
main.dart
import 'package:flutter_test_app/routes/Routes.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
initialRoute: '/', // 初始化时加载的路由
onGenerateRoute: onGenerateRoute
);
}
}
在Search.dart中有一个按钮,点击后可跳转到/productInfo
...
RaisedButton(
child: Text("跳转到商品详情"),
onPressed: () {
Navigator.pushNamed(context, "/productInfo", arguments: {
"pid": 456,
"pname": "iPhone"
});
}
)
...
ProductInfo.dart
import 'package:flutter/material.dart';
class ProductInfoPage extends StatefulWidget {
Map arguments;
ProductInfoPage({this.arguments});
@override
_ProductInfoPageState createState() => _ProductInfoPageState(arguments: this.arguments);
}
class _ProductInfoPageState extends State<ProductInfoPage> {
Map arguments;
_ProductInfoPageState({this.arguments});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("商品详情"),
),
body: Container(
child: Text("商品详情PID: ${arguments["pid"]}---PNAME: ${arguments["pname"]}"),
),
);
}
}
另一种方法:
我们并不需要把参数在创建state实例的时候传入,state可以直接访问widget.channelName获取到组件的属性。
传入参数:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => new CallPage(
channelName: _channelController.text,
)));
接收参数:
class CallPage extends StatefulWidget {
final String channelName;
const CallPage({Key key, this.channelName}) : super(key: key);
@override
_CallPageState createState() => _CallPageState();
}
class _CallPageState extends State<CallPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.channelName),
),
backgroundColor: Colors.black,
body: Center(
child: Stack(
children: <Widget>[],
),
),
);
}
}
9. 替换路由和返回根路由
替换路由
Navigator.of(context).pushReplacementNamed('...');
返回根路由(跳转并清空路由栈)
Navigator.of(context).pushAndRemoveUntil(new MaterialPageRoute(builder: (context) => new Tabs()), (route) => route == null);
返回到指定的Tab页面,加一个参数index传递
Tabs.dart
class Tabs extends StatefulWidget {
final index;
Tabs({Key key, this.index = 0}) : super(key: key);
@override
_TabsState createState() => _TabsState(this.index);
}
class _TabsState extends State<Tabs> {
int _currentIndex;
_TabsState(index) {
this._currentIndex = index;
}
...
}
Navigator.of(context).pushAndRemoveUntil(new MaterialPageRoute(builder: (context) => new Tabs(index: 1)), (route) => route == null);
10. 使用TabController实现自定义TabBar
- 被创建的组件要继承StatefulWidget和SingleTickerProviderStateMixin
- 创建一个TabController,在生命周期initState时初始化
- 在调用TabBar和TabBarView时都要配置controller
import 'package:flutter/material.dart';
class CategoryPage extends StatefulWidget {
@override
_CategoryPageState createState() => _CategoryPageState();
}
// 继承SingleTickerProviderStateMixin
class _CategoryPageState extends State<CategoryPage> with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState() {
super.initState();
_tabController = new TabController(length: 2, vsync: this);
// 可以在这里加入一些自定义方法,如监听事件
_tabController.addListener(() {
print(_tabController.index);
});
}
// 生命周期结束时摧毁,可省略
@override
void dispose() {
super.dispose();
_tabController.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("TabBarControllerPage"),
bottom: TabBar(
controller: this._tabController,
tabs: <Widget>[
Tab(text: "热销",),
Tab(text: "推荐",),
],
),
),
body: TabBarView(
controller: this._tabController,
children: <Widget>[
Center(child: Text("热销")),
Center(child: Text("推荐")),
],
),
);
}
}
11. 侧边栏
drawer: Drawer(
child: Column(
children: <Widget>[
Row(
children: [
Expanded(
child: UserAccountsDrawerHeader(
accountName: Text("admin"),
accountEmail: Text("123@qq.com"),
currentAccountPicture: CircleAvatar(
backgroundImage: NetworkImage("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=4131746241,2477555401&fm=11&gp=0.jpg"),
),
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1604332250193&di=a70283d0c2606afe5d7741a0762f839d&imgtype=0&src=http%3A%2F%2Fbenyouhuifile.it168.com%2Fforum%2Fday_100429%2F1004291840008539c8ac02cd5c.jpg"),
fit: BoxFit.cover
)
),
),
)
],
),
ListTile(
leading: CircleAvatar(
child: Icon(Icons.home_outlined),
),
title: Text("我的空间"),
),
ListTile(
leading: CircleAvatar(
child: Icon(Icons.people),
),
title: Text("用户中心"),
onTap: () {
Navigator.of(context).pop(); // 隐藏侧边栏
Navigator.pushNamed(context, "/user");
},
),
ListTile(
leading: CircleAvatar(
child: Icon(Icons.settings),
),
title: Text("设置中心"),
),
],
),
),
12. 两种循环建立ListView的方法
ListView(
children: this._list.map((value) {
return ListTile(
title: Text(value["title"]),
);
}).toList(),
)
利用ListView.builder
ListView.builder(
itemCount: this._list.length,
itemBuilder: (context, index) {
return ListTile(
title: Text("${this._list[index]["title"]}"),
)
}
)
13. 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)); 成Map类型
14. 使用http库请求接口
import 'dart:convert';
import 'package:http/http.dart' as http;
_getData() async {
var apiUrl = "http://a.itying.com/api/productlist";
var response = await http.get(apiUrl);
if (response.statusCode == 200) {
print(response.body);
setState(() {
this._list = json.decode(response.body)["result"];
});
} else {
print("Failed${response.statusCode}");
}
}
15. 使用dio库请求接口
import 'dart:convert';
import 'package:dio/dio.dart';
_getData() async {
var apiUrl = 'http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1';
Response response = await Dio().get(apiUrl);
print(json.decode(response.data)["result"]);
setState(() {
this._list = json.decode(response.data)["result"];
});
}