文章目錄
- 1、內容簡介
- 2、使用Column等容器包裹ListView報錯的問題
- 3、Navigator operation requested ... does not include a Navigator.
- 4、設定Container背景色
- 5、去除AppBar 陰影
- 6、組件描邊,圓角
- 7、如何給row或column布局添加手勢監聽?
- 8、ListView和GridView嵌套報錯?
- 9、在Android studio中匯入Flutter專案報錯
- 10、給新頁面傳值
- 11、實作滑動關閉、洗掉item
- 12、添加Material觸摸水波效果
- 13、處理點擊
- 14、實作Icon可以用Image替換
- 15、驗證碼倒計時功能
- 16、從新頁面回傳資料給上一個頁面
- 17、創建一個 grid List
- 18、使用不同型別的子項創建串列
- 19、使用長串列
- 20、用占位符淡入淡出的顯示圖片
- 21、Dart中dynamic,var,object三者的區別
- 22、TextField設定默認值默認值和游標位置
- 23、限制TextField只能輸入漢字或英文字母或數字,并限制輸入最多中文10字符,英文20字符
- 24、Dart之正則運算式相關方法總結
- 25、使用正則運算式驗證手機號
- 26、去掉輸入框最大字數顯示
- 27、TextField 限制只允許輸入數字,字母,小數,設定限制小數位數
- 28、TextField 文本輸入框的基本屬性及詳解
- 29、實作農歷(陰歷)轉公歷(陽歷)
- 30、SDK升級
- 31、Expanded組件不能直接嵌套LitView報錯,解決辦法
- 32、ListView不能直接嵌套ListView解決辦法
- 32、Text的overflow屬性不生效(14)
喜歡記得點個贊喲,我是王睿,很高興認識大家!
1、內容簡介
本文主要總結了開發時遇到的Bug、報錯、開發時遇到的技術難題等,同時,讓大家能夠快速解決自己遇到額問題和避免采坑,如果對您有幫助,希望在文章的末尾能點個贊,謝謝!
2、使用Column等容器包裹ListView報錯的問題
報錯如下所示:
I/flutter ( 4625): EXCEPTION CAUGHT BY RENDERING LIBRARY
I/flutter ( 4625): The following assertion was thrown during performResize():
I/flutter ( 4625): Vertical viewport was given unbounded height.
I/flutter ( 4625): Viewports expand in the scrolling direction to fill their container.In this case, a vertical
I/flutter ( 4625): viewport was given an unlimited amount of vertical space in which to expand. This situation
I/flutter ( 4625): typically happens when a scrollable widget is nested inside another scrollable widget.
I/flutter ( 4625): If this widget is always nested in a scrollable widget there is no need to use a viewport because
I/flutter ( 4625): there will always be enough vertical space for the children. In this case, consider using a Column
I/flutter ( 4625): instead. Otherwise, consider using the “shrinkWrap” property (or a ShrinkWrappingViewport) to size
I/flutter ( 4625): the height of the viewport to the sum of the heights of its children.
解決方案:
使用擴展小部件Expanded包裹ListView
new Expanded(
child: new ListView(
.....
)
);
這告訴ListView盡可能地獲取寬度和高度,
3、Navigator operation requested … does not include a Navigator.
原錯誤: Navigator operation requested with a context that does not include a Navigator.
flutter 最容易報的一個錯誤就是does not include,因為其思想是組合
這種情況即使是外面包裹了materialapp也是無效的,因為flutter會根據這個context一直上溯,一直到根節點的widget,注意,上溯是根據context的,會上溯到這個context相關的widget的最根節點
14down vote
This error is unrelated to the destination. It happens because you used a context that doesn’t contain a Navigator instance as parent.
How do I create a Navigator instance then ?
This is usually done by inserting in your widget tree a MaterialApp or WidgetApp. Although you can do it manually by using Navigator directly but less recommended. Then, all children of such widget can access NavigatorState using Navigator.of(context).
Wait, I already have a MaterialApp/WidgetApp !
That’s most likely the case. But this error can still happens when you use a context that is a parent of MaterialApp/WidgetApp.
This happens because when you do Navigator.of(context), it will start from the widget associated to the context used. And then go upward in the widget tree until it either find a Navigator or there’s no more widget.
In the first case, everything is fine. In the second, it throws a
Navigator operation requested with a context that does not include a Navigator.
So, how do I fix it ?
First, let’s reproduce this error :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Center(
child: RaisedButton(
child: Text("Foo"),
onPressed: () => Navigator.pushNamed(context, "/"),
),
),
);
}
}
This example creates a button that attempts to go to ‘/’ on click but will instead throw an exception.
Notice here that in the
onPressed: () => Navigator.pushNamed(context, “/”),
we used context passed by to build of MyApp.
The problem is, MyApp is actually a parent of MaterialApp. As it’s the widget who instantiate MaterialApp! Therefore MyApp’s BuildContext doesn’t have a MaterialApp as parent!
To solve this problem, we need to use a different context.
In this situation, the easiest solution is to introduce a new widget as child of MaterialApp. And then use that widget’s context to do the Navigator call.
There are a few ways to achieve this. You can extract home into a custom class :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHome()
);
}
}
class MyHome extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: Text("Foo"),
onPressed: () => Navigator.pushNamed(context, "/"),
),
);
}
}
Or you can use Builder :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (context) => Center(
child: RaisedButton(
child: Text("Foo"),
onPressed: () => Navigator.pushNamed(context, "/"),
),
),
),
);
}
}
4、設定Container背景色
child: Container(
decoration: new BoxDecoration(
color: Colors.grey,
),
5、去除AppBar 陰影
頁面本身是白色,所以我想將appbar 也設定成白色,并且需要去除陰影
child: Scaffold(
appBar: AppBar(
title: Text("xixi"),
backgroundColor: Colors.white,
elevation: 0, //默認是4, 設定成0 就是沒有陰影了
),
backgroundColor: Colors.white,
6、組件描邊,圓角
通常用Container組件的decoration來做
Container(
padding: EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 1.0),
borderRadius: BorderRadius.circular(3.0)),
child: Text(_products[index]['description'])),
padding內邊距屬性,如果不添加描邊會緊貼文字image加上邊距之后image
EdgeInsets支持多種自定義方法
EdgeInsets.all() 全方向
EdgeInsets.only(left,top,right,bottom) 自定義各方向的邊距
EdgeInsets.symmetric(vertical, horizontal)自定義垂直,水平對稱邊距
EdgeInsets.fromWindowPadding(ui.WindowPadding padding, double devicePixelRatio) 根據機型螢屏尺寸定義
decoration這里用到BoxDecoration組件 常用屬性
color顏色
border 描邊寬度
borderRadius 圓角值
boxShadow 陰影 支持gradient 梯度,混合模式backgroundBlendMode shape自定義形狀 Border BorderRadius同樣支持多種自定方法.
7、如何給row或column布局添加手勢監聽?
直接在需要堅挺的布局外面套一層如下的代碼即可!
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap(){ //監聽器
Navigator.pushNamed(context, '/guide');//處理一些相關邏輯
}
8、ListView和GridView嵌套報錯?
直接在GridView布局里,加上這兩句代碼,即可解決問題!
physics: new NeverScrollableScrollPhysics(),//增加
shrinkWrap: true,//增加
例子如下:
GridView.count(
physics: new NeverScrollableScrollPhysics(),//增加
shrinkWrap: true, //增加
...
...
children: <Widget>[
...
...
],
);
9、在Android studio中匯入Flutter專案報錯
第一步:左上角File→Settings→Dart
配置如下:

