Flutter从零到∞学习笔记
-
有状态widget:StatefulWidget和无状态widget:StatelessWidget 前者不需要实现Widget build(BuildContext context)。
具体的选择取决于widget是否需要管理一些状态
-
在Dart语言中使用下划线前缀标识符,会强制其变成私有的。
-
Icons.favorite Icons类里面有很多默认图标
-
isOdd 是否奇数 2.isOdd -> false 1.isOdd -> true
-
pushSaved “”开头的自动转成私有(方法和变量)
-
导航栏添加按钮和事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text( 'Startup Name Generator' ), actions: <Widget>[ / / AppBar 添加一个按钮 样式为 list 事件是_pushSaved new IconButton(icon: new Icon(Icons. list ), onPressed: _pushSaved) ], ), body: _buildSuggestions(), ); } / / tooltip 长时间按下的提示文字 IconButton(icon: new Icon(Icons.search), tooltip: 'Search' , onPressed: null) |
- 界面跳转方法
1 2 3 4 5 6 7 8 9 10 11 | Navigator.of(context).push( new MaterialPageRoute( builder: (context) { }, ), ); |
- 一行函数写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | / / 多行 void main() { runApp( new Center( child: new Text( 'Hello, world!' , textDirection: TextDirection.ltr, ), ) ) } / / 一行 void main() = > runApp(new MyApp()); |
-
// Material 是UI呈现的“一张纸”
-
请确保在pubspec.yaml文件中,将flutter的值设置为:uses-material-design: true。这允许我们可以使用一组预定义Material icons。
-
Row(横向排列)和Column(纵向排列)
1 2 3 4 5 6 7 8 9 10 11 12 13 | child: new Row( children: <Widget>[ new ..., new ..., new ..., ], ) |
1 2 3 4 5 6 7 8 9 10 11 12 13 | child: new Column( children: <Widget>[ new ..., new ..., new ..., ], ), |
-
cached_network_image 图片占位和淡入淡出
-
push
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Navigator.push( context, new MaterialPageRoute(builder: (context) = > new 新界面), ); / / 如果需要传值: 新界面({Key key, @required this.接收字段的名字}) : super (key: key); pop Navigator.pop(context); |
-
import 'dart:convert'; // package将响应内容转化为一个json Map
-
// 使用fromJson工厂函数,将json Map 转化为一个Post对象
1 | new Post.fromJson(json); |
-
future参数是一个异步的网络请求
-
import 'dart:io'; // 添加请求的headers
-
// 长连接
1 2 3 | import 'package:web_socket_channel/io.dart' ; import 'package:multi_server_socket/multi_server_socket.dart' ; |
- // 网络请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | Future<Post> fetchPost() async { final response = await http.get( '[http://jsonplaceholder.typicode.com/posts/1](http://jsonplaceholder.typicode.com/posts/1)' ); final responseJson = json.decode(response.body); return new Post.fromJson(responseJson); } / / 请求添加headers / * Future<Post> fetchPost() async { final response = await http.get( '[https://jsonplaceholder.typicode.com/posts/1](https://jsonplaceholder.typicode.com/posts/1)' , headers: {HttpHeaders.AUTHORIZATION: "Basic your_api_token_here" }, ); final json = jsonDecode(response.body); return new Post.fromJson(json); } * / new FutureBuilder<Post>( future: fetchPost(), builder: (context, snapshot) { return new CircularProgressIndicator(); } ) |
- 长连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | / / 连接长连接 IOWebSocketChannel.connect('[ws: / / echo](ws: / / echo / ).[websocket.org](http: / / websocket.org / )’) / / 接收消息 new StreamBuilder( stream: widget.channel.stream, builder: (context, snapshot) { return new Padding( child: new Text(snapshot.hasData ? '${snapshot.data}' : ''), padding: const EdgeInsets.symmetric(vertical: 20.0 ) ); } ) / / 发送消息 widget.channel.sink.add(_textController.text); / / 关闭长连接 widget.channel.sink.close(); |
- 在Flutter中添加资源和图片
https://flutterchina.club/assets-and-images/
- 标准widget:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | Container 添加 padding, margins, borders, background color, 或将其他装饰添加到widget. GridView 将 widgets 排列为可滚动的网格. ListView 将widget排列为可滚动列表 Stack 将widget重叠在另一个widget之上. Material Components: Card 将相关内容放到带圆角和投影的盒子中。 ListTile 将最多 3 行文字,以及可选的行前和和行尾的图标排成一行 |
- pubspec.yaml中添加字体 注意缩进对齐 注意缩进对齐 注意缩进对齐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | - asset 路径是与pubspec.yaml平级的文件路径 flutter: # Include the Material Design fonts. uses - material - design: true fonts: - family: Rock Salt fonts: # [https://fonts.google.com/specimen/Rock+Salt](https://fonts.google.com/specimen/Rock+Salt) - asset: fonts / Arial - Unicode .ttf - family: VT323 fonts: # [https://fonts.google.com/specimen/VT323](https://fonts.google.com/specimen/VT323) - asset: fonts / Arial - Unicode .ttf - family: Ewert fonts: # [https://fonts.google.com/specimen/Ewert](https://fonts.google.com/specimen/Ewert) - asset: fonts / Ewert - Regular.ttf |
- 比如一个关闭按钮在
1 2 3 4 5 6 7 | new Row(mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ new FlatButton(onPressed: () { }, child: Icon(Icons.close)) ],); |
- 分割线
new Divider(color: Colors.lightBlue,)
- 自定义Icon
new Image.asset(“图片路径", width: 20.0, height: 20.0,)
- 按钮宽高
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | 001 、 new Padding(padding: new EdgeInsets.fromLTRB( 48.0 , 20.0 , 48.0 , 20.0 ), child: new Row( children: <Widget>[ new Expanded(child: new RaisedButton(onPressed: (){ }, / / 设置控件的高度 child: new Padding(padding: new EdgeInsets.fromLTRB( 0.0 , 10.0 , 0.0 , 10.0 ), child: new Text( "登录" , style: TextStyle(color: Colors.white) ), ), color: Colors.brown, ), ), ], ), ), 002 、 new Container( width: MediaQuery.of(context).size.width - 48 * 2 , padding: new EdgeInsets.only(top: 40.0 ), child: new RaisedButton(onPressed: (){ }, / / 设置控件的高度 child: new Padding(padding: new EdgeInsets.fromLTRB( 0.0 , 10.0 , 0.0 , 10.0 ), child: new Text( "登录" , style: TextStyle(color: Colors.white) ), ), color: Colors.brown, ), ), 003 、 Widget _bigButton(String text, double lSpace, double rSpace) { return new Container( width: MediaQuery.of(context).size.width - lSpace - rSpace, height: 48.0 , margin: new EdgeInsets.only(left: lSpace, right: rSpace), color: Colors.white54, padding: new EdgeInsets.only(top: 0.0 ), child: new RaisedButton(onPressed: (){ print (text); }, child: new Padding(padding: new EdgeInsets.fromLTRB( 0.0 , 0.0 , 0.0 , 0.0 ), child: new Text(text, style: TextStyle(color: Colors.white) ), ), color: Colors.brown, ), ); } |
- 设备尺寸
1 | MediaQuery.of(context).size.width |
- 设备像素密度
1 | MediaQuery.of(context).devicePixelRatio |
- 状态栏高度
1 | MediaQuery.of(context).padding.top |
-
担心键盘挡住控件,可以使用 SingleChildScrollView,将SingleChildScrollView当做容器。
-
一个超级简单界面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | import 'package:flutter/material.dart' ; class RegisterPage extends StatelessWidget { @override Widget build(BuildContext context) { return new Scaffold( backgroundColor: Colors.black, body: new RegisterWidget(), ); } } class RegisterWidget extends StatefulWidget { RegisterWidgetState createState() = > RegisterWidgetState(); } class RegisterWidgetState extends State<RegisterWidget> { @override Widget build(BuildContext context) { return new Text( "RegisterPage" , style: TextStyle(color: Colors.white),); } } |
- Flutter 按钮总结
1 2 3 4 5 6 7 | · InkWell / / 纯文字按钮 · OutLineButton / / 边框按钮 · IconButton / / icon按钮 · |
- import 'package:flutter/services.dart';
1 2 3 4 5 6 7 | TextField inputFormatters: <TextInputFormatter> [ WhitelistingTextInputFormatter.digitsOnly, ], |
37 . 验证码按钮
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | new Positioned( child: new Container( width: 80.0 , height: 27.0 , alignment: Alignment.center, decoration: new BoxDecoration( border: new Border. all ( color: Colors.white, width: 1.0 , ), borderRadius: new BorderRadius.circular( 4.0 ), ), child: InkWell( child: _mText(_verifyStr, 12.0 ), onTap: () { }, ), ) ), |
- 倒计时方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | @override void dispose() { super .dispose(); _cancelTimer(); } _startTimer() { if (_verifyStr = = '重新发送' || _verifyStr = = '获取验证码' ) { _seconds = 5 ; _timer = new Timer.periodic(new Duration(seconds: 1 ), (timer) { if (_seconds = = 0 ) { _cancelTimer(); return ; } _seconds - - ; _verifyStr = '$_seconds(s)' ; setState(() {}); if (_seconds = = 0 ) { _verifyStr = '重新发送' ; } }); } } _cancelTimer() { _timer?.cancel(); } |
- 富文本拼接: 协议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | Widget _protocolWidget() { return new Container( child: new Row( children: <Widget>[ new GestureDetector( onTap: () { print ( "选择" ); }, child: Icon(Icons.add_alert, color: Colors.white), ), new Text.rich( new TextSpan( text: '我已阅读并同意' , style: new TextStyle( fontSize: 12.0 , color: Colors.grey[ 500 ], fontWeight: FontWeight.w400, ), children: [ new TextSpan( recognizer: new TapGestureRecognizer() ..onTap = () { print ( "《燎原用户服务协议》" ); }, text: "《燎原用户服务协议》" , style: new TextStyle( fontSize: 14.0 , color: Color( 0XFFB57A36 ), fontWeight: FontWeight.w400, ), ) ] ) ), ], ) ); } |
- 阴影、圆角
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | new Card( elevation: 4.0 , shape: new RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular( 16.0 ), topRight: Radius.circular( 16.0 ), bottomLeft: Radius.circular( 12.0 ), bottomRight: Radius.circular( 2.0 ), ) ), child: new IconButton(icon: Icon(Icons.add), onPressed: () { }), ) |
- YYTabbarWidget
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | import 'package:flutter/material.dart' ; / / with AutomaticKeepAliveClientMixin class YYTabbarWidget extends StatefulWidget { List <Widget> tabItems = []; Widget title; List <Widget> tabViews = []; PageController pageController; final ValueChanged< int > onPageChanged; final Widget drawer; YYTabbarWidget({Key key, this.drawer, this.tabItems, this.title, this.tabViews, this.pageController, this.onPageChanged, }) : super (key: key); _YYTabbarWidgetState createState() = > _YYTabbarWidgetState(drawer, title, tabItems, tabViews, pageController, onPageChanged); } class _YYTabbarWidgetState extends State<YYTabbarWidget> with SingleTickerProviderStateMixin { final Widget _title; final List <Widget> _tabViews; final List <Widget> _tabItems; final ValueChanged< int > _onPageChanged; final Widget _drawer; _YYTabbarWidgetState( this._drawer, this._title, this._tabItems, this._tabViews, this._pageController, this._onPageChanged, ) : super (); TabController _tabController; PageController _pageController; @override void initState() { super .initState(); _tabController = new TabController(length: _tabItems.length, vsync: this); } @override void dispose() { _tabController.dispose(); super .dispose(); } _renderTab() { print (_tabItems); List <Widget> list = new List (); for ( int i = 0 ; i < _tabItems.length; i + + ) { list .add(new FlatButton(onPressed: () { print (i); _pageController.jumpTo(MediaQuery .of(context) .size .width * i); }, child: _tabItems[I], ) ); } return list ; } @override Widget build(BuildContext context) { return new Scaffold( drawer: _drawer, appBar: new AppBar( title: _title, ), body: new PageView( controller: _pageController, children: _tabViews, onPageChanged: (index) { _tabController.animateTo(index); _onPageChanged?.call(index); }, ), bottomNavigationBar: new Material( color: Colors.white, child: new TabBar( indicatorPadding: new EdgeInsets.only(top: 0.0 ), controller: _tabController, tabs: _renderTab(), indicatorColor: Colors.red, ), ), ); } } |
- ListView 添加刷新,当数量少的时候不能滚动
1 | physics: new AlwaysScrollableScrollPhysics(), / / 让ListView一直可以滚动 |
- tabView切换 子界面都会调用initState
1 2 3 4 5 6 7 8 9 | 解决:AutomaticKeepAliveClientMixin class HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive = > true; } |
- 路有跳转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | / / / 不带参数的路由表跳转 Navigator.pushNamed(context,routeName); / / / 跳转新页面并且替换,比如登录页跳转主页 Navigator.pushReplacementNamed(context,routeName); / / / 跳转到新的路由,并且关闭给定路由的之前的所有页面 Navigator.pushNamedAndRemoveUntil(context, '/calendar' ,ModalRoute.withName( '/' )); / / / 带参数的路由跳转,并且监听返回 Navigator.push(context,newMaterialPageRoute(builder:(context) = >newNotifyPage())).then((res){ / / / 获取返回处理 }); |
- flutter lib
1 2 3 4 5 | cupertino_icons: ^ 0.1 . 2 #icon flutter_spinkit: "^2.1.0" # load more loading import 'package:flutter_spinkit/flutter_spinkit.dart'; dio: x.x.x #无网络请求 import 'package:dio/dio.dart'; |
- dio网络请求示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | _dioRequest() async { Dio dio = new Dio(); Response response; try { String url; var params; / / 请求参数 Options options; / / 配置:超时,请求头,请求类型等 response = await dio.request(url, data: params, options: options); } on DioError catch(e) { / / 请求出错时,返回一个DioError对象 } } |
- build_runner的使用
1 2 3 4 5 6 7 8 9 | 1 、在根目录运行 2 、一次性创建.g.dart文件 使用build 此时目录内不能有.g.dart文件 3 、watch是监听 有model类的文件创建 自动创建.g.dart文件 flutter packages pub run build_runner build flutter packages pub run build_runner watch |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话