Flutter基础笔记
目錄
- List里面常用的屬性和方法:
- Set
- Map
- forEach,map, where,any,every
- extends抽象類 和 implements
- Flutter環(huán)境搭建
- 入口文件、入口方法
- 第一個(gè) Demo Center 組件的 使用
- 把內(nèi)容單獨(dú)抽離成一個(gè)組件
- 給 Text 組件增加一些裝飾
- 用MaterialApp 和 Scaffold兩個(gè)組件裝飾 App
- Text 組件
- Container 組件
- 圖片組件
- 引入本地圖片
- 裁剪布局之 ClipRect、ClipRRect、ClipOval、ClipPath、CustomClipper
- 實(shí)現(xiàn)圓角以及實(shí)現(xiàn)圓形圖片
- 圓形頭像
- ClipOval
- ② CircleAvatar
- ③ BoxDecoration BoxShape.circle
- 圓角頭像
- ① ClipRRect
- ② BoxDecoration BoxShape.rectangle
- 列表組件概述
- 列表參數(shù)
- 基本列表
- 水平列表
- 動(dòng)態(tài)列表(動(dòng)態(tài)循環(huán)數(shù)據(jù))
- GridView 組件的常用參數(shù)
- GridView.count 實(shí)現(xiàn)網(wǎng)格布局
- GridView.builder 實(shí)現(xiàn)網(wǎng)格布局
- Paddiing 組件
- Row 水平布局組件
- Column 垂直布局組件
- Expanded 類似 Web 中的 Flex 布局
- Stack 組件
- Stack Align
- Stack Positioned
- AspectRatio 組件
- Card 組件
- Card 組件實(shí)現(xiàn)一個(gè)圖文列表布局
- RaisedButton 定義一個(gè)按鈕
- Wrap 組件
- 自定義有狀態(tài)組件
- BottomNavigationBar 組件
- 路由
- 基本路由使用
- 基本路由跳轉(zhuǎn)傳值
- 命名路由
- 命名路由跳轉(zhuǎn)傳值
- 命名路由單獨(dú)抽離到一個(gè)文件
- 返回到上一級頁面
- 替換路由
- 返回到根路由
- AppBar 自定義頂部按鈕圖標(biāo)、顏色
- AppBar 中自定義 TabBar 實(shí)現(xiàn)頂部 Tab 切換
- 把 TabBar 放在導(dǎo)航最頂部
- AppBar 中自定義 TabBar 實(shí)現(xiàn) Tabs 的另一種方法。
- Flutter Drawer 側(cè)邊欄
- DrawerHeader
- UserAccountsDrawerHeader
- 側(cè)邊欄路由跳轉(zhuǎn)
- 按鈕組件介紹
- 按鈕組件中的一些屬性
- FloatingActionButton 介紹
- 常用表單介紹
- TextField 文本框組件
- Checkbox、CheckboxListTile 多選框組件
- Radio、RadioListTile 單選按鈕組件
- 開關(guān) Switch
- 日期和時(shí)間戳
- 第三方庫 date_format 的使用
- 調(diào)用自帶日期組件和時(shí)間組件
- 調(diào)用自帶日期組件和時(shí)間組件改為中文
- 調(diào)用第三方時(shí)間組件
- 輪播圖組件
- 一、AlertDialog
- 二、SimpleDialog
- 三、showModalBottomSheet
- 四、showToast
- 自定義Dialog
- 定時(shí)器
- 定時(shí)器結(jié)合 Dialog
- JSON 字符串和 Map 類型的轉(zhuǎn)換(小項(xiàng)目)
- 使用 http 庫進(jìn)行網(wǎng)絡(luò)請求
- Dio 庫
- 下拉刷新和上拉分頁
- 下拉刷新
- 上拉分頁加載更多
- 核心代碼
- 完整代碼
- 解決請求重復(fù)問題
- 滾動(dòng)條回到頂部
- 參考代碼
- 實(shí)現(xiàn)一個(gè)簡單的新聞 APP
- 解析 html
- WebView 組件 inappbrowser的使用
- 獲取設(shè)備信息
- 使用高德定位準(zhǔn)備工作獲取 key
- 實(shí)現(xiàn)用高德定位
- image_picker 實(shí)現(xiàn)相機(jī)拍照和相冊選擇
- 上傳圖片到服務(wù)器
- 視頻播放
- chewie 視頻播放完整 demo
- 檢測網(wǎng)絡(luò)
- 檢測網(wǎng)絡(luò)完整 demo
- 本地存儲(chǔ)
- 本地存儲(chǔ)里面常用的一些方法
- 本地存儲(chǔ)完整 demo
- 掃描二維碼條形碼插件
- 檢測應(yīng)用版本號、服務(wù)器下載文件以及實(shí)現(xiàn) App 自動(dòng)升級、安裝
- 1、Android App 升級執(zhí)行流程
- 2、升級 app 之前的準(zhǔn)備工作配置權(quán)限
- 3、Android 升級 app 涉及的 API 庫
- 4、獲取版本信息
- 5、獲取文件存儲(chǔ)路徑
- 6、下載文件
- 7、打開文件
- 8、注意事項(xiàng)
- 完整代碼
- 調(diào)用 url_launcher 模塊打開外部瀏覽器 打開外部應(yīng)用 撥打電話 發(fā)送短信
- Android 修改應(yīng)用名稱、應(yīng)用圖標(biāo)、應(yīng)用啟動(dòng)畫面
- **1**、Android 修改應(yīng)用名稱
- **2**、Android 修改應(yīng)用圖標(biāo)
- **3**、Android 修改應(yīng)用啟動(dòng)畫面
- 豎向 ListView 嵌套橫向 ListView ,以及ListView 嵌套 GridView
- 不同終端屏幕適配問題
- JSON 序列化反序列化(模型類)
- JSON字符串和Map類型的轉(zhuǎn)換 dart:convert手動(dòng)序列化 JSON
- 在模型類中序列化 JSON
- json_to_dart 自動(dòng)生成模型類
- IndexedStack 保持頁面狀態(tài)
- AutomaticKeepAliveClientMixin 保持頁面狀態(tài)
- 通過事件打開側(cè)邊欄
- 修改主題樣式
- 下拉菜單 showMenu
- 彈出底部菜單
- StatefulBuilder更新Flutter showDialog 、showModalBottomSheet 中的狀態(tài)
- 狀態(tài)管理
- provider庫和flutter provide庫
- provider 的使用
- event_bus 事件廣播 事件監(jiān)聽
- MediaQuery.removePadding移除元素的pandding
- 瀑布流布局
- Sliver牛逼!!!
- 適配夜間模式
- 夜間模式跟隨系統(tǒng)
- 手動(dòng)開啟夜間模式
- 保存用戶配置
- 狀態(tài)管理
- 通用夜間模式Provider Model類
- MaterialApp修改
- 登錄注冊案例
- Flutter SliverAppBar 隱藏/顯示導(dǎo)航欄
- 骨架屏
- flutter 全屏背景圖(包括appbar和狀態(tài)欄)
- 極光推送:
- 指紋
List里面常用的屬性和方法:
/* List里面常用的屬性和方法:常用屬性:length 長度reversed 翻轉(zhuǎn)isEmpty 是否為空isNotEmpty 是否不為空常用方法: add 增加addAll 拼接數(shù)組indexOf 查找 傳入具體值remove 刪除 傳入具體值removeAt 刪除 傳入索引值fillRange 修改 insert(index,value); 指定位置插入 insertAll(index,list) 指定位置插入ListtoList() 其他類型轉(zhuǎn)換成List join() List轉(zhuǎn)換成字符串split() 字符串轉(zhuǎn)化成ListforEach mapwhereanyevery*/void main(){// List myList=['香蕉','蘋果','西瓜'];// print(myList[1]);// var list=new List();// list.add('111');// list.add('222');// print(list);//List里面的屬性:// List myList=['香蕉','蘋果','西瓜'];// print(myList.length);// print(myList.isEmpty);// print(myList.isNotEmpty);// print(myList.reversed); //對列表倒序排序// var newMyList=myList.reversed.toList();// print(newMyList);//List里面的方法:// List myList=['香蕉','蘋果','西瓜'];//myList.add('桃子'); //增加數(shù)據(jù) 增加一個(gè)// myList.addAll(['桃子','葡萄']); //拼接數(shù)組// print(myList);//print(myList.indexOf('蘋x果')); //indexOf查找數(shù)據(jù) 查找不到返回-1 查找到返回索引值// myList.remove('西瓜');// myList.removeAt(1);// print(myList);// List myList=['香蕉','蘋果','西瓜'];// myList.fillRange(1, 2,'aaa'); //修改// myList.fillRange(1, 3,'aaa'); // myList.insert(1,'aaa'); //插入 一個(gè)// myList.insertAll(1, ['aaa','bbb']); //插入 多個(gè)// print(myList);// List myList=['香蕉','蘋果','西瓜'];// var str=myList.join('-'); //list轉(zhuǎn)換成字符串// print(str);// print(str is String); //truevar str='香蕉-蘋果-西瓜';var list=str.split('-');print(list);print(list is List);}Set
//Set //用它最主要的功能就是去除數(shù)組重復(fù)內(nèi)容//Set是沒有順序且不能重復(fù)的集合,所以不能通過索引去獲取值void main(){// var s=new Set();// s.add('香蕉');// s.add('蘋果');// s.add('蘋果');// print(s); //{香蕉, 蘋果}// print(s.toList()); List myList=['香蕉','蘋果','西瓜','香蕉','蘋果','香蕉','蘋果'];var s=new Set();s.addAll(myList);print(s);print(s.toList());}Map
/*映射(Maps)是無序的鍵值對:常用屬性:keys 獲取所有的key值values 獲取所有的value值isEmpty 是否為空isNotEmpty 是否不為空常用方法:remove(key) 刪除指定key的數(shù)據(jù)addAll({...}) 合并映射 給映射內(nèi)增加屬性containsValue 查看映射內(nèi)的值 返回true/falseforEach mapwhereanyevery*/void main(){// Map person={// "name":"張三",// "age":20// };// var m=new Map();// m["name"]="李四";// print(person);// print(m);//常用屬性:// Map person={// "name":"張三",// "age":20,// "sex":"男"// };// print(person.keys.toList());// print(person.values.toList());// print(person.isEmpty);// print(person.isNotEmpty);//常用方法:Map person={"name":"張三","age":20,"sex":"男"};// person.addAll({// "work":['敲代碼','送外賣'],// "height":160// });// print(person);// person.remove("sex");// print(person);print(person.containsValue('張三')); }forEach,map, where,any,every
/*forEach map where anyevery */ void main(){// List myList=['香蕉','蘋果','西瓜'];// for(var i=0;i<myList.length;i++){// print(myList[i]);// }// for(var item in myList){// print(item);// }// myList.forEach((value){// print("$value");// });// List myList=[1,3,4];// List newList=new List();// for(var i=0;i<myList.length;i++){// newList.add(myList[i]*2);// }// print(newList);// List myList=[1,3,4]; // var newList=myList.map((value){// return value*2;// });// print(newList.toList());// List myList=[1,3,4,5,7,8,9];// var newList=myList.where((value){// return value>5;// });// print(newList.toList());// List myList=[1,3,4,5,7,8,9];// var f=myList.any((value){ //只要集合里面有滿足條件的就返回true// return value>5;// });// print(f);// List myList=[1,3,4,5,7,8,9];// var f=myList.every((value){ //每一個(gè)都滿足條件返回true 否則返回false// return value>5;// });// print(f);// set// var s=new Set();// s.addAll([1,222,333]);// s.forEach((value)=>print(value));//mapMap person={"name":"張三","age":20};person.forEach((key,value){ print("$key---$value");});}extends抽象類 和 implements
/* Dart中抽象類: Dart抽象類主要用于定義標(biāo)準(zhǔn),子類可以繼承抽象類,也可以實(shí)現(xiàn)抽象類接口。1、抽象類通過abstract 關(guān)鍵字來定義2、Dart中的抽象方法不能用abstract聲明,Dart中沒有方法體的方法我們稱為抽象方法。3、如果子類繼承抽象類必須得實(shí)現(xiàn)里面的抽象方法4、如果把抽象類當(dāng)做接口實(shí)現(xiàn)的話必須得實(shí)現(xiàn)抽象類里面定義的所有屬性和方法。5、抽象類不能被實(shí)例化,只有繼承它的子類可以extends抽象類 和 implements的區(qū)別:1、如果要復(fù)用抽象類里面的方法,并且要用抽象方法約束自類的話我們就用extends繼承抽象類2、如果只是把抽象類當(dāng)做標(biāo)準(zhǔn)的話我們就用implements實(shí)現(xiàn)抽象類*/Flutter環(huán)境搭建
安裝最新的 Xcode
下載androidstudio
https://developer.android.google.cn/studio
下載 Flutter SDK
https://flutter.dev/docs/development/tools/sdk/releases?tab=macos
把下載好的 Flutter SDK 隨便減壓到你想安裝 Sdk 的目錄如
/Users/cc/flutter把 Flutter 安裝目錄的 bin 目錄配置到環(huán)境變量,然后把 Flutter 國內(nèi)鏡像也配置到環(huán)境 變量里面
vim ~/.zshrc export PATH=/Users/cc/flutter/bin:$PATH export ANDROID_HOME="/Users/cc/Library/Android/sdk" export PATH=${PATH}:${ANDROID_HOME}/tools export PATH=${PATH}:${ANDROID_HOME}/platform-tools export PUB_HOSTED_URL=https://pub.flutter-io.cn export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn source ~/.zshrcflutter -h 如果能出來一些命令說明 flutter sdk 配置成功。
注意如果配置完成后輸入 flutter -h 告訴你 flutter 不是內(nèi)置命令之類的錯(cuò)誤的話,可能 sdk 沒有配置成功,也可能 sdk 下載的時(shí)候沒有下載全
運(yùn)行 flutter doctor 命令檢測環(huán)境
入口文件、入口方法
每一個(gè) flutter 項(xiàng)目的 lib 目錄里面都有一個(gè) main.dart 這個(gè)文件就是 flutter 的入口文件
main.dart 里面的
void main() {runApp(MyApp()); } //也可以簡寫 void main() => runApp(MyApp());其中的 main 方法是 dart 的入口方法。runApp 方法是 flutter 的入口方法。 MyApp 是自定義的一個(gè)組件
第一個(gè) Demo Center 組件的 使用
import 'package:flutter/material.dart';void main() {runApp(Center(child: Text("我是一個(gè)文本內(nèi)容",textDirection: TextDirection.ltr,),)); }把內(nèi)容單獨(dú)抽離成一個(gè)組件
在 Flutter 中自定義組件其實(shí)就是一個(gè)類,這個(gè)類需要繼承 StatelessWidget/StatefulWidget
前期我們都繼承 StatelessWidget。后期給大家講 StatefulWidget 的使用。
StatelessWidget 是無狀態(tài)組件,狀態(tài)不可變的 widget
StatefulWidget 是有狀態(tài)組件,持有的狀態(tài)可能在 widget 生命周期改變
給 Text 組件增加一些裝飾
import 'package:flutter/material.dart';void main() {runApp(MyApp()); }class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return Center(child: Text("我是一個(gè)文本內(nèi)容",textDirection: TextDirection.ltr,style: TextStyle(fontSize: 40.0,fontWeight: FontWeight.bold,// color: Colors.yellowcolor: Color.fromRGBO(255, 222, 222, 0.5)),),);} }用MaterialApp 和 Scaffold兩個(gè)組件裝飾 App
1、MaterialApp
MaterialApp 是一個(gè)方便的 Widget,它封裝了應(yīng)用程序?qū)崿F(xiàn) Material Design 所需要的 一些 Widget。一般作為頂層 widget 使用。
常用的屬性:
home(主頁)
title(標(biāo)題)
color(顏色)
theme(主題)
routes(路由)
…
2、Scaffold
Scaffold 是 Material Design 布局結(jié)構(gòu)的基本實(shí)現(xiàn)。此類提供了用于顯示 drawer、snackbar 和底部 sheet 的 API。
Scaffold 有下面幾個(gè)主要屬性:
appBar - 顯示在界面頂部的一個(gè) AppBar。
body - 當(dāng)前界面所顯示的主要內(nèi)容 Widget。
drawer - 抽屜菜單控件。
…
Text 組件
| textAlign | 文本對齊方式(center 居中,left 左 對齊,right 右對齊,justfy 兩端對齊) |
| textDirection | 文本方向(ltr 從左至右,rtl 從右至 左) |
| overflow | 文字超出屏幕之后的處理方式(clip 裁剪,fade 漸隱,ellipsis 省略號) |
| textScaleFactor | 字體顯示倍率 |
| maxLines | 文字顯示最大行數(shù) |
| style | 字體的樣式設(shè)置 |
下面是 TextStyle 的參數(shù) :
| decoration | 文字裝飾線(none 沒有線,lineThrough 刪 除線,overline 上劃線,underline 下劃線) |
| decorationColor | 文字裝飾線顏色 |
| decorationStyle | 文字裝飾線風(fēng)格([dashed,dotted]虛線, double 兩根線,solid 一根實(shí)線,wavy 波浪 線) |
| wordSpacing | 單詞間隙(如果是負(fù)值,會(huì)讓單詞變得更緊 湊 |
| letterSpacing | 字母間隙(如果是負(fù)值,會(huì)讓字母變得更緊 湊) |
| fontStyle | 文字樣式(italic 斜體,normal 正常體) |
| fontSize | 文字大小 |
| color | 文字顏色 |
| fontWeight | 字體粗細(xì)(bold 粗體,normal 正常體) |
更多參數(shù):https://docs.flutter.io/flutter/painting/TextStyle-class.html
Container 組件
| alignment | topCenter:頂部居中對齊 topLeft:頂部左對齊 topRight:頂部右對齊 center:水平垂直居中對齊 centerLeft:垂直居中水平居左對齊 centerRight:垂直居中水平居右對齊 bottomCenter 底部居中對齊 bottomLeft:底部居左對齊 bottomRight:底部居右對齊 |
| decoration | decoration: BoxDecoration( color: Colors.blue, border: Border.all( color: Colors.red, width: 2.0, ), borderRadius: BorderRadius.all( Radius.circular(8.0) ) ) |
| margin | margin 屬性是表示 Container 與外部其他 組件的距離。 EdgeInsets.all(20.0), |
| padding | padding 就是 Container 的內(nèi)邊距,指 Container 邊緣與 Child 之間的距離 padding: EdgeInsets.all(10.0) |
| transform | 讓 Container 容易進(jìn)行一些旋轉(zhuǎn)之類的 transform: Matrix4.rotationZ(0.2) |
| height | 容器高度 |
| width | 容器寬度 |
| child | 容器子元素 |
更多參數(shù):https://api.flutter.dev/flutter/widgets/Container-class.html
圖片組件
圖片組件是顯示圖像的組件,Image 組件有很多構(gòu)造函數(shù),這里我們只給大家講兩個(gè)
Image.asset 本地圖片
Image.network 遠(yuǎn)程圖片
Image 組件的常用屬性:
| alignment | alignment | 圖片的對齊方式 |
| color 和 colorBlendMode | 設(shè)置圖片的背景顏色,通常和 colorBlendMode 配合一起使用,這樣可以是圖片顏色和背景色混合。上面的圖片就是進(jìn)行了顏色的混合,綠色背景和圖片紅色的混合 | |
| fit | BoxFit | fit 屬性用來控制圖片的拉伸和擠壓,這都是根據(jù)父容器來的。 BoxFit.fill:全圖顯示,圖片會(huì)被拉伸,并充滿父容器。 BoxFit.contain:全圖顯示,顯示原比例,可能會(huì)有空隙。 BoxFit.cover:顯示可能拉伸,可能裁切,充滿(圖片要充滿整個(gè)容器,還不變形)。 BoxFit.fitWidth:寬度充滿(橫向充滿),顯示可能拉伸,可能裁切。 BoxFit.fitHeight :高度充滿(豎向充滿),顯示可能拉伸,可能裁切。 BoxFit.scaleDown:效果和 contain 差不多,但是此屬性不允許顯示超過源圖片大小,可小不可大。 |
| repeat | 平鋪 | ImageRepeat.repeat : 橫向和縱向都進(jìn)行重復(fù),直到鋪滿整個(gè)畫布。 ImageRepeat.repeatX: 橫向重復(fù),縱向不重復(fù)。 ImageRepeat.repeatY:縱向重復(fù),橫向不重復(fù)。 |
| width | 寬度 一般結(jié)合 ClipOval 才能看到效果 | |
| height | 高度 一般結(jié)合 ClipOval 才能看到效果 |
更多屬性參考:https://api.flutter.dev/flutter/widgets/Image-class.html
return Center(child: Container(child: Image.network("http://pic.baike.soso.com/p/20130828/20130828161137-1346445960.jpg",alignment: Alignment.topLeft,color: Colors.red,colorBlendMode: BlendMode.colorDodge,// repeat: ImageRepeat.repeatX,fit: BoxFit.cover,),width: 300.0,height: 400.0,decoration: BoxDecoration(color: Colors.yellow),),);引入本地圖片
emmm…不記了
裁剪布局之 ClipRect、ClipRRect、ClipOval、ClipPath、CustomClipper
widget 作用
ClipRect 將 child 剪裁為給定的矩形大小
ClipRRect 將 child 剪裁為圓角矩形
ClipOval 如果 child 為正方形時(shí)剪裁之后是圓形,如果 child 為矩形時(shí),剪裁之后為橢圓形
ClipPath 將 child 按照給定的路徑進(jìn)行裁剪
CustomClipper 并不是一個(gè)widget,但是使用CustomClipper可以繪制出任何我們想要的形狀
實(shí)現(xiàn)圓角以及實(shí)現(xiàn)圓形圖片
實(shí)現(xiàn)圓角圖片
return Center(child: Container(width: 300.0,height: 300.0,decoration: BoxDecoration(color: Colors.yellow,borderRadius: BorderRadius.circular(150),image: DecorationImage(image: NetworkImage("http://pic.baike.soso.com/p/20130828/20130828161137-1346445960.jpg",),fit: BoxFit.cover)),),);實(shí)現(xiàn)圓形圖片
return Center(child: Container(child: ClipOval(child: Image.network("https://www.itying.com/images/201905/thumb_img/1101_thumb_G_1557845381862.jpg",width: 150.0,height: 150.0,),),),);圓形頭像
ClipOval
new ClipOval(child: new Image.asset(Utils.getImgPath('ali_connors')),)② CircleAvatar
new CircleAvatar(radius: 36.0,backgroundImage: AssetImage(Utils.getImgPath('ali_connors'),),)③ BoxDecoration BoxShape.circle
new Container(width: 72.0,height: 72.0,decoration: BoxDecoration(shape: BoxShape.circle,image: DecorationImage(image: AssetImage(Utils.getImgPath('ali_connors'),),),),)圓角頭像
① ClipRRect
new ClipRRect(borderRadius: BorderRadius.circular(6.0),child: new Image.asset(Utils.getImgPath('ali_connors')),)② BoxDecoration BoxShape.rectangle
new Container(width: 88.0,height: 88.0,decoration: BoxDecoration(shape: BoxShape.rectangle,borderRadius: BorderRadius.circular(6.0),image: DecorationImage(image: AssetImage(Utils.getImgPath('ali_connors'),),),),列表組件概述
列表布局是我們項(xiàng)目開發(fā)中最常用的一種布局方式。Flutter 中我們可以通過 ListView 來定義 列表項(xiàng),支持垂直和水平方向展示。通過一個(gè)屬性就可以控制列表的顯示方向。列表有一下 分類:
**1、垂直列表(寬度自動(dòng)擴(kuò)展,設(shè)置寬度無效)**可以在外層包Container控制
2、垂直圖文列表
**3、水平列表(高度自動(dòng)擴(kuò)展,設(shè)置高度無效)**可以在外層包Container控制
4、動(dòng)態(tài)列表
5、矩陣式列表(網(wǎng)格布局)
列表參數(shù)
| scrollDirection | Axis | Axis.horizontal 水平列表 Axis.vertical 垂直列表 |
| padding | EdgeInsetsGeometry | 內(nèi)邊距 |
| resolve | bool | 組件反向排序 |
| children | List | 列表元素 |
基本列表
class HomeContent extends StatelessWidget {@overrideWidget build(BuildContext context) {return Center(child: ListView(children: <Widget>[ListTile(leading: Icon(Icons.phone),title: Text("this is list", style: TextStyle(fontSize: 28.0)),subtitle: Text('this is list this is list'),),ListTile(title: Text("this is list"),subtitle: Text('this is list this is list'),trailing: Icon(Icons.phone),),ListTile(title: Text("this is list"),subtitle: Text('this is list this is list'),),ListTile(title: Text("this is list"),subtitle: Text('this is list this is list'),),ListTile(title: Text("this is list"),subtitle: Text('this is list this is list'),)],),);} }水平列表
class HomeContent extends StatelessWidget {@overrideWidget build(BuildContext context) {return Container(height: 200.0,margin: EdgeInsets.all(5),child: ListView(scrollDirection: Axis.horizontal,children: <Widget>[Container(width: 180.0,color: Colors.lightBlue,),Container(width: 180,color: Colors.amber,child: ListView(children: <Widget>[Image.network("https://resources.ninghao.org/images/childhood-in-a-picture.jpg"),SizedBox(height: 16.0),Text("這是一個(gè)文本信息",textAlign: TextAlign.center,style: TextStyle(fontSize: 16.0)),],),)],),);} }動(dòng)態(tài)列表(動(dòng)態(tài)循環(huán)數(shù)據(jù))
import 'package:flutter/material.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(home: Scaffold(appBar: AppBar(title: Text("Hello Flutter")),body: HomeContent(),),);} }class HomeContent extends StatelessWidget {List list = List();HomeContent() {for (int i = 0; i < 20; i++) {list.add("這是第$i條數(shù)據(jù)");}print(list);}@overrideWidget build(BuildContext context) {return ListView.builder(itemCount: this.list.length,itemBuilder: (context, index) {// print(context);return ListTile(leading: Icon(Icons.phone),title: Text("${list[index]}"),);});} }GridView 組件的常用參數(shù)
當(dāng)數(shù)據(jù)量很大的時(shí)候用矩陣方式排列比較清晰。此時(shí)我們可以用網(wǎng)格列表組件 GridView 實(shí)現(xiàn)布局。
GridView 創(chuàng)建網(wǎng)格列表有多種方式,下面我們主要介紹兩種。
1、可以通過 GridView.count 實(shí)現(xiàn)網(wǎng)格布局
2、通過 GridView.builder 實(shí)現(xiàn)網(wǎng)格布局
常用屬性:
| scrollDirection | Axis | 滾動(dòng)方法 |
| padding | EdgeInsetsGeometry | 內(nèi)邊距 |
| resolve | bool | 組件反向排序 |
| crossAxisSpacing | double | 水平子 Widget 之間間距 |
| mainAxisSpacing | double | 垂直子 Widget 之間間距 |
| crossAxisCount | int | 一行的 Widget 數(shù)量 |
| childAspectRatio | double | 子 Widget 寬高比例 |
| children | [ ] | |
| gridDelegate | SliverGridDelegateWithFix edCrossAxisCount(常用) SliverGridDelegateWithMax CrossAxisExtent | 控制布局主要用在GridView.builder 里面 |
GridView.count 實(shí)現(xiàn)網(wǎng)格布局
import 'package:cc/res/listData.dart'; import 'package:flutter/material.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(home: Scaffold(appBar: AppBar(title: Text("Hello Flutter")),body: LayoutContent(),),);} }class LayoutContent extends StatelessWidget {List<Widget> _getListData() {var tempList = listData.map((value) {return Container(child: Column(children: <Widget>[Image.network(value["imageUrl"]),SizedBox(height: 12),Text(value["title"],textAlign: TextAlign.center, style: TextStyle(fontSize: 20))],),decoration: BoxDecoration(border: Border.all(color: Color.fromRGBO(230, 230, 230, 0.9), width: 1.0)),);});// ('124124','124214')return tempList.toList();}@overrideWidget build(BuildContext context) {return GridView.count(crossAxisCount: 2,crossAxisSpacing: 20,mainAxisSpacing: 20, // childAspectRatio:0.7,children: this._getListData(),);} }GridView.builder 實(shí)現(xiàn)網(wǎng)格布局
import 'package:cc/res/listData.dart'; import 'package:flutter/material.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(home: Scaffold(appBar: AppBar(title: Text("Hello Flutter")),body: LayoutContent(),),);} }class LayoutContent extends StatelessWidget {Widget _getListData(context, index) {return Container(child: Column(children: <Widget>[Image.network(listData[index]["imageUrl"]),SizedBox(height: 12),Text(listData[index]["title"],textAlign: TextAlign.center, style: TextStyle(fontSize: 20))],),decoration: BoxDecoration(border: Border.all(color: Color.fromRGBO(230, 230, 230, 0.9), width: 1.0)),);}@overrideWidget build(BuildContext context) {return GridView.builder(itemCount: listData.length,gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(//橫軸元素個(gè)數(shù)crossAxisCount: 2,//縱軸間距mainAxisSpacing: 20.0,//橫軸間距crossAxisSpacing: 10.0,//子組件寬高長度比例childAspectRatio: 1.0),itemBuilder: this._getListData,);} }Paddiing 組件
在 html 中常見的布局標(biāo)簽都有 padding 屬性,但是 Flutter 中很多 Widget 是沒有 padding 屬性。這個(gè)時(shí)候我們可以用 Padding 組件處理容器與子元素直接的間距。
| padding | padding 值, EdgeInsetss 設(shè)置填充的值 |
| child | 子組件 |
Row 水平布局組件
| mainAxisAlignment | 主軸的排序方式 |
| crossAxisAlignment | 次軸的排序方式 |
| children | 組件子元素 |
Column 垂直布局組件
| mainAxisAlignment | 主軸的排序方式 |
| crossAxisAlignment | 次軸的排序方式 |
| children | 組件子元素 |
Expanded 類似 Web 中的 Flex 布局
Expanded 可以用在 Row 和 Column 布局中
| flex | 元素站整個(gè)父 Row /Column 的比例 |
| child | 子元素 |
Stack 組件
Stack 表示堆的意思,我們可以用 Stack 或者 Stack 結(jié)合 Align 或者 Stack 結(jié)合 Positiond 來實(shí)現(xiàn)頁面的定位布局
| alignment | 配置所有子元素的顯示位置 |
| children | 子組件 |
Stack Align
Stack 組件中結(jié)合 Align 組件可以控制每個(gè)子元素的顯示位置
| alignment | 配置所有子元素的顯示位置 |
| child | 子組件 |
Stack Positioned
Stack 組件中結(jié)合 Positioned 組件也可以控制每個(gè)子元素的顯示位置
| top | 子元素距離頂部的距離 |
| bottom | 子元素距離底部的距離 |
| left | 子元素距離左側(cè)距離 |
| right | 子元素距離右側(cè)距離 |
| child | 子組件 |
AspectRatio 組件
AspectRatio 的作用是根據(jù)設(shè)置調(diào)整子元素 child 的寬高比。
AspectRatio 首先會(huì)在布局限制條件允許的范圍內(nèi)盡可能的擴(kuò)展,widget 的高度是由寬度和比率決定的,類似于 BoxFit 中的 contain,按照固定比率去盡量占滿區(qū)域。
如果在滿足所有限制條件過后無法找到一個(gè)可行的尺寸,AspectRatio 最終將會(huì)去優(yōu)先適應(yīng)布局限制條件,而忽略所設(shè)置的比率。
| aspectRatio | 寬高比,最終可能不會(huì)根據(jù)這個(gè)值去布局,具體則要看綜合因素,外層是否允許按照這種比率進(jìn)行布局,這只是一個(gè)參考值 |
| child | 子組件 |
Card 組件
Card 是卡片組件塊,內(nèi)容可以由大多數(shù)類型的 Widget 構(gòu)成,Card 具有圓角和陰影,這讓它看起來有立體感。
| margin | 外邊距 |
| child | 子組件 |
| Shape | Card 的陰影效果,默認(rèn)的陰影效果為圓角的長方形邊。 |
Card 組件實(shí)現(xiàn)一個(gè)圖文列表布局
class LayoutDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return ListView(children: listData.map((value) {return Card(margin: EdgeInsets.all(10),child: Column(children: <Widget>[AspectRatio(aspectRatio: 16 / 9,child: Image.network(value["imageUrl"], fit: BoxFit.cover),),ListTile(leading: CircleAvatar(backgroundImage: NetworkImage(value["imageUrl"]),),title: Text(value["description"]),subtitle: Text(value["description"],overflow: TextOverflow.ellipsis,),)],),);}).toList());} } class LayoutDemo extends StatelessWidget {@overrideWidget build(BuildContext context) {return ListView.builder(itemCount: listData.length,itemBuilder: (context,index){return Card(margin: EdgeInsets.all(10),child: Column(children: <Widget>[AspectRatio(aspectRatio: 16 / 9,child: Image.network(listData[index]["imageUrl"], fit: BoxFit.cover),),ListTile(leading: CircleAvatar(backgroundImage: NetworkImage(listData[index]["imageUrl"]),),title: Text(listData[index]["description"]),subtitle: Text(listData[index]["description"],overflow: TextOverflow.ellipsis,),)],),);},);} }RaisedButton 定義一個(gè)按鈕
Flutter 中通過 RaisedButton 定義一個(gè)按鈕。RaisedButton 里面有很多的參數(shù),這一講我們只是簡單的進(jìn)行使用。
return RaisedButton(child: Text("Flutter"),textColor: Theme.of(context).accentColor,onPressed: (){});Wrap 組件
Wrap 可以實(shí)現(xiàn)流布局,單行的 Wrap 跟 Row 表現(xiàn)幾乎一致,單列的 Wrap 則跟 Row 表 現(xiàn)幾乎一致。但 Row 與 Column 都是單行單列的,Wrap 則突破了這個(gè)限制,mainAxis 上空 間不足時(shí),則向 crossAxis 上去擴(kuò)展顯示。
| direction | 主軸的方向,默認(rèn)水平 |
| alignment | 主軸的對其方式 |
| spacing | 主軸方向上的間距 |
| textDirection | 文本方向 |
| verticalDirection | 定義了 children 擺放順序,默認(rèn)是 down,見Flex 相關(guān)屬性介紹。 |
| runAlignment | run 的對齊方式。run 可以理解為新的行或者列,如果是水平方向布局的話,run 可以理解為新的一行 |
| runSpacing | run 的間距 |
自定義有狀態(tài)組件
在 Flutter 中自定義組件其實(shí)就是一個(gè)類,這個(gè)類需要繼承 StatelessWidget/StatefulWidget。
StatelessWidget 是無狀態(tài)組件,狀態(tài)不可變的 widget
StatefulWidget 是有狀態(tài)組件,持有的狀態(tài)可能在 widget 生命周期改變。通俗的講:如果我們想改變頁面中的數(shù)據(jù)的話這個(gè)時(shí)候就需要用到 StatefulWidget
BottomNavigationBar 組件
BottomNavigationBar 是底部導(dǎo)航條,可以讓我們定義底部 Tab 切換,bottomNavigationBar是 Scaffold 組件的參數(shù)。
BottomNavigationBar 常見的屬性
| items | List 底 部 導(dǎo) 航條按鈕集合 |
| iconSize | icon |
| currentIndex | 默認(rèn)選中第幾個(gè) |
| fixedColor | 選中的顏色 |
| type | BottomNavigationBarType.fixed BottomNavigationBarType.shifting |
| (上面解決4個(gè)底部導(dǎo)航顯示出錯(cuò)) |
路由
Flutter 中的路由通俗的講就是頁面跳轉(zhuǎn)。在 Flutter 中通過 Navigator 組件管理路由導(dǎo)航。
并提供了管理堆棧的方法。如:Navigator.push 和 Navigator.pop
Flutter 中給我們提供了兩種配置路由跳轉(zhuǎn)的方式:1、基本路由 2、命名路由
基本路由使用
比如我們現(xiàn)在想從 HomePage 組件跳轉(zhuǎn)到 SearchPage 組件。
1、需要在 HomPage 中引入 SearchPage.dart
import '../SearchPage.dart';2、在 HomePage 中通過下面方法跳轉(zhuǎn)
RaisedButton(child: Text("跳轉(zhuǎn)到搜索頁面"),onPressed: () {Navigator.of(context).push(MaterialPageRoute(builder: (context) => SerachPage()));},color: Theme.of(context).accentColor,textTheme: ButtonTextTheme.primary,)基本路由跳轉(zhuǎn)傳值
比如我們現(xiàn)在想從 HomePage 組件跳轉(zhuǎn)到 SearchPage 組件傳值。
1、需要在 HomPage 中引入 SearchPage.dart
import '../SearchPage.dart';2、在 HomePage 中通過下面方法跳轉(zhuǎn)
RaisedButton(child: Text("跳轉(zhuǎn)到搜索頁面"),onPressed: () {Navigator.of(context).push(MaterialPageRoute(builder: (context) => SerachPage(title:"表單") //傳值 SerachPage加構(gòu)造函數(shù)并傳參數(shù)));},color: Theme.of(context).accentColor,textTheme: ButtonTextTheme.primary,)命名路由
1、配置路由
return MaterialApp(// home:Tabs(),initialRoute: '/', //初始化的時(shí)候加載的路由routes: {'/':(contxt)=>Tabs(),'/search':(contxt) =>SearchPage(),'/form': (context) => FormPage(),},);2、路由跳轉(zhuǎn)
RaisedButton(child: Text("跳轉(zhuǎn)到搜索頁面"),onPressed: () {Navigator.pushNamed(context, '/search');},color: Theme.of(context).accentColor,textTheme: ButtonTextTheme.primary)d命名路由跳轉(zhuǎn)傳值
花里胡哨
命名路由單獨(dú)抽離到一個(gè)文件
抽個(gè)雞兒
返回到上一級頁面
Navigator.of(context).pop();替換路由
比如我們從用戶中心頁面跳轉(zhuǎn)到了 registerFirst 頁面,然后從 registerFirst 頁面通過pushReplacementNamed 跳轉(zhuǎn)到了 registerSecond 頁面。這個(gè)時(shí)候當(dāng)我們點(diǎn)擊 registerSecond的返回按鈕的時(shí)候它會(huì)直接返回到用戶中心。
Navigator.of(context).pushReplacementNamed('/registerSecond'); //命名路由替換 // 普通路由替換 Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context)=>Second()));返回到根路由
比如我們從用戶中心跳轉(zhuǎn)到 registerFirst 頁面,然后從 registerFirst 頁面跳轉(zhuǎn)到 registerSecond頁面,然后從 registerSecond 跳轉(zhuǎn)到了 registerThird 頁面。這個(gè)時(shí)候我們想的是 registerThird注冊成功后返回到用戶中心。 這個(gè)時(shí)候就用到了返回到根路由的方法。
Navigator.of(context).pushAndRemoveUntil(new MaterialPageRoute(builder: (context) => new Tabs(index:1)),(route) => route == null );AppBar 自定義頂部按鈕圖標(biāo)、顏色
| leading | 在標(biāo)題前面顯示的一個(gè)控件,在首頁通常顯示應(yīng)用的 logo;在其他界面通常顯示為返回按鈕 |
| title | 標(biāo)題,通常顯示為當(dāng)前界面的標(biāo)題文字,可以放組件 |
| actions | 在標(biāo)題后面顯示的一個(gè)控件,通常使用 IconButton 來表示,可以放按鈕組 |
| bottom | 通常放 tabBar,標(biāo)題下面顯示一個(gè) Tab 導(dǎo)航欄 |
| backgroundColor | 導(dǎo)航背景顏色 |
| iconTheme | 圖標(biāo)樣式 |
| textTheme | 文字樣式 |
| centerTitle | 標(biāo)題是否居中顯示 |
AppBar 中自定義 TabBar 實(shí)現(xiàn)頂部 Tab 切換
TabBar 常見屬性:
| tabs | 顯示的標(biāo)簽內(nèi)容,一般使用 Tab 對象,也可以是其他的 Widget |
| controller | TabController 對象 |
| isScrollable | 是否可滾動(dòng)(是指有很多個(gè)appbar時(shí)滾動(dòng)appbar,左右滾動(dòng)appbar。不是滾動(dòng)內(nèi)容) |
| indicatorColor | 指示器顏色 |
| indicatorWeight | 指示器高度 |
| indicatorPadding | 底部指示器的 Padding |
| indicator | 指示器 decoration,例如邊框等 |
| indicatorSize | 指示器大小計(jì)算方式,TabBarIndicatorSize.label 跟文字等寬,TabBarIndicatorSize.tab 跟每個(gè) tab 等寬 |
| labelColor | 選中 label 顏色 |
| labelStyle | 選中 label 的 Style |
| labelPadding | 每個(gè) label 的 padding 值 |
| unselectedLabelColor | 未選中 label 顏色 |
| unselectedLabelStyle | 未選中 label 的 Style |
把 TabBar 放在導(dǎo)航最頂部
把TabBar放在titile里面
import 'package:flutter/material.dart';class AppBardemoPage extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(home: DefaultTabController(length: 2,child: Scaffold(appBar: AppBar(isScrollable: true, //如果多個(gè)按鈕的話可以滑動(dòng) // backgroundColor: Colors.red,leading: IconButton(icon: Icon(Icons.arrow_back),tooltip: "Search",onPressed: () {Navigator.of(context).pop();}),title: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: <Widget>[Expanded(flex: 1,child: TabBar(tabs: <Widget>[Tab(text: "熱門"), Tab(text: "推薦")],))],),),body: TabBarView(children: <Widget>[ListView(children: <Widget>[ListTile(title: Text("這是第一個(gè) tab")),ListTile(title: Text("這是第一個(gè) tab")),ListTile(title: Text("這是第一個(gè) tab"))],),ListView(children: <Widget>[ListTile(title: Text("這是第一個(gè) tab")),ListTile(title: Text("這是第一個(gè) tab")),ListTile(title: Text("這是第一個(gè) tab"))],),],),),),);} }AppBar 中自定義 TabBar 實(shí)現(xiàn) Tabs 的另一種方法。
TabController需要繼承有狀態(tài)組件
import 'package:flutter/material.dart';class AppBardemoPage extends StatefulWidget {@override_AppBardemoPageState createState() => _AppBardemoPageState(); }class _AppBardemoPageState extends State<AppBardemoPage>with SingleTickerProviderStateMixin {TabController _tabController;@overridevoid initState() {// TODO: implement initStatesuper.initState();_tabController = new TabController(vsync: this,length: 2);}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("AppBardemoPage"),bottom: TabBar(controller: this._tabController, //注意tabs: <Widget>[Tab(text: "熱銷"), Tab(text: "推薦"),],),),body: TabBarView(controller: this._tabController, //注意children: <Widget>[Center(child: Text("熱銷")),Center(child: Text("推薦"))],),);} }Flutter Drawer 側(cè)邊欄
在 Scaffold 組件里面?zhèn)魅?drawer 參數(shù)可以定義左側(cè)邊欄,傳入 endDrawer 可以定義右側(cè)邊欄。側(cè)邊欄默認(rèn)是隱藏的,我們可以通過手指滑動(dòng)顯示側(cè)邊欄,也可以通過點(diǎn)擊按鈕顯示側(cè)邊欄。
return Scaffold(appBar: AppBar(title: Text("Hello Flutter"),),drawer: Drawer(child: Text("左側(cè)邊欄"),),endDrawer: Drawer(child: Text("右側(cè)邊欄"),),);DrawerHeader
常見屬性:
| decoration | 設(shè)置頂部背景顏色 |
| child | 配置子元素 |
| padding | 內(nèi)邊距 |
| margin | 外邊距 |
UserAccountsDrawerHeader
| decoration | 設(shè)置頂部背景顏色 |
| accountName | 賬戶名稱 |
| accountEmail | 賬戶郵箱 |
| currentAccountPicture | 用戶頭像 |
| otherAccountsPictures | 用來設(shè)置當(dāng)前賬戶其他賬戶頭像 |
| margin |
側(cè)邊欄路由跳轉(zhuǎn)
onTap: () {Navigator.of(context).pop();Navigator.pushNamed(context, "/tabBarController");},按鈕組件介紹
Flutter 里有很多的 Button 組件很多,常見的按鈕組件有:RaisedButton、FlatButton、IconButton、OutlineButton、ButtonBar、FloatingActionButton 等。
RaisedButton :凸起的按鈕,其實(shí)就是 Material Design 風(fēng)格的 Button
FlatButton :扁平化的按鈕
OutlineButton:線框按鈕
IconButton :圖標(biāo)按鈕
ButtonBar:按鈕組
FloatingActionButton:浮動(dòng)按鈕
按鈕組件中的一些屬性
| onPressed | VoidCallback,一般接收一個(gè)方法 | 必填參數(shù),按下按鈕時(shí)觸發(fā)的回調(diào),接收一個(gè)方法,傳 null 表示按鈕禁用,會(huì)顯示禁用相關(guān)樣式 |
| child | Widget | 文本控件 |
| textColor | Color | 文本顏色 |
| color | Color | 按鈕的顏色 |
| disabledColor | Color | 按鈕禁用時(shí)的顏色 |
| disabledTextColor | Color | 按鈕禁用時(shí)的文本顏色 |
| splashColor | Color | 點(diǎn)擊按鈕時(shí)水波紋的顏色 |
| highlightColor | Color | 點(diǎn)擊(長按)按鈕后按鈕的顏色 |
| elevation | double | 陰影的范圍,值越大陰影范圍越大 |
| padding | 內(nèi)邊距 | |
| shape | 設(shè)置按鈕的形狀 shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ) //圓角按鈕 shape: CircleBorder( side: BorderSide( color: Colors.white, ) ) //圓形按鈕 |
FloatingActionButton 介紹
FloatingActionButton簡稱FAB ,可以實(shí)現(xiàn)浮動(dòng)按鈕,也可以實(shí)現(xiàn)類似閑魚app的地步凸起導(dǎo)航
| child | 子視圖,一般為 Icon,不推薦使用文字 |
| tooltip | FAB 被長按時(shí)顯示,也是無障礙功能 |
| backgroundColor | 背景顏色 |
| elevation | 未點(diǎn)擊的時(shí)候的陰影 |
| hignlightElevation | 點(diǎn)擊時(shí)陰影值,默認(rèn) 12.0 |
| onPressed | 點(diǎn)擊事件回調(diào) |
| shape | 可以定義 FAB 的形狀等 |
| mini | 是否是 mini 類型默認(rèn) false |
常用表單介紹
Flutter 中常見的表單有 TextField 單行文本框,TextField 多行文本框、CheckBox、Radio、SwitchCheckboxListTile、RadioListTile、SwitchListTile、Slide.
TextField 文本框組件
TextField 表單常見屬性:
| maxLines | 設(shè)置此參數(shù)可以把文本框改為多行文本框 |
| onChanged | 文本框改變的時(shí)候觸發(fā)的事件 |
| decoration | hintText 類似 html 中的 placeholder border 配置文本框邊框 OutlineInputBorder 配合使用 labelText lable 的名稱 labelStyle 配置 lable 的樣式 |
| obscureText | obscureText |
| controller | controller 結(jié)合 TextEditingController()可以配置表單默認(rèn)顯示的內(nèi)容 |
Checkbox、CheckboxListTile 多選框組件
Checkbox 常見屬性:
| value | true 或者 false |
| onChanged | 改變的時(shí)候觸發(fā)的事件 |
| activeColor | 選中的顏色、背景顏色 |
| checkColor | 選中的顏色、Checkbox 里面對號的顏色 |
CheckboxListTile 常見屬性:
| value | true 或者 false |
| onChanged | 改變的時(shí)候觸發(fā)的事件 |
| activeColor | 選中的顏色、背景顏色 |
| title | 標(biāo)題 |
| subtitle | 二級標(biāo)題 |
| secondary | 配置圖標(biāo)或者圖片 |
| selected | 選中的時(shí)候文字顏色是否跟著改變 |
Radio、RadioListTile 單選按鈕組件
Radio 常用屬性:
| value | 單選的值 |
| onChanged | 改變時(shí)觸發(fā) |
| activeColor | 選中的顏色、背景顏色 |
| groupValue | 選擇組的值 |
RadioListTile 常用屬性:
| value | true 或者 false |
| onChanged | 改變的時(shí)候觸發(fā)的事件 |
| activeColor | 選中的顏色、背景顏色 |
| title | 標(biāo)題 |
| subtitle | 二級標(biāo)題 |
| secondary | 配置圖標(biāo)或者圖片 |
| groupValue | 選擇組的值 |
開關(guān) Switch
| value | 單選的值 |
| onChanged | 改變時(shí)觸發(fā) |
| activeColor | 選中的顏色、背景顏色 |
日期和時(shí)間戳
日期轉(zhuǎn)化成時(shí)間戳:
var now = new DateTime.now();print(now.millisecondsSinceEpoch);//單位毫秒,13 位時(shí)間戳時(shí)間戳轉(zhuǎn)化成日期:
var now = new DateTime.now(); var a=now.millisecondsSinceEpoch; //時(shí)間戳print(DateTime.fromMillisecondsSinceEpoch(a));第三方庫 date_format 的使用
文檔:https://pub.dev/packages/date_format
調(diào)用自帶日期組件和時(shí)間組件
日期組件:
var _datetime = DateTime.now();_showDatePicker() async {var date = await showDatePicker(context: context,initialDate: _datetime,firstDate: DateTime(1900),lastDate: DateTime(2050));if (date == null) return;print(date);setState(() {_datetime = date;});}時(shí)間組件:
var _time = TimeOfDay(hour: 9, minute: 20);_showTimePicker() async {var time = await showTimePicker(context: context, initialTime: _time);if (time == null) return;print(time);setState(() {this._time = time;});} Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[Row(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[InkWell(child: Row(children: <Widget>[Text("${formatDate(_datetime, [yyyy, '-', mm, '-', dd])}"),Icon(Icons.arrow_drop_down)],),onTap: _showDatePicker,),InkWell(child: Row(children: <Widget>[Text("${this._time.format(context)}"),Icon(Icons.arrow_drop_down)],),onTap: _showTimePicker,)],)],)調(diào)用自帶日期組件和時(shí)間組件改為中文
http://bbs.itying.com/topic/5cfb2a12f322340b2c90e764
調(diào)用第三方時(shí)間組件
https://pub.dev/packages/flutter_cupertino_date_picker
懶得記了。。。
輪播圖組件
地址:https://pub.dev/packages/flutter_swiper
import 'package:flutter/material.dart'; import 'package:flutter_swiper/flutter_swiper.dart';class SwiperPage extends StatefulWidget {SwiperPage({Key key}) : super(key: key);_SwiperPageState createState() => _SwiperPageState(); }class _SwiperPageState extends State<SwiperPage> {List<Map> list = [{"url": "https://www.itying.com/images/flutter/1.png"},{"url": "https://www.itying.com/images/flutter/2.png"},{"url": "https://www.itying.com/images/flutter/3.png"}];@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('輪播圖組件演示'),),body: Column(children: <Widget>[Container(width: double.infinity,child: AspectRatio(aspectRatio: 16 / 9,child: new Swiper(itemBuilder: (BuildContext context, int index) {return new Image.network(this.list[index]["url"],fit: BoxFit.fill,);},itemCount: list.length,pagination: new SwiperPagination(),autoplay: true,// control: new SwiperControl(),),),)],),);} }一、AlertDialog
var alertRel = await showDialog(context: context,builder: (context) {return AlertDialog(title: Text("提示!"),content: Text("確定要?jiǎng)h除嗎"),actions: <Widget>[FlatButton(child: Text("取消"),onPressed: () {Navigator.pop(context, 'Cancle');},),FlatButton(child: Text("確定"),onPressed: () {Navigator.pop(context, 'Ok');},)],);});二、SimpleDialog
var simpleRel = await showDialog(context: context,builder: (BuildContext context) {return SimpleDialog(title: Text("select 單選按鈕框"),children: <Widget>[SimpleDialogOption(child: Text("Option A"),onPressed: () {Navigator.pop(context, 'Option A');},),Divider(),SimpleDialogOption(child: Text("Option B"),onPressed: () {Navigator.pop(context, 'Option B');},),Divider(),SimpleDialogOption(child: Text("Option C"),onPressed: () {Navigator.pop(context, 'Option C');},)],);});三、showModalBottomSheet
var actionSheet = await showModalBottomSheet(context: context,builder: (builder) {return Container(height: 200, //高度不設(shè)置顯示一半child: Column(children: <Widget>[ListTile(title: Text("分享 A"),onTap: () {Navigator.pop(context, 'A');},),ListTile(title: Text("分享 B"),onTap: () {Navigator.pop(context, 'B');},),ListTile(title: Text("分享 C"),onTap: () {Navigator.pop(context, 'C');},)],),);});四、showToast
https://pub.dev/packages/fluttertoast
Fluttertoast.showToast(msg: "This is Short Toast",toastLength: Toast.LENGTH_SHORT,timeInSecForIos: 1);自定義Dialog
自定義 Dialog 對象,需要繼承 Dialog 類,盡管 Dialog 提供了 child 參數(shù)可以用來寫視圖界面,但是往往會(huì)達(dá)不到我們想要的效果,因?yàn)槟J(rèn)的 Dialog 背景框是滿屏的。如果我們想完全定義界面,就需要重寫 build 函數(shù)。
import 'package:flutter/material.dart';class LoadingDialog extends Dialog {final String text;LoadingDialog(this.text);@overrideWidget build(BuildContext context) {// TODO: implement buildreturn new Material( //創(chuàng)建透明層type: MaterialType.transparency, //透明類型 child: new Center(child: Container(width: 300,height: 200,color: Colors.white,child: Column(children: <Widget>[Padding(padding: EdgeInsets.all(10),child: Stack(children: <Widget>[Align(alignment: Alignment.center,child: Text("關(guān)于我們"),),Align(alignment: Alignment.centerRight,child: InkWell(child: Icon(Icons.close),onTap: () {Navigator.pop(context);},),)],),),Divider(),Column(children: <Widget>[Container(height: 40,child: Text(this.text),)],)],),),);} }定時(shí)器
import 'dart:async';_showTimer(context) {var timer;timer = Timer.periodic(Duration(milliseconds: 1500), (t) {print('執(zhí)行');Navigator.pop(context);t.cancel();});}定時(shí)器結(jié)合 Dialog
import 'dart:async';import 'package:flutter/material.dart';class LoadingDialog extends Dialog {final String text;LoadingDialog(this.text);_showTimer(context) {var timer;timer = Timer.periodic(Duration(milliseconds: 1500), (t) {print('執(zhí)行');Navigator.pop(context);t.cancel();});}@overrideWidget build(BuildContext context) {_showTimer(context);return Material(//創(chuàng)建透明層type: MaterialType.transparency, //透明類型child: Center(child: Container(width: 300,height: 200,color: Colors.white,child: Column(children: <Widget>[Padding(padding: EdgeInsets.all(10),child: Stack(children: <Widget>[Align(alignment: Alignment.center,child: Text("關(guān)于我們"),),Align(alignment: Alignment.centerRight,child: InkWell(child: Icon(Icons.close),onTap: () {Navigator.pop(context);},),)],),),Divider(),Column(children: <Widget>[Container(height: 40,child: Text(this.text),)],)],),),),);} }JSON 字符串和 Map 類型的轉(zhuǎn)換(小項(xiàng)目)
(小項(xiàng)目直接轉(zhuǎn),大項(xiàng)目用模型類,筆記在下面)
import 'dart:convert';var mapData = {"name": "張三", "age": "20"}; var strData = '{"name":"張三","age":"20"}'; print(json.encode(mapData)); //Map轉(zhuǎn)換成Json字符串 print(json.decode(strData)); //Json 字符串轉(zhuǎn)化成 Map 類型使用 http 庫進(jìn)行網(wǎng)絡(luò)請求
請參考官方文檔:https://pub.dev/packages/http
import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert';class HomePage extends StatefulWidget {HomePage({Key key}) : super(key: key);_HomePageState createState() => _HomePageState(); }class _HomePageState extends State<HomePage> {String _news = '';//請求數(shù)據(jù)_getData() async {var apiUrl = "http://127.0.0.1:8080/";var result = await http.get(apiUrl);if (result.statusCode == 200) {// print(json.decode(result.body));setState(() {this._news = json.decode(result.body)["msg"];});} else {print(result.statusCode);}}//提交數(shù)據(jù)_postData() async {var apiUrl = "http://127.0.0.1:8080/x";var result = await http.post(apiUrl, body: {'username': '張三', 'age': '20'});if (result.statusCode == 200) {print(json.decode(result.body));} else {print(result.statusCode);}}@overrideWidget build(BuildContext context) {return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Text(this._news),RaisedButton(child: Text('Get請求數(shù)據(jù)'),onPressed: _getData,),SizedBox(height: 20),RaisedButton(child: Text('Post提交數(shù)據(jù)'),onPressed: _postData,),SizedBox(height: 20),RaisedButton(child: Text('Get請求數(shù)據(jù)、渲染數(shù)據(jù)演示demo'),onPressed: () {Navigator.pushNamed(context, '/http');},),SizedBox(height: 20),],),);} }map遍歷,外層套ListView組件
import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert';class HttpDemo extends StatefulWidget {HttpDemo({Key key}) : super(key: key);_HttpDemoState createState() => _HttpDemoState(); }class _HttpDemoState extends State<HttpDemo> {List _list = [];@overridevoid initState() {// TODO: implement initStatesuper.initState();this._getData();}_getData() async {var apiUrl = "http://a.itying.com/api/productlist";var result = await http.get(apiUrl);if (result.statusCode == 200) {print(result.body);setState(() {this._list = json.decode(result.body)["result"];/*{"result": [{"_id": "5ac0896ca880f20358495508","title": "精選熱菜","pid": "0", }, {"_id": "5ac089e4a880f20358495509","title": "特色菜","pid": "0",}]}*/});} else {print("失敗${result.statusCode}");}}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("請求數(shù)據(jù)Demo"),),body: this._list.length > 0? ListView(children: this._list.map((value) { //map遍歷,外層套ListView組件return ListTile(title: Text(value["title"]),);}).toList(),): Text("加載中..."));} }ListView.builder
import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert';class HttpDemo extends StatefulWidget {HttpDemo({Key key}) : super(key: key);_HttpDemoState createState() => _HttpDemoState(); }class _HttpDemoState extends State<HttpDemo> {List _list = [];@overridevoid initState() {// TODO: implement initStatesuper.initState();this._getData();}_getData() async {var apiUrl = "http://a.itying.com/api/productlist";var result = await http.get(apiUrl);if (result.statusCode == 200) {print(result.body);setState(() {this._list = json.decode(result.body)["result"];/*{"result": [{"_id": "5ac0896ca880f20358495508","title": "精選熱菜","pid": "0", }, {"_id": "5ac089e4a880f20358495509","title": "特色菜","pid": "0",}]}*/});} else {print("失敗${result.statusCode}");}}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("請求數(shù)據(jù)Demo"),),body: this._list.length > 0? ListView.builder(itemCount: this._lidst.length,itemBuilder: (context, index) {return ListTile(title: Text("${this._list[index]["title"]}"),);},): Text("加載中..."));} }Dio 庫
dio 是一個(gè)強(qiáng)大的 Dart Http 請求庫,支持 Restful API、FormData、攔截器、請求取消、Cookie 管理、文件上傳/下載、超時(shí)、自定義適配器等…
https://pub.dev/packages/dio
https://github.com/flutterchina/dio/blob/master/README-ZH.md
不做栗子,依官方最新文檔栗子為準(zhǔn)
下拉刷新和上拉分頁
在 Flutter 官方 sdk 中給我們提供了下拉刷新的組件 RefreshIndicator。但是沒有提供上拉分頁加載更多的組件。但是在 Flutter ListView 中有一個(gè)ScrollController 屬性,它就是專門來控制 ListView 滑動(dòng)事件,在這里我們可以根據(jù) ListView 的位置來判斷是否滑動(dòng)到了底部來做加載更多的處理。
Api 接口:http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1
下拉刷新
@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("請求數(shù)據(jù) Dio Demo"),),body: this._list.length > 0? RefreshIndicator(onRefresh: _onRefresh,child: ListView.builder(itemCount: this._list.length,itemBuilder: (context, index) {return ListTile(title: Text(this._list[index]["title"]));})): Text("加載中..."));}Future<void> _onRefresh() async {print('執(zhí)行刷新');}上拉分頁加載更多
_scrollController.position.pixels 滾動(dòng)的距離
_scrollController.position.maxScrollExtent 總距離
核心代碼
ScrollController _scrollController = ScrollController(); //listview 的控制器@overridevoid initState() {// TODO: implement initStatesuper.initState();this._getData();//監(jiān)聽滾動(dòng)條事件_scrollController.addListener(() {if (_scrollController.position.pixels >_scrollController.position.maxScrollExtent - 20) {print("滾動(dòng)到了最底部");_getData();}});}@overridevoid dispose() {// TODO: implement disposesuper.dispose();_scrollController.dispose(); //不用了砍掉,提高性能}ListView.builder(itemCount: this._list.length,controller: _scrollController, //注意itemBuilder: (context, index) {})完整代碼
import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'dart:convert';class NewsPage extends StatefulWidget {@override_NewsPageState createState() => _NewsPageState(); }class _NewsPageState extends State<NewsPage> {ScrollController _scrollController = ScrollController(); //listview 的控制器List _list = [];int _page = 1;bool isLoading = true; //是否正在加載數(shù)據(jù)@overridevoid initState() {// TODO: implement initStatesuper.initState();this._getData();//監(jiān)聽滾動(dòng)條事件_scrollController.addListener(() {if (_scrollController.position.pixels >_scrollController.position.maxScrollExtent - 20) {print("滑動(dòng)到了最底部");_getData();}});}@overridevoid dispose() {// TODO: implement disposesuper.dispose();_scrollController.dispose(); //不用了砍掉,提高性能}_getData() async {String apiUrl ="http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=${this._page}";Response result = await Dio().get(apiUrl);var res = json.decode(result.data)["result"];// print(json.decode(result.data)["result"]);setState(() {this._list.addAll(res);this._page++;});//判斷是否是最后一頁if (res.length < 20) {setState(() {this.isLoading = false;});}}Widget _getMoreWidget() {if (isLoading) {return Center(child: Padding(padding: EdgeInsets.all(10.0),child: Row(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: <Widget>[Text('加載中...',style: TextStyle(fontSize: 16.0),),CircularProgressIndicator(strokeWidth: 1.0,)],),),);} else {return Center(child: Text("--我是有底線的--"),);}}//下拉刷新Future<void> _onRefresh() async {print("執(zhí)行刷新");this._getData();await Future.delayed(Duration(seconds: 3), () {print("refresh");});}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("請求數(shù)據(jù) Dio Demo"),),body: this._list.length > 0? RefreshIndicator(onRefresh: _onRefresh,child: ListView.builder(controller: _scrollController,itemCount: this._list.length,itemBuilder: (context, index) {if (index == this._list.length - 1) {return Column(children: <Widget>[ListTile(title: Text(this._list[index]["title"], maxLines: 1),onTap: () {Navigator.pushNamed(context, '/newsContent');},),Divider(),_getMoreWidget()],);} else {return Column(children: <Widget>[ListTile(title: Text(this._list[index]["title"], maxLines: 1),onTap: () {Navigator.pushNamed(context, '/newsContent');},),Divider()],);}},),): _getMoreWidget(),);} }解決請求重復(fù)問題
//解決重復(fù)請求的問題bool flag=true;@overridevoid initState() {super.initState();_getData();//監(jiān)聽滾動(dòng)條滾動(dòng)事件_scrollController.addListener((){//_scrollController.position.pixels //獲取滾動(dòng)條滾動(dòng)的高度//_scrollController.position.maxScrollExtent //獲取頁面高度 if(_scrollController.position.pixels>_scrollController.position.maxScrollExtent-20){if(this.flag && this._hasMore){ //如果已經(jīng)請求就不再獲取數(shù)據(jù)_getData();}}});}@overridevoid dispose() {// TODO: implement disposesuper.dispose();_scrollController.dispose(); //不用了砍掉,提高性能}//獲取數(shù)據(jù)_getData() async {// 請求數(shù)據(jù)之前置為falsesetState(() {this.flag=false; });var api =‘url‘;var result = await Dio().get(api);。。。// 數(shù)據(jù)請求完成之后置為truesetState(() {。。。this.flag=true; }); }滾動(dòng)條回到頂部
//回到頂部 _scrollController.jumpTo(0);參考代碼
//導(dǎo)航改變的時(shí)候觸發(fā)_subHeaderChange(id) {if (id == 4) {_scaffoldKey.currentState.openEndDrawer();setState(() {this._selectHeaderId = id;});} else {setState(() {this._selectHeaderId = id;this._sort ="${this._subHeaderList[id - 1]["fileds"]}_${this._subHeaderList[id - 1]["sort"]}";//重置分頁this._page = 1;//重置數(shù)據(jù)this._productList = [];//改變sort排序this._subHeaderList[id - 1]['sort'] =this._subHeaderList[id - 1]['sort'] * -1;//回到頂部_scrollController.jumpTo(0);//重置_hasMorethis._hasMore = true;//重新請求this._getProductListData();});}}實(shí)現(xiàn)一個(gè)簡單的新聞 APP
涉及的 api 接口:
新聞列表: http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1
新聞詳情:http://www.phonegap100.com/appapi.php?a=getPortalArticle&aid=20
列表頁
import 'package:cc/pages/NewsContent.dart'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'dart:convert';class NewsPage extends StatefulWidget {@override_NewsPageState createState() => _NewsPageState(); }class _NewsPageState extends State<NewsPage> {ScrollController _scrollController = ScrollController(); //listview 的控制器List _list = [];int _page = 1;bool isLoading = true; //是否正在加載數(shù)據(jù)@overridevoid initState() {// TODO: implement initStatesuper.initState();this._getData();//監(jiān)聽滾動(dòng)條事件_scrollController.addListener(() {if (_scrollController.position.pixels >_scrollController.position.maxScrollExtent - 20) {print("滑動(dòng)到了最底部");_getData();}});}_getData() async {String apiUrl ="http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=${this._page}";Response result = await Dio().get(apiUrl);var res = json.decode(result.data)["result"];// print(json.decode(result.data)["result"]);setState(() {this._list.addAll(res);this._page++;});//判斷是否是最后一頁if (res.length < 20) {setState(() {this.isLoading = false;});}}Widget _getMoreWidget() {if (isLoading) {return Center(child: Padding(padding: EdgeInsets.all(10.0),child: Row(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: <Widget>[Text('加載中...',style: TextStyle(fontSize: 16.0),),CircularProgressIndicator(strokeWidth: 1.0,)],),),);} else {return Center(child: Text("--我是有底線的--"),);}}//下拉刷新Future<void> _onRefresh() async {print("執(zhí)行刷新");this._getData();await Future.delayed(Duration(seconds: 3), () {print("refresh");});}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("請求數(shù)據(jù) Dio Demo"),),body: this._list.length > 0? RefreshIndicator(onRefresh: _onRefresh,child: ListView.builder(controller: _scrollController,itemCount: this._list.length,itemBuilder: (context, index) {if (index == this._list.length - 1) {return Column(children: <Widget>[ListTile(title: Text(this._list[index]["title"], maxLines: 1),onTap: () {Navigator.pushNamed(context, '/newsContent');},),Divider(),_getMoreWidget()],);} else {return Column(children: <Widget>[ListTile(title: Text(this._list[index]["title"], maxLines: 1),onTap: () {Navigator.of(context).push(MaterialPageRoute(builder: (context) =>NewsContent(this._list[index]["aid"])));},),Divider()],);}},),): _getMoreWidget(),);} }詳情頁
import 'package:flutter/material.dart'; import 'dart:convert'; import 'package:dio/dio.dart';class NewsContent extends StatefulWidget {var aid;NewsContent(this.aid);createState() => _NewsContentState(); }class _NewsContentState extends State<NewsContent> {List list = [];_getData() async {String apiUrl ="http://www.phonegap100.com/appapi.php?a=getPortalArticle&aid=${this.widget.aid}";Response response = await Dio().get(apiUrl);setState(() {this.list = json.decode(response.data)["result"];});}@overridevoid initState() {// TODO: implement initStatesuper.initState();this._getData();}@overrideWidget build(BuildContext context) {return Container(child: Scaffold(appBar: AppBar(title: Text(this.list.length > 0 ? this.list[0]["title"] : ""),),body: ListView(children: <Widget>[Text(this.list.length > 0 ? this.list[0]["content"] : "")],),),);} }解析 html
https://pub.dev/packages/flutter_html
雞肋。。。只能解析部分HTML標(biāo)簽
WebView 組件 inappbrowser的使用
建議使用WebView_flutter官方庫,inappbrowser翹辮子了
https://pub.dev/packages/flutter_inappbrowser
ios模擬器測試失敗。。。
獲取設(shè)備信息
https://pub.dev/packages/device_info
使用高德定位準(zhǔn)備工作獲取 key
1、申請成為開發(fā)者
2、創(chuàng)建應(yīng)用配置獲取 Key (參考教程演示)
https://lbs.amap.com/api/android-sdk/guide/create-project/get-key
實(shí)現(xiàn)用高德定位
https://pub.dev/packages/amap_location
image_picker 實(shí)現(xiàn)相機(jī)拍照和相冊選擇
https://pub.dev/packages/image_picker
/*拍照*/_takePhoto() async {var image = await ImagePicker.pickImage(source: ImageSource.camera);setState(() {_imgPath = image;});}/*相冊*/_openGallery() async {var image = await ImagePicker.pickImage(source: ImageSource.gallery);setState(() {_imgPath = image;});}上傳圖片到服務(wù)器
https://pub.dev/packages/dio
Dio2.x和3.x代碼不同,依Dio官方文檔為準(zhǔn)
//上傳圖片_uploadImage(File _imageDir) async {//注意:dio3.x版本為了兼容web做了一些修改,上傳圖片的時(shí)候需要把File類型轉(zhuǎn)換成String類型,具體代碼如下var fileDir = _imageDir.path;FormData formData = FormData.fromMap({"name": "zhangsna 6666666666","age": 20,"sex": "男","file": await MultipartFile.fromFile(fileDir, filename: "xxx.jpg")});var response =await Dio().post("http://jd.itying.com/imgupload", data: formData);print(response);}視頻播放
video_player 官方庫,支持flutter web
在 Flutter 里官方提供了一個(gè) video_player 插件可以播放視頻。但是 video_player 有一些局限性。沒法控制底部播放進(jìn)度等。 所以我們主要給大家講解一個(gè)第三方的視頻播放庫chewie。chewie 是一個(gè)非官方的第三方視頻播放組件,看起來好像是基于 HTML5 播放的組件。chewie 相對 video_player 來說,有控制欄和全屏的功能。Chewie 使用 video_player 引擎并將其包裹在友好的 Material 或 Cupertino UI 中!
https://pub.dev/packages/video_player
https://pub.dev/packages/chewie
iOS警告
chewie使用的視頻播放器插件在iOS模擬器上不起作用。開發(fā)/測試期間必須使用iOS設(shè)備。
coffee 21:44:59
安利一下 flutter_ijkplayer 視頻播放器,這兩天試了很多個(gè),還是這個(gè)解決了目前的問題
coffee 22:31:46
flutter_tencentplayer 騰訊云的ios無法播放rtmp協(xié)議的直播流
coffee 22:33:13
官方提供的video_player 沒有ui,需要自己實(shí)現(xiàn)
coffee 22:34:47
chewie 包裝了一層video_player提供了ui,我視頻回放用的這個(gè),費(fèi)了好大力氣才搞定使用原視頻尺寸播放,今兒發(fā)現(xiàn) flutter_ijkplayer 很好用,不過回放功能暫時(shí)不打算改了,好不容易調(diào)完
chewie 視頻播放完整 demo
import 'package:flutter/material.dart'; import 'package:chewie/chewie.dart'; import 'package:video_player/video_player.dart';class ChewieVideoDemo extends StatefulWidget {ChewieVideoDemo({Key key}) : super(key: key);_ChewieVideoDemoState createState() => _ChewieVideoDemoState(); }class _ChewieVideoDemoState extends State<ChewieVideoDemo> {VideoPlayerController videoPlayerController;ChewieController chewieController;@overridevoid 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,);}@overridevoid dispose() { // TODO: implement dispose super.dispose();videoPlayerController.dispose();chewieController.dispose();super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('標(biāo)題'),),body: Center(child: Chewie(controller: chewieController,),),);} }檢測網(wǎng)絡(luò)
https://pub.dev/packages/connectivity
檢測網(wǎng)絡(luò)完整 demo
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> {String _state;var _subscription;@overrideinitState() {super.initState();_subscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {// Got a new connectivity status!if (result == ConnectivityResult.mobile) {setState(() {_state = "手機(jī)網(wǎng)絡(luò)";}); // I am connected to a mobile network. } else if (result == ConnectivityResult.wifi) {setState(() {_state = "Wifi 網(wǎng)絡(luò)";}); // I am connected to a wifi network. }else{setState(() {_state = "沒有網(wǎng)絡(luò)";});}});}@overridedispose() {super.dispose();_subscription.cancel();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("檢測網(wǎng)絡(luò)變化"),),body: Text("${_state}"),);} }本地存儲(chǔ)
https://pub.dev/packages/shared_preferences
注意:
如果SharedPreferences prefs = await SharedPreferences.getInstance();寫在runapp()的外層,
要加上
本地存儲(chǔ)里面常用的一些方法
1、設(shè)置值
SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.setString(key, value); prefs.setBool(key, value) prefs.setDouble(key, value) prefs.setInt(key, value) prefs.setStringList(key, value)2、獲取值
SharedPreferences prefs = await SharedPreferences.getInstance(); var data=prefs.getString("name");3、刪除值
SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.remove(key); //刪除指定鍵 prefs.clear();//清空鍵值對本地存儲(chǔ)完整 demo
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 prefs = await SharedPreferences.getInstance();prefs.setString("name", "張三"); // prefs.setBool(key, value) // prefs.setDouble(key, value)// prefs.setInt(key, value) // prefs.setStringList(key, value)}_getData() async {SharedPreferences prefs = await SharedPreferences.getInstance();var data = prefs.getString("name");print(data);}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("本地存儲(chǔ)"),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [RaisedButton(child: Text('保存數(shù)據(jù)'),onPressed: _saveData,),SizedBox(height: 10),RaisedButton(child: Text('獲取數(shù)據(jù)'),onPressed: _getData,)]),),);} }掃描二維碼條形碼插件
https://pub.dev/packages/barcode_scan
錯(cuò)誤多多,修改大大,不做記錄。。。用到再說
檢測應(yīng)用版本號、服務(wù)器下載文件以及實(shí)現(xiàn) App 自動(dòng)升級、安裝
1、Android App 升級執(zhí)行流程
1、獲取本地版本號
2、請求服務(wù)器獲取服務(wù)器版本號
3、本地版本和服務(wù)器版本不一致提示升級,彈窗提示用戶是否更新
4、用戶確定升級,調(diào)用文件傳輸方法下載 apk 文件
5、監(jiān)聽下載進(jìn)度
6、下載完成打開 Apk 進(jìn)行安裝
注意:在 Ios 中沒法直接下載安裝,如果版本不一致直接跳轉(zhuǎn)到 Ios 應(yīng)用對應(yīng)的應(yīng)用市場就可以了。
配置版本號: (Flutter應(yīng)用獲取的不是這里的版本號,在pubspec.yaml文件)
<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">2、升級 app 之前的準(zhǔn)備工作配置權(quán)限
配置 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 | 檢測版本號 | https://pub.dev/packages/package_info |
| path_provider | 獲取文件存儲(chǔ)路徑 | https://pub.dev/packages/path_provider |
| flutter_downloader | flutter_downloader | https://pub.dev/packages/flutter_downloader |
| open_file | 打開文件插件 | https://pub.dev/packages/open_file |
4、獲取版本信息
https://pub.dev/packages/package_info
PackageInfo packageInfo = await PackageInfo.fromPlatform(); String appName = packageInfo.appName; String packageName = packageInfo.packageName; String version = packageInfo.version; String buildNumber = packageInfo.buildNumber; print("appName:${appName}"); print("packageName:${packageName}"); print("version:${version}"); print("buildNumber:${buildNumber}");5、獲取文件存儲(chǔ)路徑
https://pub.dev/packages/path_provider
Directory tempDir = await getTemporaryDirectory(); String tempPath = tempDir.path;Directory appDocDir = await getApplicationDocumentsDirectory(); String appDocPath = appDocDir.path;var directory = await getExternalStorageDirectory();String storageDirectory=directory.path;print("tempPath:${tempPath}");print("appDocDir:${appDocPath}");print("StorageDirectory:${storageDirectory}");6、下載文件
https://pub.dev/packages/flutter_downloader
final directory = await getExternalStorageDirectory();String _localPath = directory.path;final taskId = await FlutterDownloader.enqueue(url: "http://www.ionic.wang/jdshop.apk",savedDir: _localPath,showNotification:true, // show download progress in status bar (for Android)openFileFromNotification:true, // click on notification to open downloaded file (for Android)7、打開文件
https://pub.dev/packages/open_file
OpenFile.open("${_localPath}/jdshop.apk");8、注意事項(xiàng)
1、服務(wù)器的 App 版本必須大于本地 App 版本
2、本地 App 和服務(wù)器 App 的包名稱 簽名必須一致,這樣的話服務(wù)器的包才可以替換本地的包。
完整代碼
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> {@overridevoid initState() {// TODO: implement initStatesuper.initState();this._getPackageInfo();this._getAppPath();}//彈出Dialog_showDialog() async {var alertRel = await showDialog(context: context,builder: (context) {return AlertDialog(title: Text("更新APP提示!"),content: Text("發(fā)現(xiàn)新的版本,新版本修復(fù)了如下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 UIprint('1111111');print(progress);});//打開文件OpenFile.open("${_localPath}/jdshop.apk");}@overrideWidget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(child: Icon(Icons.arrow_downward),onPressed: _downLoad,),appBar: AppBar(title: Text("app升級演示"),),body: Text("app升級演示"),);} }調(diào)用 url_launcher 模塊打開外部瀏覽器 打開外部應(yīng)用 撥打電話 發(fā)送短信
1、Flutter url_launcher 模塊
Flutter url_launcher 模塊可以讓我們實(shí)現(xiàn)打開外部瀏覽器、打開外部應(yīng)用、發(fā)送短信、撥打電話等功能。
https://pub.dev/packages/url_launcher
2、Flutter url_launcher 模塊的使用
import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart';class _UrlLauncherState extends State<UrlLauncher> {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('UrlLauncher'),),body: Center(child: Padding(padding: EdgeInsets.all(20),child: ListView(children: <Widget>[RaisedButton(child: Text('打開外部瀏覽器'),onPressed: () async {const url = 'https://cflutter.com';if (await canLaunch(url)) {await launch(url);} else {throw 'Could not launch $url';}},),SizedBox(height: 10,),RaisedButton(child: Text('撥打電話'),onPressed: () async {const tel = 'tel:10086';if (await canLaunch(tel)) {await launch(tel);} else {throw 'Could not launch $tel';}},),SizedBox(height: 10),RaisedButton(child: Text('發(fā)送短信'),onPressed: () async {const tel = 'sms:10086';if (await canLaunch(tel)) {await launch(tel);} else {throw 'Could not launch $tel';}},),SizedBox(height: 10),RaisedButton(child: Text('打開外部應(yīng)用'),onPressed: () async {// weixin://const app = 'alipays://';if (await canLaunch(app)) {await launch(app);} else {throw 'Could not launch $app';}},)],),),),);} }3、關(guān)于打開其他 app 請參考這個(gè)帖子
https://www.cflutter.com/topic/5d0853733b57e317a4d0af01
Android 修改應(yīng)用名稱、應(yīng)用圖標(biāo)、應(yīng)用啟動(dòng)畫面
1、Android 修改應(yīng)用名稱
Android 是在 android ? app ? src ? main? AndroidManifest.xml 中修改 android:label=“XXX”;
2、Android 修改應(yīng)用圖標(biāo)
Android 在 android ? app ? src ? res ? mipmap 下面對應(yīng)的文件夾中替換相應(yīng)圖片
3、Android 修改應(yīng)用啟動(dòng)畫面
Android 添加啟動(dòng)界面
打開文件 android/app/src/main/res/drawable/launch_background.xml
修改內(nèi)容,打開注釋了的代碼 launch_image 那段。
<!-- <item><bitmapandroid:gravity="center"android:src="@mipmap/launch_image" /></item> -->里面的ic_launch.png是圖標(biāo),啟動(dòng)畫面添加launch_image.png,格式要求png
注意@mipmap/launch_image 就是你要設(shè)置的啟動(dòng)界面的圖片資源名字,你要放置到對應(yīng)的文件夾里面
| ldpi | 240 x 320 |
| mdpi | 320 x 480 |
| hdpi | 480 x 800 |
| xhdpi | 720 x 1280 |
| xxhdpi | 1080 x 1920 |
| xxxhdpi | 3840×2160 |
豎向 ListView 嵌套橫向 ListView ,以及ListView 嵌套 GridView
1、豎向 ListView 嵌套橫向 ListView 注意事項(xiàng):
在豎向 ListView 中嵌套橫向 ListView 的時(shí)候要注意給橫向 ListView 外層加一個(gè)容器,然后外層這個(gè)容器要設(shè)置高度,外層這個(gè)容器可以是 SizedBox ,也可以是 Container。
2、ListView 嵌套 GridView 注意事項(xiàng):
由于 GridView 和 ListView 都是可以滾動(dòng)的組件,所以嵌套的時(shí)候要注意把里面的組件改為不可滾動(dòng)組件。
重要屬性:
shrinkWrap: true, //解決無限高度問題
physics:NeverScrollableScrollPhysics(), //禁用滑動(dòng)事件
不同終端屏幕適配問題
我寫的代碼還用適配???
JSON 序列化反序列化(模型類)
1、使用 dart:convert 手動(dòng)序列化 JSON
2、模型類中序列化 JSON
小項(xiàng)目中使用 dart:convert 手動(dòng)序列化 JSON 非常好,也非常快速。但是隨著項(xiàng)目的增大,dart:convert 手動(dòng)序列化 JSON 的話失去了大部分靜態(tài)類型語言特性:類型安全、自動(dòng)補(bǔ)全和最重要的編譯時(shí)異常。這樣一來,我們的代碼可能會(huì)變得非常容易出錯(cuò)。
當(dāng)我們訪問 name 或 email 字段時(shí),我們輸入的很快,導(dǎo)致字段名打錯(cuò)了。但由于這個(gè) JSON 在 map 結(jié)構(gòu)中,所以編譯器不知道這個(gè)錯(cuò)誤的字段名。
為了解決上面的問題在大型項(xiàng)目中使用的更多的是在模型類中序列化 JSON。
JSON字符串和Map類型的轉(zhuǎn)換 dart:convert手動(dòng)序列化 JSON
import 'dart:convert';var mapData = {"name": "張三", "age": "20"};var strData = '{"name":"張三","age":"20"}';print(json.encode(mapData)); //Map轉(zhuǎn)換成Json字符串print(json.decode(strData)); //Json 字符串轉(zhuǎn)化成 Map 類型在模型類中序列化 JSON
class FocusModel {String sId;String title;String status;String pic;String url;FocusModel({this.sId, this.title, this.status, this.pic, this.url});FocusModel.fromJson(Map<String, dynamic> json) {sId = json['_id'];title = json['title'];status = json['status'];pic = json['pic'];url = json['url'];}Map<String, dynamic> toJson() {final Map<String, dynamic> data = new Map<String, dynamic>();data['_id'] = this.sId;data['title'] = this.title;data['status'] = this.status;data['pic'] = this.pic;data['url'] = this.url;return data;} } var strData='{"_id":"59f6ef443ce1fb0fb02c7a43","title":"筆記本電腦","status":"1","pic":"public\\upload\\UObZahqPYzFvx_C9CQjU8KiX.png","url":"12"}';var data=FocusModel.fromJson(strData);可參考:https://flutterchina.club/json/
json_to_dart 自動(dòng)生成模型類
https://javiercbk.github.io/json_to_dart/
IndexedStack 保持頁面狀態(tài)
IndexedStack 和 Stack 一樣,都是層布局控件, 可以在一個(gè)控件上面放置另一個(gè)控件,但唯一不同的是 IndexedStack 在同一時(shí)刻只能顯示子控件中的一個(gè)控件,通過 Index 屬性來設(shè)置顯示的控件。
IndexedStack 來保持頁面狀態(tài)的優(yōu)點(diǎn)就是配置簡單。IndexedStack 保持頁面狀態(tài)的缺點(diǎn)就是不方便單獨(dú)控制每個(gè)頁面的狀態(tài)。
body: IndexedStack(index: this._currentIndex,children: <Widget>[],),AutomaticKeepAliveClientMixin 保持頁面狀態(tài)
花里胡哨。。。
通過事件打開側(cè)邊欄
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); return Scaffold( key:_scaffoldKey, appBar: AppBar( title: Text("商品列表"), ) ) Expanded( flex: 1, child: InkWell( onTap: () { _scaffoldKey.currentState.openEndDrawer(); }, child: Text("篩選", textAlign: TextAlign.center), ), )修改主題樣式
return MaterialApp( debugShowCheckedModeBanner: false, // home: Tabs(), initialRoute: '/', onGenerateRoute:onGenerateRoute, theme: ThemeData( primaryColor: Colors.white ), );下拉菜單 showMenu
IconButton(icon: Icon(Icons.more_horiz),onPressed: () {showMenu(context: context,position: RelativeRect.fromLTRB(500, 76, 10, 0),items: [PopupMenuItem(child: Row(children: <Widget>[Icon(Icons.home),Container(padding: EdgeInsets.fromLTRB(20, 0, 20, 0),child: Text("首頁"),)],),),PopupMenuItem(child: Row(children: <Widget>[Icon(Icons.search),Container(padding: EdgeInsets.fromLTRB(20, 0, 20, 0),child: Text("搜索"),)],),)]);})彈出底部菜單
實(shí)際就是點(diǎn)擊事件后,彈出showModalBottomSheet,參考diolog筆記
StatefulBuilder更新Flutter showDialog 、showModalBottomSheet 中的狀態(tài)
參考:https://www.cflutter.com/topic/5d202202403aa10564178c65
狀態(tài)管理
通俗的講:當(dāng)我們想在多個(gè)頁面(組件/Widget)之間共享狀態(tài)(數(shù)據(jù)),或者一個(gè)頁面(組件/Widget)中的多個(gè)子組件之間共享狀態(tài)(數(shù)據(jù)),這個(gè)時(shí)候我們就可以用 Flutter 中的狀態(tài)管理來管理統(tǒng)一的狀態(tài)(數(shù)據(jù)),實(shí)現(xiàn)不同組件直接的傳值和數(shù)據(jù)共享。
現(xiàn)在 Flutter 的狀態(tài)管理方案很多,redux、bloc、state、provide、provider。
目前我們推薦使用 provider,這個(gè)是官方提供的狀態(tài)管理解決方案。相比其他狀態(tài)管理庫使用起來比較方便。
provider庫和flutter provide庫
provider 是 Flutter 團(tuán)隊(duì)推出的狀態(tài)管理模式。
官方地址為:https://pub.dev/packages/provider
注意:provider 和 provide 是兩個(gè)庫哦。Flutter 官方推薦使用的是 provider 哦,provider 是flutter 官方出的。provide 不是 Flutter 官方寫的哦。
provider 的使用
(官方文檔為準(zhǔn),builder關(guān)鍵字變creat)
1、配置依賴 provider: ^4.3.3
2、新建一個(gè)文件夾叫 provider,在 provider 文件夾里面放我們對于的狀態(tài)管理類
3、在 provider 里面新建 Counter.dart
4、Counter.dart 里面新建一個(gè)類繼承 minxins 的 ChangeNotifier 代碼如下
5、找到 main.dart 修改代碼如下
import 'package:flutter/material.dart'; import 'routers/router.dart'; import 'package:provider/provider.dart'; import 'provider/Counter.dart';void main() => runApp(MyApp());// void main() => runApp(MyApp()); class MyApp extends StatefulWidget {MyApp({Key key}) : super(key: key);_MyAppState createState() => _MyAppState(); }class _MyAppState extends State<MyApp> {@overrideWidget build(BuildContext context) {return MultiProvider(providers: [// Provider<Counter>.value(value: foo),ChangeNotifierProvider(create: (_) => Counter()), // provider4.x的寫法全局監(jiān)聽//ChangeNotifierProvider(builder: (_) => Counter()),],child: MaterialApp(// home: Tabs(),debugShowCheckedModeBanner: false,initialRoute: '/productContent',onGenerateRoute: onGenerateRoute,theme: ThemeData(// primaryColor: Colors.yellowprimaryColor: Colors.white),));} }6、獲取值、以及設(shè)置值
import 'package:provider/provider.dart'; import '../../provider/Counter.dart';Widget build(BuildContext context) {final counter = Provider.of<Counter>(context); // counter.init();//在build里面return Scaffold(floatingActionButton: FloatingActionButton(child: Icon(Icons.add),onPressed: () {counter.increment();},),body: Text("counter 的值:${counter.count}")); }擴(kuò)展栗子
import 'package:flutter/material.dart';class Cart with ChangeNotifier {List _cartList = []; //狀態(tài)// int _cartNum=0; //數(shù)量直接從數(shù)組中獲取,不用在定義一個(gè)獲取數(shù)量的方法int get cartNum => this._cartList.length; //數(shù)量直接從數(shù)組中獲取,不用在定義一個(gè)獲取數(shù)量的方法List get cartList => this._cartList;addData(value) {this._cartList.add(value);notifyListeners();}deleteData(value) {this._cartList.remove(value);notifyListeners();} }event_bus 事件廣播 事件監(jiān)聽
花里胡哨。。。
MediaQuery.removePadding移除元素的pandding
通過MediaQuery.removePadding可以移除元素的pandding,需要注意要指定移除哪個(gè)方向的padding,例如移除上面的padding
MediaQuery.removePadding(removeTop: true,context: context,child: , )瀑布流布局
https://pub.dev/packages/flutter_staggered_grid_view
new StaggeredGridView.countBuilder(crossAxisCount: 4,itemCount: 8,itemBuilder: (BuildContext context, int index) => new Container(color: Colors.green,child: new Center(child: new CircleAvatar(backgroundColor: Colors.white,child: new Text('$index'),),)),staggeredTileBuilder: (int index) =>new StaggeredTile.count(2, index.isEven ? 2 : 1), //固定個(gè)數(shù)修改count()為fit(2)mainAxisSpacing: 4.0,crossAxisSpacing: 4.0, )Sliver牛逼!!!
return Scaffold(body: CustomScrollView(slivers: <Widget>[SliverAppBar( // title: Text("SliverAppBar"), // pinned: true,floating: true,expandedHeight: 200,flexibleSpace: FlexibleSpaceBar(title: Text("Hello Flutter".toUpperCase()),background: Image.network("https://images.unsplash.com/photo-1579964190836-13f5022f5c40?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60",fit: BoxFit.cover,),),),SliverGrid(delegate: SliverChildBuilderDelegate((context, index) {return Container(child: Center(child: Text("$index"),),);}, childCount: 1000),gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2,crossAxisSpacing: 8.0,mainAxisSpacing: 8.0))],),);適配夜間模式
簡單做法:
直接設(shè)置模式
theme: ThemeData.dark(),自動(dòng)切換模式
darkTheme: ThemeData.dark()正片:
夜間模式(Dark Mode),也被稱為暗黑模式或深色模式,是一種高對比度,或者反色模式的顯示模式,開啟之后在夜間可以緩解疲勞,更易于閱讀,同時(shí)也能在一定程度上達(dá)到省電的效果。
夜間模式跟隨系統(tǒng)
使用MaterialApp的darkTheme選項(xiàng),可以很方便地適配跟隨系統(tǒng)的DarkMode:
MaterialApp( theme: ThemeData( brightness: Brightness.light, primaryColor: Colors.blue, ), darkTheme: ThemeData( brightness: Brightness.dark, ), );也可直接寫做
darkTheme: ThemeData.dark()- 這種方式是自動(dòng)跟隨iOS/Android的系統(tǒng)設(shè)置來切換的,無需用戶再單獨(dú)設(shè)置
手動(dòng)開啟夜間模式
上述的跟隨系統(tǒng)自動(dòng)切換暗黑模式的體驗(yàn)可能并不是很好,比如用戶不喜歡夜間模式或者App的夜間模式配色適配并不是很好,這就會(huì)導(dǎo)致用戶無法手動(dòng)控制app的夜間模式或者只能關(guān)閉系統(tǒng)的設(shè)置。因此我們可以增加手動(dòng)控制以及跟隨系統(tǒng)的選項(xiàng),讓用戶選擇是否開啟以及開啟的方式。
保存用戶配置
在flutter中可以使用shared_preferences來保存用戶的配置數(shù)據(jù),具體使用方法詳見:shared_preferences使用
狀態(tài)管理
主題的手動(dòng)切換是影響全局的,如果通過常規(guī)的數(shù)據(jù)流向很難做到。常見的幾種狀態(tài)管理:
- InheritedWidget
- Scoped model
- BLoC
- Redux
- Provider
Provider是Google I/O 2019大會(huì)宣布的現(xiàn)在官方推薦的狀態(tài)管理方式,我們需要在設(shè)置頁里面通過用戶設(shè)置,把變更狀態(tài)共享給其他Widget,這里采用Provider這種方式來實(shí)現(xiàn)狀態(tài)共享。
通用夜間模式Provider Model類
import 'package:flutter/foundation.dart'; import 'package:shared_preferences/shared_preferences.dart';class DarkModeModel with ChangeNotifier {/// 夜間模式 0: 關(guān)閉 1: 開啟 2: 隨系統(tǒng)int _darkMode;static const Map<int, String> darkModeMap = {0: "關(guān)閉",1: "開啟",2: "跟隨系統(tǒng)"};static const String STORE_KEY = 'darkMode';SharedPreferences _prefs;int get darkMode => _darkMode;DarkModeModel() {_init();}void _init() async {this._prefs = await SharedPreferences.getInstance();int localMode = this._prefs.getInt(STORE_KEY);changeMode(localMode ?? 0);}void changeMode(int darkMode) async {_darkMode = darkMode;notifyListeners();SharedPreferences prefs = this._prefs ?? SharedPreferences.getInstance();await prefs.setInt(STORE_KEY, darkMode);} }MaterialApp修改
如果手動(dòng)控制是否開啟夜間模式,可以設(shè)置MaterialApp的theme選項(xiàng)為ThemeData.dark()
theme: ThemeData.dark()因?yàn)樾枰瑫r(shí)保留隨系統(tǒng)自動(dòng)切換與手動(dòng)切換,而darkTheme選項(xiàng)和theme又有沖突,所以這里需要根據(jù)darkModeModel.darkMode的取值來渲染不同的MaterialApp,如果是手動(dòng)模式再根據(jù)darkModeModel.darkMode的取值來渲染不同的theme。
MultiProvider(providers: [ChangeNotifierProvider(builder: (_) => DarkModeModel())],child: Consumer<DarkModeModel>(builder: (context, darkModeModel, _) {return darkModeModel.darkMode == 2 ? MaterialApp(title: '特效君',theme: ThemeData(primarySwatch: Colors.blue,),darkTheme: ThemeData.dark(),home: MainPage(title: '特效君'),) : MaterialApp(title: '特效君',theme: darkModeModel.darkMode == 1 ? ThemeData.dark(): ThemeData(primarySwatch: Colors.blue,),home: MainPage(title: '特效君'),);},), )這樣我們就可以給用戶提供自動(dòng)跟隨系統(tǒng)切換以及手動(dòng)控制的選項(xiàng)了
登錄注冊案例
login.dart
class Login extends StatefulWidget {@override_LoginState createState() => _LoginState(); }class _LoginState extends State<Login> {String _nickname = "";String _password = "";final _formKey = GlobalKey<FormState>();bool _autoValidate = false;_toLogin() async {Response response = await Dio().post("http://127.0.0.1:8080/auth",data: {"nickname": _nickname, "password": _password});LoginModel signUp = LoginModel.fromJson(response.data);if (signUp.code == 2000) {Scaffold.of(context).showSnackBar(SnackBar(content: Text(signUp.msg)));} else {Scaffold.of(context).showSnackBar(SnackBar(content: Text(signUp.msg)));}}void _submitForm() {if (_formKey.currentState.validate()) {_formKey.currentState.save();_toLogin();} else {setState(() {_autoValidate = true;});}}String _validateNickname(String value) {if (value.isEmpty) {return "<昵稱>不能為空!";} else if (value.length > 20) {return "<昵稱>不能大于20個(gè)字符!";}return null;}String _validatePassword(String value) {if (value.isEmpty) {return "<密碼>不能為空!";} else if (value.length < 6) {return "<密碼>不能小于6位!";}return null;}@overrideWidget build(BuildContext context) {return Scaffold(body: GestureDetector(behavior: HitTestBehavior.translucent,onTap: () {FocusScope.of(context).requestFocus(FocusNode());},// 外層包一個(gè)Container方便設(shè)置內(nèi)外邊距,背景圖片等child: Container(padding: EdgeInsets.all(40.0),child: ListView(children: <Widget>[// 登錄LOGOContainer(height: 100,decoration: BoxDecoration(image: DecorationImage(image: NetworkImage("https://c-ssl.duitang.com/uploads/item/201607/23/20160723143004_vyjTa.thumb.1000_0.jpeg",),),),),// 表單Form(key: _formKey,child: Column(children: <Widget>[// 昵稱TextFormField(decoration: InputDecoration(labelText: "昵稱",hintText: "請輸入登錄昵稱",helperText: '',),onSaved: (String value) {_nickname = value;},autovalidate: _autoValidate,validator: _validateNickname,),// 密碼TextFormField(decoration: InputDecoration(labelText: "密碼",hintText: "請輸入登錄密碼",helperText: '',),obscureText: true,onSaved: (String value) {_password = value;},autovalidate: _autoValidate,validator: _validatePassword,),// 登錄按鈕Container(child: RaisedButton(child: Text("登錄"),onPressed: _submitForm,color: Theme.of(context).accentColor,elevation: 0.0,),),],),),// 注冊新賬號Container(child: FlatButton(onPressed: () {Navigator.of(context).push(MaterialPageRoute(builder: (context) => SignUp()));},child: Text("注冊新賬號"),),),],),),),);} }Flutter SliverAppBar 隱藏/顯示導(dǎo)航欄
class MyWidget extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(body: NestedScrollView(headerSliverBuilder: _sliverBuilder,body: Center(child: Text('hahaha'),)),);} }List<Widget> _sliverBuilder(BuildContext context, bool innerBoxIsScrolled) {return <Widget>[SliverAppBar(centerTitle: true, //標(biāo)題居中expandedHeight: 200.0, //展開高度200backgroundColor: Colors.tealAccent,floating: false, //不隨著滑動(dòng)隱藏標(biāo)題pinned: false, //不固定在頂部flexibleSpace: FlexibleSpaceBar(centerTitle: true,background: Image.asset("assets/pic.jpg", fit: BoxFit.cover,),),)]; }骨架屏
https://pub.dev/packages/pk_skeleton
flutter 全屏背景圖(包括appbar和狀態(tài)欄)
class _HomeState extends State<Home> {@overrideWidget build(BuildContext context) {SelfAdapt _adapt = SelfAdapt.init(context);return Container(width: _adapt.width,height: _adapt.height,decoration: BoxDecoration(image: DecorationImage(image: NetworkImage('https://img.zcool.cn/community/0372d195ac1cd55a8012062e3b16810.jpg'),fit: BoxFit.cover,),),child: Scaffold(backgroundColor: Colors.transparent,appBar: AppBar(elevation: 0,backgroundColor: Colors.transparent,// title: Text('首頁'),),drawer: MyDrawer(),body: Container(width: _adapt.width,padding: _adapt.setfromLTRB(100, 0, 100, 0),child: Text('hello'),),),);} }極光推送:
注冊賬戶-----創(chuàng)建應(yīng)用-----appkey—應(yīng)用報(bào)名要一致
集成
1.下載依賴
Android: 在 /android/app/build.gradle 中添加下列代碼:android: {....defaultConfig {applicationId "替換成自己應(yīng)用 ID"...ndk {//選擇要添加的對應(yīng) cpu 類型的 .so 庫。abiFilters 'armeabi', 'armeabi-v7a', 'x86', 'x86_64', 'mips', 'mips64', 'arm64-v8a', }manifestPlaceholders = [JPUSH_PKGNAME : applicationId,JPUSH_APPKEY : "appkey", // NOTE: JPush 上注冊的包名對應(yīng)的 Appkey.JPUSH_CHANNEL : "developer-default", //暫時(shí)填寫默認(rèn)值即可.]} } void initState() {super.initState();this.initJpush();// ///創(chuàng)建 JPush// JPush jpush = new JPush();// ///配置應(yīng)用 Key// jpush.setup(// appKey: "3cc9670e26b5b3e83cabe979",// channel: "theChannel",// production: false,// /// 設(shè)置是否打印 debug 日志// debug: true,// );}或者init:
void initState() {// TODO: implement initStatesuper.initState();this.initJpush();}//監(jiān)聽極光推送 (自定義的方法)//https://github.com/jpush/jpush-flutter-plugin/blob/master/documents/APIs.mdinitJpush() async {JPush jpush = new JPush();//獲取注冊的idjpush.getRegistrationID().then((rid) {print("獲取注冊的id:$rid");});//初始化jpush.setup(appKey: "17d78ecf32c322db169a1d98",channel: "theChannel",production: false,debug: true, // 設(shè)置是否打印 debug 日志);//設(shè)置別名 實(shí)現(xiàn)指定用戶推送jpush.setAlias("jg123").then((map) {print("設(shè)置別名成功");});try {//監(jiān)聽消息通知jpush.addEventHandler(// 接收通知回調(diào)方法。onReceiveNotification: (Map<String, dynamic> message) async {print("flutter onReceiveNotification: $message");},// 點(diǎn)擊通知回調(diào)方法。onOpenNotification: (Map<String, dynamic> message) async {print("flutter onOpenNotification: $message");},// 接收自定義消息回調(diào)方法。onReceiveMessage: (Map<String, dynamic> message) async {print("flutter onReceiveMessage: $message");},);} catch (e) {print('極光sdk配置異常');}}指定設(shè)備推送
sockio
var http=require('http');var fs=require('fs'); /*fs內(nèi)置的模塊*/var app=http.createServer(function(req,res){//加載靜態(tài)頁面fs.readFile('app.html',function(err,data){res.writeHead(200,{"Content-Type":"text/html;charset='utf-8'"});res.end(data);}) })//引入socket.io var io = require('socket.io')(app);io.on('connection', function (socket) {console.log('服務(wù)器建立連接了');//服務(wù)器獲取客戶端廣播的數(shù)據(jù)socket.on('addcart',function(data){console.log(data);//服務(wù)器給客戶端發(fā)送數(shù)據(jù)//socket.emit(); /*誰給我發(fā)信息我把信息廣播給誰*///io.emit() ; /*群發(fā) 給所有連接服務(wù)器的客戶都廣播數(shù)據(jù)*///socket.emit('to-client','我是服務(wù)器的數(shù)據(jù)'+data.client);io.emit('to-client','我是服務(wù)器的數(shù)據(jù)'+data.client)})});app.listen(3000);/*使用socket.io 1.安裝npm install socket.io2、引入建立連接var io = require('socket.io')(app);io.on('connection', function (socket) {console.log('服務(wù)器建立連接了');});3、在客戶端 html里面引入jshttp://localhost:3000/socket.io/socket.io.js* */指紋
android:theme="@style/Theme.AppCompat" package com.xinxing.luckly_flutter//import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterFragmentActivity class MainActivity: FlutterFragmentActivity() { }FlutterIos中使用生物識別認(rèn)證的一些配置
Info.plist中加入下面配置
NSFaceIDUsageDescriptionfaceid進(jìn)行身份驗(yàn)證?
總結(jié)
以上是生活随笔為你收集整理的Flutter基础笔记的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 学习go语言国内最全资料链接
- 下一篇: vscode快捷键大全