第二步:

10、給新頁面傳值
效果圖一:
點擊跳轉

效果圖二:
點擊第四項后并傳值到下一個頁面來顯示

代碼+注釋:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
/**
* 定義主頁,
添加一個打開選擇頁面的按鈕,
在選擇頁面上顯示兩個按鈕,
點擊一個按鈕時,關閉選擇的頁面,
主頁上彈出一個snackbar以顯示用戶的選擇,
*/
class Todo {
final String title;
final String description;
Todo(this.title, this.description);
}
void main() {
runApp(new MaterialApp(
title: 'Passing Data',
home: new TodosScreen(
todos: new List.generate(
20,
(i) => new Todo(
'Todo $i',
'A description of what needs to be done for Todo $i',
),
),
),
));
}
class TodosScreen extends StatelessWidget {
final List<Todo> todos;
TodosScreen({Key key, @required this.todos}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Todos'),
),
body: new ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return new ListTile(
title: new Text(todos[index].title),
// When a user taps on the ListTile, navigate to the DetailScreen.
// Notice that we're not only creating a new DetailScreen, we're
// also passing the current todo through to it!
/**
*當用戶點擊ListTile時,導航到DetailScreen,請注意,我們不僅在創建新的DetailScreen,而且還在傳遞當前的待辦事項!
*/
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => new DetailScreen(todo: todos[index]), //傳入Todo物件
),
);
},
);
},
),
);
}
}
class DetailScreen extends StatelessWidget {
// Declare a field that holds the Todo
final Todo todo; //接收Todo 物件
// In the constructor, require a Todo
DetailScreen({Key key, @required this.todo}) : super(key: key);
@override
Widget build(BuildContext context) {
// Use the Todo to create our UI
return new Scaffold(
appBar: new AppBar(
title: new Text("${todo.title}"),
),
body: new Padding(
padding: new EdgeInsets.all(16.0),
child: new Text('${todo.description}'),
),
);
}
}
喜歡記得點個贊喲,我是王睿,很高興認識大家!
更多原理請參考谷歌官網:給新頁面傳值
11、實作滑動關閉、洗掉item
效果圖一:

效果圖二:
右滑洗掉第六個item

效果圖三:
左滑洗掉第4個item

效果圖四:
最后看到,第四項和第六項都被洗掉了

代碼+注釋:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp(
items: new List<String>.generate(20, (i) => "Item ${i + 1}"),
));
}
class MyApp extends StatelessWidget {
final List<String> items;
MyApp({Key key, @required this.items}) : super(key: key);
@override
Widget build(BuildContext context) {
final title = 'Dismissing Items';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return new Dismissible(
// Each Dismissible must contain a Key. Keys allow Flutter to
// uniquely identify Widgets.
/**
* 每個可豁免項都必須包含一個密鑰, 鍵使Flutter能夠唯一標識小部件,
*/
key: new Key(item),
// We also need to provide a function that will tell our app
// what to do after an item has been swiped away.
/**
* 我們還需要提供一個可以告訴我們應用程式的功能刷掉物品后該怎么辦,
* onDismissed —— 被解雇
* direction —— 方位表示:洗掉方向是左邊還是右邊,startToEnd 左邊,endToStart 右邊
*/
onDismissed: (direction) {
print(direction);
items.removeAt(index); //根據坐標移除具體的item項
// 顯示移除了哪一條item
Scaffold.of(context).showSnackBar(
new SnackBar(content: new Text("$item dismissed")));
},
// Show a red background as the item is swiped away
// 滑動洗掉item時顯示紅色背景
background: new Container(color: Colors.red),
child: new ListTile(title: new Text('$item')),
);
},
),
),
);
}
}
喜歡記得點個贊喲,我是王睿,很高興認識大家!
更多原理請參考谷歌官網:實作滑動關閉、洗掉item
12、添加Material觸摸水波效果
效果圖一:
點擊按鈕出現灰色水波紋特效

效果圖二:
顯示底部彈出框

代碼+注釋:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final title = 'InkWell Demo';
return new MaterialApp(
title: title,
home: new MyHomePage(title: title),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(child: new MyButton()),
);
}
}
class MyButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
// The InkWell Wraps our custom flat button Widget
/// InkWell 有水波紋, GestureDetector 沒有水波紋
return new InkWell(
// When the user taps the button, show a snackbar
// 當用戶點擊按鈕時,顯示 snackbar
onTap: () {
Scaffold.of(context).showSnackBar(new SnackBar(
content: new Text('Tap'),
));
},
child: new Container(
padding: new EdgeInsets.all(12.0),
child: new Text('Flat Button'),
),
);
}
}
喜歡記得點個贊喲,我是王睿,很高興認識大家!
更多原理請參考谷歌官網:添加Material觸摸水波效果
13、處理點擊
效果圖一:
點擊按鈕

效果圖二:
顯示底部彈出框

代碼+注釋:
import 'package:chapter02one/Api.dart';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final title = 'Gesture Demo';
return new MaterialApp(
title: title,
home: new MyHomePage(title: title),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(child: new MyButton()),
);
}
}
class MyButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Our GestureDetector wraps our button
///我們的GestureDetector包裹了我們的按鈕
return new GestureDetector(
// When the child is tapped, show a snackbar
///監聽器
onTap: () {
/**
* 準備: 底部彈出框物件
* content 是一個組件,可以是自定義的任何型別組件,也可以是復合型組合組件,例如Column
*/
final snackBar = new SnackBar(content: Text('底部彈出框呀'));
// 顯示 SnackBar
Scaffold.of(context).showSnackBar(snackBar);
},
// Our Custom Button!
child: new Container(
padding: new EdgeInsets.all(12.0),
decoration: new BoxDecoration(
color: Theme.of(context).buttonColor,
borderRadius: new BorderRadius.circular(8.0),
),
child: new Text('My Button'),
),
);
}
}
喜歡記得點個贊喲,我是王睿,很高興認識大家!
更多原理請參考谷歌官網:處理點擊
14、實作Icon可以用Image替換
flutter實作Icon可以用Image替換
23/100
發布文章
qq_27494201
未選擇任何檔案

flutter的Icon可以用Image替換
15、驗證碼倒計時功能
效果圖:

import 'dart:async';
import 'package:flutter/material.dart';
import '../widget/JdText.dart';
import '../widget/JdButton.dart';
import '../services/ScreenAdapter.dart';
import 'dart:async'; //Timer定時器需要引入
import '../config/Config.dart';
import 'package:dio/dio.dart';
import 'package:fluttertoast/fluttertoast.dart';
/**
* 注冊 — 第二步
*/
class RegisterSecondPage extends StatefulWidget {
Map arguments;
RegisterSecondPage({Key key, this.arguments}) : super(key: key);
_RegisterSecondPageState createState() => _RegisterSecondPageState();
}
class _RegisterSecondPageState extends State<RegisterSecondPage> {
String tel; //手機號碼
bool sendCodeBtn = false; //判斷發送短信按鈕是否點擊過標志
int seconds = 10; //倒計時 10秒
String code; //驗證碼
@override
void initState() {
// TODO: implement initState
super.initState();
this.tel = widget.arguments['tel'];
this._showTimer();
}
//倒計時
_showTimer() {
Timer t;
t = Timer.periodic(Duration(milliseconds: 1000), (timer) {
setState(() {
this.seconds--;
});
if (this.seconds == 0) {
t.cancel(); //清除定時器
setState(() {
this.sendCodeBtn = true;
});
}
});
}
//重新發送驗證碼
sendCode() async {
setState(() {
this.sendCodeBtn = false;
this.seconds = 10;
this._showTimer();
});
var api = '${Config.domain}api/sendCode';
var response = await Dio().post(api, data: {"tel": this.tel});
if (response.data["success"]) {
print(response); //演示期間服務器直接回傳 給手機發送的驗證碼
}
}
//驗證驗證碼
validateCode() async {
var api = '${Config.domain}api/validateCode';
var response =
await Dio().post(api, data: {"tel": this.tel, "code": this.code});
if (response.data["success"]) {
Navigator.pushNamed(context, '/registerThird',arguments: {
"tel":this.tel,
"code":this.code
});
} else {
Fluttertoast.showToast(
msg: '${response.data["message"]}',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("用戶注冊-第二步"),
),
body: Container(
padding: EdgeInsets.all(ScreenAdapter.width(20)),
child: ListView(
children: <Widget>[
SizedBox(height: 50),
Container(
padding: EdgeInsets.only(left: 10),
child: Text("驗證碼已經發送到了您的${this.tel}手機,請輸入${this.tel}手機號收到的驗證碼"),
),
SizedBox(height: 40),
Stack(
children: <Widget>[
Container(
child: JdText(
text: "請輸入驗證碼",
onChanged: (value) {
// print(value);
this.code = value;
},
),
height: ScreenAdapter.height(100),
),
Positioned(
right: 0,
top: 0,
child: this.sendCodeBtn
? RaisedButton(
child: Text('重新發送'),
onPressed: this.sendCode,
)
: RaisedButton(
child: Text('${this.seconds}秒后重發'),
onPressed: () {},
),
)
],
),
SizedBox(height: 20),
JdButton(
text: "下一步",
color: Colors.red,
height: 74,
cb: this.validateCode,
)
],
),
),
);
}
}
16、從新頁面回傳資料給上一個頁面
效果圖一:
點擊按鈕

效果圖二:
點擊YES后,回傳上一級頁面并傳值

效果圖三:
收到值,并用底部彈出框顯示結果

注釋+代碼:
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
title: 'Returning Data',
home: new HomeScreen(),
));
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Returning Data Demo'),
),
body: new Center(child: new SelectionButton()),
);
}
}
class SelectionButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new RaisedButton(
onPressed: () {
_navigateAndDisplaySelection(context);
},
child: new Text('Pick an option, any option!'),
);
}
// A method that launches the SelectionScreen and awaits the result from
// Navigator.pop!
/**
* 一種啟動SelectionScreen并等待以下結果的方法 Navigator.pop!
*/
_navigateAndDisplaySelection(BuildContext context) async {
// Navigator.push returns a Future that will complete after we call
// Navigator.pop on the Selection Screen!
/**
* Navigator.push回傳一個Future,它將在我們呼叫后完成選擇螢屏上的Navigator.pop!
*/
final result = await Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new SelectionScreen()),
);
// After the Selection Screen returns a result, show it in a Snackbar!
///選擇螢屏回傳結果后,將其顯示在小吃欄中!
Scaffold
.of(context)
.showSnackBar(new SnackBar(content: new Text("$result")));
}
}
class SelectionScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Pick an option'),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Padding(
padding: const EdgeInsets.all(8.0),
child: new RaisedButton(
onPressed: () {
// Close the screen and return "Yep!" as the result
/// 關閉螢屏并回傳“是!” 作為結果
Navigator.pop(context, 'Yep!');
},
child: new Text('Yep!'),
),
),
new Padding(
padding: const EdgeInsets.all(8.0),
child: new RaisedButton(
onPressed: () {
// Close the screen and return "Nope!" as the result
/// 關閉螢屏并回傳“不!” 作為結果
Navigator.pop(context, 'Nope.');
},
child: new Text('Nope.'),
),
)
],
),
),
);
}
}
喜歡記得點個贊喲,我是王睿,很高興認識大家!
更多原理請參考谷歌官網:從新頁面回傳資料給上一個頁面
17、創建一個 grid List
效果圖:

代碼+注釋:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final title = 'Grid List';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new GridView.count(
// Create a grid with 2 columns. If you change the scrollDirection to
// horizontal, this would produce 2 rows.
/**
* 創建一個包含2列的網格, 如果將scrollDirection更改為水平,這將產生2行,
*/
crossAxisCount: 2,
// Generate 100 Widgets that display their index in the List
// 生成100個在串列中顯示其索引的小部件,引數一: 數量,引數二: 下標
children: new List.generate(100, (index) {
return new Center(
child: new Text(
'Item $index',
style: Theme.of(context).textTheme.headline,
),
);
}),
),
),
);
}
}
喜歡記得點個贊喲,我是王睿,很高興認識大家!
更多原理請參考谷歌官網:創建一個 grid List
18、使用不同型別的子項創建串列
效果圖:

代碼+注釋:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp(
// List.generate建構式 —— 生成擁有1000個字串的串列
items: new List<ListItem>.generate(
1000,
(i) => i % 6 == 0 //根據規律: 每一個標題后面會跟著五條內容
? new HeadingItem("Heading $i") //標題型別
: new MessageItem("Sender $i", "Message body $i"), //內容型別
),
));
}
class MyApp extends StatelessWidget {
final List<ListItem> items;
MyApp({Key key, @required this.items}) : super(key: key);
@override
Widget build(BuildContext context) {
final title = 'Mixed List';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new ListView.builder(
// Let the ListView know how many items it needs to build
// 讓ListView知道需要構建多少個專案
itemCount: items.length,
// Provide a builder function. This is where the magic happens! We'll
// 提供構建器功能, 這就是魔術發生的地方! 好
// convert each item into a Widget based on the type of item it is.
// 根據專案型別將每個專案轉換為Widget,
itemBuilder: (context, index) {
final item = items[index]; //得到具體的items
if (item is HeadingItem) { //是否為標題型別
return new ListTile(
title: new Text(
item.heading,
style: Theme.of(context).textTheme.headline,
),
);
} else if (item is MessageItem) { //是否為內容型別
return new ListTile(
title: new Text(item.sender),
subtitle: new Text(item.body),
);
}
},
),
),
);
}
}
// The base class for the different types of items the List can contain
// 串列可以包含的不同型別的專案的基類
abstract class ListItem {}
// A ListItem that contains data to display a heading
// 一個ListItem,其中包含顯示標題的資料
class HeadingItem implements ListItem {
final String heading;
HeadingItem(this.heading);
}
// A ListItem that contains data to display a message
// 一個ListItem包含顯示訊息的資料
class MessageItem implements ListItem {
final String sender;
final String body;
MessageItem(this.sender, this.body);
}
喜歡記得點個贊喲,我是王睿,很高興認識大家!
更多原理請參考谷歌官網:使用不同型別的子項創建串列
19、使用長串列
效果圖:
利用 List.generate建構式 —— 配合 ListView 生成擁有10000個字串的串列

代碼+注釋:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp(
// List.generate建構式 —— 生成擁有10000個字串的串列
items: new List<String>.generate(10000, (i) => "Item $i"),
));
}
class MyApp extends StatelessWidget {
final List<String> items;
MyApp({Key key, @required this.items}) : super(key: key);
@override
Widget build(BuildContext context) {
final title = 'Long List';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new ListView.builder(
itemCount: items.length, //List長度
itemBuilder: (context, index) { //Item構造者
return new ListTile(
title: new Text('${items[index]}'),
);
},
),
),
);
}
}
喜歡記得點個贊喲,我是王睿,很高興認識大家!
更多原理請參考谷歌官網:
使用長串列
20、用占位符淡入淡出的顯示圖片
效果圖一:
由于是網路圖片,加載速度由網速決定,所以先顯示進度條圈圈

效果圖二:
加載完畢后,淡入淡出的顯示圖片,不是一下子顯示的喲!

匯入依賴:
transparent_image: ^1.0.0
代碼+注釋:
import 'package:chapter02one/Api.dart';
import 'package:flutter/material.dart';
import 'package:transparent_image/transparent_image.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final title = 'Fade in images';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Stack(
children: <Widget>[
new Center(child: new CircularProgressIndicator()),
new Center(
/**
* FadeInImage.memoryNetwork
* 正在加載時,會顯示加載進度條
* 加載完畢后,會慢慢淡入淡出的顯示圖片
*/
child: new FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image:
'${Api.URL}/aa.png',
),
),
],
),
),
);
}
}
喜歡記得點個贊喲,我是王睿,很高興認識大家!
更多原理請參考谷歌官網:
用占位符淡入圖片
21、Dart中dynamic,var,object三者的區別
void main()//dynamic,var,object三者的區別
{
//dynamic
dynamic x = 'hello';//編譯時不會揣測資料型別,但是運行時會推斷
print(x.runtimeType);//String
print(x);
//但是這樣的壞處就是會讓dart的語法檢查失效,所以有可能會造成混亂而不報錯
//所以不要直接使用dynamic
x = 123;
print(x.runtimeType);//int,說明型別是可變的
print(x);
//var
var a = 'hello';
print(a.runtimeType);
print(a);
//a = 123;//會報錯
a = '123';
print(a);
//Object
Object w = 1;
print(w.runtimeType);
print(w);
//不能呼叫Object不存在的方法
}
22、TextField設定默認值默認值和游標位置
TextField(
//輸入鍵盤型別
keyboardType: TextInputType.text,
autofocus: true,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(),
borderSide: BorderSide.none),
),
onChanged: (value) {
this._keyword = value;
},
controller: TextEditingController.fromValue(TextEditingValue(
text: '${this._keyword == null ? "" : this._keyword}', //判斷keyword是否為空
// 保持游標在最后
selection: TextSelection.fromPosition(TextPosition(
affinity: TextAffinity.downstream,
offset: '${this._keyword}'.length)))),
),
23、限制TextField只能輸入漢字或英文字母或數字,并限制輸入最多中文10字符,英文20字符
原文地址
第一步,給textfield設定輸入法則:
inputFormatters: [
WhitelistingTextInputFormatter(RegExp(
“[a-zA-Z]|[\u4e00-\u9fa5]|[0-9]”)), //只能輸入漢字或者字母或數字
LengthLimitingTextInputFormatter(maxLength),//最大長度
],
第二步,動態修改最大字長:
onChanged: (value) {
_changeMaxLimit(value);
},
/// 改字數限制,每輸入一個中文字符,要減1,
/// 字符要求:10個漢字或20個英文
void _changeMaxLimit(String value) {
maxLength = 20;
for (int i = 0; i < value.length; i++) {
if (value.codeUnitAt(i) > 122) {
maxLength–;
}
}
setState(() {});
}
我這里簡略寫了關鍵部分,關于maxLength的初始化,textfield別的引數設定,大家還請自行解決,
24、Dart之正則運算式相關方法總結
原文地址
【Dart學習】–Dart之正則運算式相關方法總結
一,部分屬性
RegExp exp = new RegExp(r"(\w+)");
回傳正則運算式的哈希碼
print(exp.hashCode);
正則運算式是否區分大小寫
print(exp.isCaseSensitive);
正則運算式是否匹配多行
print(exp.isMultiLine);
回傳源正則運算式字串
print(exp.pattern);
回傳物件運行時的型別
print(exp.runtimeType);
二,常用方法
…
25、使用正則運算式驗證手機號
RegExp exp = RegExp(
r'^((13[0-9])|(14[0-9])|(15[0-9])|(16[0-9])|(17[0-9])|(18[0-9])|(19[0-9]))\d{8}$');
bool matched = exp.hasMatch(mobileTextController.text);
26、去掉輸入框最大字數顯示
new TextField(
maxLength:11 ,
enabled:widget.phone == null?true:false,
keyboardType: TextInputType.number,
decoration: InputDecoration(`在這里插入代碼片`
counterText: "",//此處控制最大字符是否顯示
hintText: widget.phone == null?'手機號':widget.phone,
hintStyle: TextStyle(fontSize: 14,color: AppColors.colorGrayText),
border: InputBorder.none,
),
controller: _phoneContro, ));
27、TextField 限制只允許輸入數字,字母,小數,設定限制小數位數
原文地址
TextField(
inputFormatters: [
//只允許輸入字母
WhitelistingTextInputFormatter(RegExp("[a-zA-Z]")),
],
),
TextField(
//只允許輸入數字
inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],
),
TextField(
inputFormatters: [
//只允許輸入小數
WhitelistingTextInputFormatter(RegExp("[0-9.]")),
],
)
,
TextField(
inputFormatters: [
//限制小數位數
_MyNumberTextInputFormatter(digit:5),
],
)
// 限制小數位數
class _MyNumberTextInputFormatter extends TextInputFormatter {
static const defaultDouble = 0.001;
///允許的小數位數,-1代表不限制位數
int digit;
_MyNumberTextInputFormatter({this.digit=-1});
static double strToFloat(String str, [double defaultValue = defaultDouble]) {
try {
return double.parse(str);
} catch (e) {
return defaultValue;
}
}
///獲取目前的小數位數
static int getValueDigit(String value){
if(value.contains(".")){
return value.split(".")[1].length;
}else{
return -1;
}
}
@override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
String value = newValue.text;
int selectionIndex = newValue.selection.end;
if (value == ".") {
value = "0.";
selectionIndex++;
} else if (value != ""
&& value != defaultDouble.toString()
&& strToFloat(value, defaultDouble) == defaultDouble
||getValueDigit(value)>digit) {
value = oldValue.text;
selectionIndex = oldValue.selection.end;
}
return new TextEditingValue(
text: value,
selection: new TextSelection.collapsed(offset: selectionIndex),
);
}
}
28、TextField 文本輸入框的基本屬性及詳解
const TextField({
Key key,
this.controller, // 控制正在編輯文本
this.focusNode, // 獲取鍵盤焦點
this.decoration = const InputDecoration(), // 邊框裝飾
TextInputType keyboardType, // 鍵盤型別
this.textInputAction, // 鍵盤的操作按鈕型別
this.textCapitalization = TextCapitalization.none, // 配置大小寫鍵盤
this.style, // 輸入文本樣式
this.textAlign = TextAlign.start, // 對齊方式
this.textDirection, // 文本方向
this.autofocus = false, // 是否自動對焦
this.obscureText = false, // 是否隱藏內容,例如密碼格式
this.autocorrect = true, // 是否自動校正
this.maxLines = 1, // 最大行數
this.maxLength, // 允許輸入的最大長度
this.maxLengthEnforced = true, // 是否允許超過輸入最大長度
this.onChanged, // 文本內容變更時回呼
this.onEditingComplete, // 提交內容時回呼
this.onSubmitted, // 用戶提示完成時回呼
this.inputFormatters, // 驗證及格式
this.enabled, // 是否不可點擊
this.cursorWidth = 2.0, // 游標寬度
this.cursorRadius, // 游標圓角弧度
this.cursorColor, // 游標顏色
this.keyboardAppearance, // 鍵盤亮度
this.scrollPadding = const EdgeInsets.all(20.0), // 滾動到視圖中時,填充邊距
this.enableInteractiveSelection, // 長按是否展示【剪切/復制/粘貼選單LengthLimitingTextInputFormatter】
this.onTap, // 點擊時回呼
})
29、實作農歷(陰歷)轉公歷(陽歷)
/**
* 農歷的工具類
*/
class LunarCalendarUtil {
/**
* 支持轉換的最小農歷年份
*/
static final int MIN_YEAR = 1900;
/**
* 支持轉換的最大農歷年份
*/
static final int MAX_YEAR = 2099;
/**
* 公歷每月前的天數
*/
static final List<int> DAYS_BEFORE_MONTH = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
/**
* 用來表示1900年到2099年間農歷年份的相關資訊,共24位bit的16進制表示,其中:
* 1. 前4位表示該年閏哪個月;
* 2. 5-17位表示農歷年份13個月的大小月分布,0表示小,1表示大;
* 3. 最后7位表示農歷年首(正月初一)對應的公歷日期,
* 以2014年的資料0x955ABF為例說明:
* 1001 0101 0101 1010 1011 1111
* 閏九月 農歷正月初一對應公歷1月31號
*/
static final List<int> LUNAR_INFO = [
/*1900*/
0x84B6BF,
/*1901-1910*/
0x04AE53, 0x0A5748, 0x5526BD, 0x0D2650, 0x0D9544, 0x46AAB9, 0x056A4D, 0x09AD42, 0x24AEB6, 0x04AE4A,
/*1911-1920*/
0x6A4DBE, 0x0A4D52, 0x0D2546, 0x5D52BA, 0x0B544E, 0x0D6A43, 0x296D37, 0x095B4B, 0x749BC1, 0x049754,
/*1921-1930*/
0x0A4B48, 0x5B25BC, 0x06A550, 0x06D445, 0x4ADAB8, 0x02B64D, 0x095742, 0x2497B7, 0x04974A, 0x664B3E,
/*1931-1940*/
0x0D4A51, 0x0EA546, 0x56D4BA, 0x05AD4E, 0x02B644, 0x393738, 0x092E4B, 0x7C96BF, 0x0C9553, 0x0D4A48,
/*1941-1950*/
0x6DA53B, 0x0B554F, 0x056A45, 0x4AADB9, 0x025D4D, 0x092D42, 0x2C95B6, 0x0A954A, 0x7B4ABD, 0x06CA51,
/*1951-1960*/
0x0B5546, 0x555ABB, 0x04DA4E, 0x0A5B43, 0x352BB8, 0x052B4C, 0x8A953F, 0x0E9552, 0x06AA48, 0x6AD53C,
/*1961-1970*/
0x0AB54F, 0x04B645, 0x4A5739, 0x0A574D, 0x052642, 0x3E9335, 0x0D9549, 0x75AABE, 0x056A51, 0x096D46,
/*1971-1980*/
0x54AEBB, 0x04AD4F, 0x0A4D43, 0x4D26B7, 0x0D254B, 0x8D52BF, 0x0B5452, 0x0B6A47, 0x696D3C, 0x095B50,
/*1981-1990*/
0x049B45, 0x4A4BB9, 0x0A4B4D, 0xAB25C2, 0x06A554, 0x06D449, 0x6ADA3D, 0x0AB651, 0x095746, 0x5497BB,
/*1991-2000*/
0x04974F, 0x064B44, 0x36A537, 0x0EA54A, 0x86B2BF, 0x05AC53, 0x0AB647, 0x5936BC, 0x092E50, 0x0C9645,
/*2001-2010*/
0x4D4AB8, 0x0D4A4C, 0x0DA541, 0x25AAB6, 0x056A49, 0x7AADBD, 0x025D52, 0x092D47, 0x5C95BA, 0x0A954E,
/*2011-2020*/
0x0B4A43, 0x4B5537, 0x0AD54A, 0x955ABF, 0x04BA53, 0x0A5B48, 0x652BBC, 0x052B50, 0x0A9345, 0x474AB9,
/*2021-2030*/
0x06AA4C, 0x0AD541, 0x24DAB6, 0x04B64A, 0x6a573D, 0x0A4E51, 0x0D2646, 0x5E933A, 0x0D534D, 0x05AA43,
/*2031-2040*/
0x36B537, 0x096D4B, 0xB4AEBF, 0x04AD53, 0x0A4D48, 0x6D25BC, 0x0D254F, 0x0D5244, 0x5DAA38, 0x0B5A4C,
/*2041-2050*/
0x056D41, 0x24ADB6, 0x049B4A, 0x7A4BBE, 0x0A4B51, 0x0AA546, 0x5B52BA, 0x06D24E, 0x0ADA42, 0x355B37,
/*2051-2060*/
0x09374B, 0x8497C1, 0x049753, 0x064B48, 0x66A53C, 0x0EA54F, 0x06AA44, 0x4AB638, 0x0AAE4C, 0x092E42,
/*2061-2070*/
0x3C9735, 0x0C9649, 0x7D4ABD, 0x0D4A51, 0x0DA545, 0x55AABA, 0x056A4E, 0x0A6D43, 0x452EB7, 0x052D4B,
/*2071-2080*/
0x8A95BF, 0x0A9553, 0x0B4A47, 0x6B553B, 0x0AD54F, 0x055A45, 0x4A5D38, 0x0A5B4C, 0x052B42, 0x3A93B6,
/*2081-2090*/
0x069349, 0x7729BD, 0x06AA51, 0x0AD546, 0x54DABA, 0x04B64E, 0x0A5743, 0x452738, 0x0D264A, 0x8E933E,
/*2091-2099*/
0x0D5252, 0x0DAA47, 0x66B53B, 0x056D4F, 0x04AE45, 0x4A4EB9, 0x0A4D4C, 0x0D1541, 0x2D92B5
];
/**
* 將農歷日期轉換為公歷日期
* @param year 農歷年份
* @param month 農歷月
* @param monthDay 農歷日
* @param isLeapMonth 該月是否是閏月(該引數可以根據本類中leapMonth()方法,先判斷一下要查詢的年份是否有閏月,并且閏的幾月)
* @return 回傳農歷日期對應的公歷日期,year0, month1, day2.
*/
static List<int> lunarToSolar(int year, int month, int monthDay, bool isLeapMonth) {
int dayOffset;
int leapMonth;
int i;
if (year < MIN_YEAR || year > MAX_YEAR || month < 1 || month > 12
|| monthDay < 1 || monthDay > 30) {
throw new Exception(
"Illegal lunar date, must be like that:\n\t" +
"year : 1900~2099\n\t" +
"month : 1~12\n\t" +
"day : 1~30");
}
dayOffset = (LUNAR_INFO[year - MIN_YEAR] & 0x001F) - 1;
if (((LUNAR_INFO[year - MIN_YEAR] & 0x0060) >> 5) == 2) {
dayOffset += 31;
}
for (i = 1; i < month; i++) {
if ((LUNAR_INFO[year - MIN_YEAR] & (0x80000 >> (i - 1))) == 0) {
dayOffset += 29;
} else {
dayOffset += 30;
}
}
dayOffset += monthDay;
leapMonth = (LUNAR_INFO[year - MIN_YEAR] & 0xf00000) >> 20;
// 這一年有閏月
if (leapMonth != 0) {
if (month > leapMonth || (month == leapMonth && isLeapMonth)) {
if ((LUNAR_INFO[year - MIN_YEAR] & (0x80000 >> (month - 1))) == 0) {
dayOffset += 29;
} else {
dayOffset += 30;
}
}
}
if (dayOffset > 366 || (year % 4 != 0 && dayOffset > 365)) {
year += 1;
if (year % 4 == 1) {
dayOffset -= 366;
} else {
dayOffset -= 365;
}
}
List<int> solarInfo = new List(3);
for (i = 1; i < 13; i++) {
int iPos = DAYS_BEFORE_MONTH[i];
if (year % 4 == 0 && i > 2) {
iPos += 1;
}
if (year % 4 == 0 && i == 2 && iPos + 1 == dayOffset) {
solarInfo[1] = i;
solarInfo[2] = dayOffset - 31;
break;
}
if (iPos >= dayOffset) {
solarInfo[1] = i;
iPos = DAYS_BEFORE_MONTH[i - 1];
if (year % 4 == 0 && i > 2) {
iPos += 1;
}
if (dayOffset > iPos) {
solarInfo[2] = dayOffset - iPos;
} else if (dayOffset == iPos) {
if (year % 4 == 0 && i == 2) {
solarInfo[2] = DAYS_BEFORE_MONTH[i] - DAYS_BEFORE_MONTH[i - 1] + 1;
} else {
solarInfo[2] = DAYS_BEFORE_MONTH[i] - DAYS_BEFORE_MONTH[i - 1];
}
} else {
solarInfo[2] = dayOffset;
}
break;
}
}
solarInfo[0] = year;
return solarInfo;
}
/**
* 傳回農歷year年month月的總天數
*
* @param year 要計算的年份
* @param month 要計算的月
* @return 傳回天數
*/
static int daysInMonth(int year, int month) {
return daysInMontaThree(year, month, false);
}
/**
* 傳回農歷year年month月的總天數
*
* @param year 要計算的年份
* @param month 要計算的月
* @param leap 當月是否是閏月
* @return 傳回天數,如果閏月是錯誤的,回傳0.
*/
static int daysInMontaThree(int year, int month, bool leap) {
int mLeapMonth = leapMonth(year);
int offset = 0;
// 如果本年有閏月且month大于閏月時,需要校正
if (leapMonth != 0 && month > mLeapMonth) {
offset = 1;
}
// 不考慮閏月
if (!leap) {
return daysInLunarMonth(year, month + offset);
} else {
// 傳入的閏月是正確的月份
if (leapMonth != 0 && leapMonth == month) {
return daysInLunarMonth(year, month + 1);
}
}
return 0;
}
/**
* 傳回農歷 year年的總天數
*
* @param year 將要計算的年份
* @return 回傳傳入年份的總天數
*/
static int daysInLunarYear(int year) {
int i, sum = 348;
if (leapMonth(year) != 0) {
sum = 377;
}
int monthInfo = LUNAR_INFO[year - MIN_YEAR] & 0x0FFF80;
for (i = 0x80000; i > 0x7; i >>= 1) {
if ((monthInfo & i) != 0) {
sum += 1;
}
}
return sum;
}
/**
* 傳回農歷 year年month月的總天數,總共有13個月包括閏月
*
* @param year 將要計算的年份
* @param month 將要計算的月份
* @return 傳回農歷 year年month月的總天數
*/
static int daysInLunarMonth(int year, int month) {
if ((LUNAR_INFO[year - MIN_YEAR] & (0x100000 >> month)) == 0) {
return 29;
} else {
return 30;
}
}
/**
* 傳回農歷 year年閏哪個月 1-12 , 沒閏傳回 0
* @param year 將要計算的年份
* @return 傳回農歷 year年閏哪個月1-12, 沒閏傳回 0
*/
static int leapMonth(int year) {
return ((LUNAR_INFO[year - MIN_YEAR] & 0xF00000)) >> 20;
}
}
測驗資料:
測驗資料:2020年12月5日轉公歷結果為:2021年1月17日
Text('${LunarCalendarUtil.lunarToSolar(2020, 12, 5, true)}'),
30、SDK升級
1、在終端中使用flutter upgrade
2、洗掉SDK包重新下載
31、Expanded組件不能直接嵌套LitView報錯,解決辦法
這樣寫,即可解決問題!
Row(
children: <Widget>[
Expanded(
flex: 1,
child: Container(
height: 180,
child: ListView(
children: <Widget>[
Container(
height: 85,
child: Image.network("https://www.itying.com/images/flutter/3.png",fit: BoxFit.cover),
),
SizedBox(height: 10),
Container(
height: 85,
child: Image.network("https://www.itying.com/images/flutter/4.png",fit: BoxFit.cover),
)
],
)
)
),
],
)
32、ListView不能直接嵌套ListView解決辦法
ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
Container(
width: 180,
color: Colors.orange,
child: ListView(
children: <Widget>[
Image.network("https://www.itying.com/images/flutter/1.png"),
Text('我是一個文本')
],
),
),
],
),
直接套一個Container即可解決問題
32、Text的overflow屬性不生效(14)
Text的overflow屬性不生效(14)
33、
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/291072.html
標籤:其他
下一篇:MacBook 配置adb命令
