目錄:
- Views 視圖
1.1 widgets
1.2 動(dòng)態(tài)更新 UI
1.3 增加或者隱藏一個(gè)控件
1.4 增加動(dòng)畫效果
1.5 畫布的使用
1.6 自定義組件
- Intents 意圖
2.1 指定跳轉(zhuǎn)路線
2.2 直接跳到目標(biāo)
2.3 使用插件
- Async UI 異步更新UI
3.1 與 Android 的區(qū)別
3.2 async / await
3.3 如何移動(dòng)任務(wù)到后臺(tái)進(jìn)程
3.4 關(guān)于網(wǎng)絡(luò)框架
3.5 關(guān)于進(jìn)度條
- Project structure & resources 項(xiàng)目結(jié)構(gòu)&資源
差異1:圖片資源
差異2:string.xml
差異3:lib 資源绣檬,使用框架
- Activities and fragments 界面
5.1 Activities and fragments VS Widgets
5.2 如何監(jiān)聽 Activities 的生命周期事件
- Layouts 布局
LinearLayout
RelativeLayout
ScrollView
- Gesture detection and touch event handling 手勢(shì)和觸屏事件處理
第一桦踊,如何處理手機(jī)屏幕轉(zhuǎn)動(dòng)的問(wèn)題?
第二,如何小部件的點(diǎn)擊事件乃摹?
- Listviews & adapters
- Working with text 文本框
設(shè)置字體
設(shè)置風(fēng)格
- Form input 輸入框
[1] 增加提示 hint
[2] 設(shè)置允許輸入字符類型
- Themes 主題
- Databases and local storage 數(shù)據(jù)庫(kù)和本地存儲(chǔ)
[1] Shared Preferences
[2] SQLite
- 總結(jié)
1. Views
在 Android 中嗽冒, 視圖是展示在屏幕上的所有的基礎(chǔ)舌界。例如 按鈕离福、工具欄摩桶、輸入框等都是一個(gè)視圖制肮。在 Flutter 中词疼,Widget 粗略的等同于 Android 中的 View.
Widget 有不同于 Android 視圖的生命周期:她們是不可改變的并且只有在需要改變的時(shí)候才存在饮怯。對(duì)比 Android, 視圖只繪制一次蝌衔,并且在它無(wú)效之前不會(huì)被重新繪制榛泛。
如何去理解這句話呢?
Widget 只是 UI 的描述胚委,然后編譯的時(shí)候被對(duì)應(yīng)到引擎的實(shí)際視圖對(duì)象中而顯現(xiàn)出來(lái)挟鸠,不需要繪制任何東西。因此亩冬,Widget 是輕量級(jí)的艘希。
這就像是你只是根據(jù) Flutter 的語(yǔ)義描述了這個(gè)界面應(yīng)該是怎么樣的硼身,然后這些描述在編譯解釋對(duì)應(yīng)到引擎下的實(shí)際視圖對(duì)象。
原文描述:
In Android, the View is the foundation of everything that shows up on the screen. Buttons, toolbars, and inputs, everything is a View. In Flutter, the rough equivalent to a View is a Widget
widgets have a different lifespan: they are immutable and only exist until they need to be changed. In comparison, an Android view is drawn once and does not redraw until invalidate is called.
1.1 widgets
[1] StatelessWidgets (靜態(tài)部件)
靜態(tài)部件用在不需要變化的地方覆享,例如說(shuō) app 的 logo . icon 是放在資源文件下佳遂,而不是網(wǎng)絡(luò)請(qǐng)求獲取的; ImageView 的顯示是可以寫固定的撒顿。
StatelessWidgets are useful when the part of the user interface you are describing does not depend on anything other than the configuration information in the object. == For example, in Android, this is similar to placing an ImageView with your logo. The logo is not going to change during runtime, so use a StatelessWidget in Flutter.
[2] StatefulWidget(狀態(tài)部件)
狀態(tài)部件用在動(dòng)態(tài)變化的地方丑罪,例如說(shuō) ListView 顯示服務(wù)端返回的數(shù)據(jù)。
If you want to dynamically change the UI based on data received after making an HTTP call or user interaction then you have to work with StatefulWidget and tell the Flutter framework that the widget’s State has been updated so it can update that widget.
1.2 動(dòng)態(tài)更新 UI 凤壁,例如: 點(diǎn)擊 FloatingActionButton 改變 Text 顯示的文字吩屹。
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
// Default placeholder text
String textToShow = "I Like Flutter";
void _updateText() {
setState(() {
// update the text
textToShow = "Flutter is Awesome!";
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(child: Text(textToShow)),
floatingActionButton: FloatingActionButton(
onPressed: _updateText,
tooltip: 'Update Text',
child: Icon(Icons.update),
),
);
}
}
效果如下:
1.3 增加或者隱藏一個(gè)組件,例如 原本顯示一個(gè) Text , FloatingActionButton 點(diǎn)擊時(shí)顯示一個(gè) MaterialButton.
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
// Default value for toggle
bool toggle = true;
void _toggle() {
setState(() {
toggle = !toggle;
});
}
_getToggleChild() {
if (toggle) {
return Text('Toggle One');
} else {
return MaterialButton(onPressed: () {}, child: Icon(Icons.account_balance),padding: EdgeInsets.only(left: 10.0, right: 10.0));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(
child: _getToggleChild(),
),
floatingActionButton: FloatingActionButton(
onPressed: _toggle,
tooltip: 'Update Text',
child: Icon(Icons.update),
),
);
}
}
效果如下:
1.4 增加動(dòng)畫效果拧抖,示例:FloatingActionButton 點(diǎn)擊后動(dòng)畫顯示出 Icon.
In Flutter, use an AnimationController which is an Animation<double> that can pause, seek, stop and reverse the animation. It requires a Ticker that signals when vsync happens, and produces a linear interpolation between 0 and 1 on each frame while it’s running. You then create one or more Animations and attach them to the controller.
import 'package:flutter/material.dart';
void main() {
runApp(FadeAppTest());
}
class FadeAppTest extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fade Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyFadeTest(title: 'Fade Demo'),
);
}
}
class MyFadeTest extends StatefulWidget {
MyFadeTest({Key key, this.title}) : super(key: key);
final String title;
@override
_MyFadeTest createState() => _MyFadeTest();
}
class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin {
AnimationController controller;
CurvedAnimation curve;
@override
void initState() {
super.initState();
controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(
child: FadeTransition(
opacity: curve,
child: FlutterLogo(
size: 100.0,
)))),
floatingActionButton: FloatingActionButton(
tooltip: 'Fade',
child: Icon(Icons.brush),
onPressed: () {
controller.forward();
},
),
);
}
}
效果顯示如下煤搜,logo 動(dòng)態(tài)淡入。
1.5 畫布的使用
創(chuàng)建一個(gè)手勢(shì)區(qū)域唧席,使用 GestureDetector 去記錄觸碰路徑擦盾,CustomPaint 去繪制顯示在屏幕上。
create a signature area using GestureDetector to record touches and CustomPaint to draw on the screen. Here are a few tips:
- Use
RenderBox.globalToLocal
to convert theDragUpdateDetails
provided byGestureDetector.onPanUpdate
into relative coordinates - Use a
GestureDetector.onPanEnd
gesture handler to record the breaks between strokes. - Mutating the same List won't automatically trigger a repaint because the
CustomPainter
constructor arguments are the same. You can trigger a repaint by creating a new List each time a new point is provided. - Use
Canvas.drawLine
to draw a rounded line between each of the recorded points of the signature.
--這段文字來(lái)源于Collin Jackson 在 stackoverflow 上的回答淌哟。更多的源碼可以在他的 github 上找到迹卢。
import 'package:flutter/material.dart';
class SignaturePainter extends CustomPainter {
SignaturePainter(this.points);
final List<Offset> points;
void paint(Canvas canvas, Size size) {
Paint paint = new Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null)
canvas.drawLine(points[i], points[i + 1], paint);
}
}
bool shouldRepaint(SignaturePainter other) => other.points != points;
}
class Signature extends StatefulWidget {
SignatureState createState() => new SignatureState();
}
class SignatureState extends State<Signature> {
List<Offset> _points = <Offset>[];
Widget build(BuildContext context) {
return new Stack(
children: [
GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
RenderBox referenceBox = context.findRenderObject();
Offset localPosition =
referenceBox.globalToLocal(details.globalPosition);
setState(() {
_points = new List.from(_points)..add(localPosition);
});
},
onPanEnd: (DragEndDetails details) => _points.add(null),
),
CustomPaint(painter: SignaturePainter(_points), size: Size.infinite),
],
);
}
}
class DemoApp extends StatelessWidget {
Widget build(BuildContext context) => new Scaffold(body: new Signature());
}
void main() => runApp(new MaterialApp(home: new DemoApp()));
1.6 自定義組件
在 Android 中自定義組件是通過(guò)繼承 View 或者是 TextView 這類已經(jīng)擁有一些特性的組件,然后重寫父類的方法或者實(shí)現(xiàn)接口徒仓。
在 Flutter 中腐碱,構(gòu)建一個(gè)自定義組件是通過(guò)組合小部件。有點(diǎn)類似 Android 中的 ViewGroup.
例如蓬衡,你要自定義一個(gè) button 中含有 圖片和 文字喻杈。
In Android, you typically subclass View
, or use a pre-existing view, to override and implement methods that achieve the desired behavior.
In Flutter, build a custom widget by composing smaller widgets (instead of extending them). It is somewhat similar to implementing a custom ViewGroup
in Android, where all the building blocks are already existing, but you provide a different behavior—for example, custom layout logic.
For example, how do you build a CustomButton that takes a label in the constructor? Create a CustomButton that composes a RaisedButton with a label, rather than by extending RaisedButton:
class CustomButton extends StatelessWidget {
final String label;
CustomButton(this.label);
@override
Widget build(BuildContext context) {
return RaisedButton(onPressed: () {}, child: Text(label));
}
}
Then use CustomButton, just as you’d use any other Flutter widget:
@override
Widget build(BuildContext context) {
return Center(
child: CustomButton("Hello"),
);
}
2. Intents 意圖
2.1 Android 中有顯式 Intent 和 隱式 Intent ,作為 Activity 之間的導(dǎo)航儀狰晚,實(shí)現(xiàn)界面的跳轉(zhuǎn)。但 Flutter 沒有與之相對(duì)應(yīng)的概念缴啡,由于 Flutter 沒有很多的 Activities 和 Fragments 壁晒,需要實(shí)現(xiàn) Activity 間的通信。因此业栅,在 Flutter 中實(shí)現(xiàn)界面的跳轉(zhuǎn)等操作是通過(guò) Navigator 和 Routes秒咐,全都在相同的一個(gè) Activity 中使用。
Navigator 和 Routes 的關(guān)系:
Route 是app 中頁(yè)面或者界面的抽象碘裕,Navigator 是管理 Route 的組件(管理機(jī)制類似棧)携取。
A Route is an abstraction for a “screen” or “page” of an app, and a Navigator is a widget that manages routes
2.2 實(shí)現(xiàn)界面的跳轉(zhuǎn)有幾種方式:
[1] Specify a Map of route names.
void main() {
runApp(MaterialApp(
home: MyAppHome(), // becomes the route named '/'
routes: <String, WidgetBuilder> {
'/a': (BuildContext context) => MyPage(title: 'page A'),
'/b': (BuildContext context) => MyPage(title: 'page B'),
'/c': (BuildContext context) => MyPage(title: 'page C'),
},
));
}
[2] Directly navigate to a route.
Navigator.of(context).pushNamed('/b');
[3] 使用插件 android_intent 0.3.0+2
這個(gè) 插件只支持 android 系統(tǒng),如果在 ios 中使用會(huì)閃退帮孔。
if (platform.isAndroid) {
AndroidIntent intent = AndroidIntent(
action: 'action_view',
data: 'https://play.google.com/store/apps/details?'
'id=com.google.android.apps.myapp',
arguments: {'authAccount': currentUserEmail},
);
await intent.launch();
}
[4] 對(duì)于一些常用的調(diào)用攝像頭或者打開文件夾等操作雷滋,需要建立一個(gè)原生的平臺(tái)去集成不撑。具體參考
3. Async UI 異步更新UI (runOnUiThread)
3.1 與 Android 的區(qū)別:
Dart 是一個(gè)單線程執(zhí)行的模型,但也支持 Isolates (一種使 Dart 代碼運(yùn)行在另外一個(gè)線程的方式)晤斩,也支持 Event Loop (事件循環(huán)) 和 asynchronous programming(異步)焕檬。Dart 的機(jī)制是除非你開啟了一個(gè)Isolates,否則代碼由 Event Loop 驅(qū)動(dòng)運(yùn)行在主線程澳泵。Dart 的 event loop 相當(dāng)于 android 的 main looper 实愚。
不同于 android 需要一直保持主線程空閑的狀態(tài),F(xiàn)lutter 使用 Dart 提供的異步機(jī)制兔辅,例如 async / await 去完成異步任務(wù)腊敲。
3.2 具體用法
[1] 使用 async / await 去做一些重量級(jí)的操作,例如網(wǎng)絡(luò)請(qǐng)求等维苔。
[2] 網(wǎng)絡(luò)請(qǐng)求完成后兔仰,通過(guò)調(diào)用設(shè)置狀態(tài)更新 UI,觸發(fā)一個(gè)重建部件的子樹并更新數(shù)據(jù)蕉鸳。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
}));
}
Widget getRow(int i) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row ${widgets[i]["title"]}")
);
}
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
3.3 如何移動(dòng)任務(wù)到后臺(tái)進(jìn)程
在 Android 中為了避免阻塞主線程和 ANRs乎赴,你可能會(huì)使用 AsyncTask、LiveData潮尝、IntentService榕吼、JobScheduler job、RxJava 管道這些執(zhí)行在后臺(tái)線程的調(diào)度程序去完成訪問(wèn)網(wǎng)絡(luò)資源工作勉失。
由于 Flutter 是單線程模型和運(yùn)行一個(gè)事件循環(huán)機(jī)制羹蚣,因此作為開發(fā)人員,不需要擔(dān)憂線程管理或者產(chǎn)生大量的后臺(tái)線程乱凿。如果你要執(zhí)行 IO 范圍內(nèi)的任務(wù)顽素,例如磁盤訪問(wèn)或網(wǎng)絡(luò)請(qǐng)求,那你可以用 async/await 徒蟆。除此之外胁出,如果你需要完成密集型計(jì)算的工作讓 CPU 一直處于busy 的狀態(tài),那么你可以把這個(gè)任務(wù)移到 Isolate 去避免阻塞事件循環(huán)段审。
Isolates are separate execution threads that do not share any memory with the main execution memory heap. This means you can’t access variables from the main thread, or update your UI by calling setState(). Unlike Android threads, Isolates are true to their name, and cannot share memory (in the form of static fields, for example).
具體示例代碼:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:isolate';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
showLoadingDialog() {
if (widgets.length == 0) {
return true;
}
return false;
}
getBody() {
if (showLoadingDialog()) {
return getProgressDialog();
} else {
return getListView();
}
}
getProgressDialog() {
return Center(child: CircularProgressIndicator());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: getBody());
}
ListView getListView() => ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
});
Widget getRow(int i) {
return Padding(padding: EdgeInsets.all(10.0), child: Text("Row ${widgets[i]["title"]}"));
}
loadData() async {
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(dataLoader, receivePort.sendPort);
// The 'echo' isolate sends its SendPort as the first message
SendPort sendPort = await receivePort.first;
List msg = await sendReceive(sendPort, "https://jsonplaceholder.typicode.com/posts");
setState(() {
widgets = msg;
});
}
// the entry point for the isolate
static dataLoader(SendPort sendPort) async {
// Open the ReceivePort for incoming messages.
ReceivePort port = ReceivePort();
// Notify any other isolates what port this isolate listens to.
sendPort.send(port.sendPort);
await for (var msg in port) {
String data = msg[0];
SendPort replyTo = msg[1];
String dataURL = data;
http.Response response = await http.get(dataURL);
// Lots of JSON to parse
replyTo.send(json.decode(response.body));
}
}
Future sendReceive(SendPort port, msg) {
ReceivePort response = ReceivePort();
port.send([msg, response.sendPort]);
return response.first;
}
}
3.4 小主全蝶,在 Flutter 可以使用 OkHttp 嗎?如何使用呢?
事實(shí)上寺枉, Flutter 有自己的網(wǎng)絡(luò)請(qǐng)求框架 http(等同于 android 的 OkHttp )抑淫。
[1] pubspec.yaml
dependencies:
...
http: ^0.11.3+16
[2] http.get()
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
[...]
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
3.5 小主,那如何添加進(jìn)度條呢姥闪?
Android 中有 ProgressBar 始苇, Flutter 中有 ProgressIndicator。
showLoadingDialog() {
return widgets.length == 0;
}
getBody() {
if (showLoadingDialog()) {
return getProgressDialog();
}
}
getProgressDialog() {
return Center(child: CircularProgressIndicator());
}
4. Project structure & resources 項(xiàng)目結(jié)構(gòu)&資源
新創(chuàng)建的 Flutter Project 的結(jié)構(gòu)是如下圖的筐喳。
對(duì)比 Android Project 的結(jié)構(gòu):
差異1: 小主催式,沒有 res 資源包函喉,那我的圖片等資源文件應(yīng)該放在哪里?怎么調(diào)用呢蓄氧?
Android 會(huì)對(duì)資源(resources)和資產(chǎn)(assets)進(jìn)行分類函似,但在 Flutter 中只有assets 的概念。當(dāng)然喉童,也就沒有 dps 之分撇寞, 而是用像素區(qū)分。
怎么添加圖片堂氯?
[1] 首先在項(xiàng)目根目錄下創(chuàng)建 images 文件夾蔑担,
[2] 然后把圖片復(fù)制黏貼到 images 目錄下,
[3] 其次在 pubspec.yaml 咽白,引入 assets: - images/my_icon.jpeg啤握,
[4] 最后在代碼中調(diào)用 @override
Widget build(BuildContext context) {
return Image.asset("images/my_image.png");
}
區(qū)分不同像素的圖片:
images/my_icon.png // Base: 1.0x image
images/2.0x/my_icon.png // 2.0x image
images/3.0x/my_icon.png // 3.0x image
差異2 : 小主小主,還有一個(gè)問(wèn)題吶晶框, string.xml 的字符在 Flutter 中是怎么使用的呢排抬?
Flutter 沒有等同于 Android 的 string.xml , 因此對(duì)于項(xiàng)目的常量字符串授段,你可以創(chuàng)建一個(gè) Strings 類蹲蒲,然后在代碼中調(diào)用。
1.Strings 類
class Strings {
static String welcomeMessage = "Hello world !";
}
2.代碼中調(diào)用
Text(Strings.welcomeMessage)
差異3: 小主小主侵贵,我前面看了你使用網(wǎng)絡(luò)請(qǐng)求框架届搁,那我以前用的視頻播放、Json 解析這些開源框架都不用了窍育,那我豈不是全部要手動(dòng)造輪子卡睦??漱抓?
是的表锻,趕緊造輪子去吧(偷笑),多開源幾個(gè)辽旋,為 Flutter 做貢獻(xiàn)(一臉認(rèn)真滴樣子)浩嫌!所有的 Flutter 開源項(xiàng)目 在這 Pub~
5. Activities and fragments 界面
5.1 Activities and fragments VS Widgets
我們都知道,在 Android 中补胚,Activity 扮演者用戶交互接口的角色,即用戶界面追迟。Fragment 是行為或者用戶界面的碎片溶其,用于模塊化代碼,組成復(fù)雜的界面敦间。在 Flutter 瓶逃,這兩個(gè)概念都集中到了 Widgets 中束铭。任何一切都是一個(gè)小部件(widget),不同的路由(Routes) 代表不同的頁(yè)面厢绝。
5.2 如何監(jiān)聽 Activities 的生命周期事件
在 Android 中契沫,你可以重寫 Activity 的方法去捕獲自身 Activity 的生命周期事件,或者在 Application 注冊(cè)一個(gè) ActivityLifecycleCallbacks昔汉。在 Flutter 中懈万,因?yàn)闆]有 Activity 的概念,因此此路不通靶病,提供的新方法是通過(guò) WidgetsBinding 觀察者和監(jiān)聽 didChangeAppLifecycleState() 變化事件会通。
可以觀察到的生命周期事件:
[1] inactive
不活躍的:處于不活躍狀態(tài)不能接收用戶輸入。這種情況是工作在 IOS 系統(tǒng)上時(shí)產(chǎn)生的娄周,Android 中沒有對(duì)應(yīng)的事件涕侈。
[2] paused
暫停中止的:程序?qū)τ脩舨豢梢姡豁憫?yīng)用戶操作煤辨,運(yùn)行在后臺(tái)裳涛,這相當(dāng)于 Android 的 onPause().
[3] resumed
重新建立,恢復(fù):應(yīng)用程序?qū)τ脩艨梢娭诒妫⑶铱梢越邮沼脩糨斎攵巳_@相當(dāng)于 Android 的 onResume().
[4] suspending
懸浮:應(yīng)用程序暫停片刻泻轰,相當(dāng)于 Android 的 onStop()技肩。這種情況不會(huì)在 IOS 系統(tǒng)產(chǎn)生。
import 'package:flutter/widgets.dart';
class LifecycleWatcher extends StatefulWidget {
@override
_LifecycleWatcherState createState() => _LifecycleWatcherState();
}
class _LifecycleWatcherState extends State<LifecycleWatcher> with WidgetsBindingObserver {
AppLifecycleState _lastLifecycleState;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
_lastLifecycleState = state;
});
}
@override
Widget build(BuildContext context) {
if (_lastLifecycleState == null)
return Text('This widget has not observed any lifecycle changes.', textDirection: TextDirection.ltr);
return Text('The most recent lifecycle state this widget observed was: $_lastLifecycleState.',
textDirection: TextDirection.ltr);
}
}
void main() {
runApp(Center(child: LifecycleWatcher()));
}
6. Layouts 布局
【1】 LinearLayout
水平線性布局 == Row widget(行)
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Row One'),
Text('Row Two'),
Text('Row Three'),
Text('Row Four'),
],
);
}
垂直線性布局 == Column widget(列)
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Column One'),
Text('Column Two'),
Text('Column Three'),
Text('Column Four'),
],
);
}
【2】 RelativeLayout
相對(duì)布局是根據(jù)組件與組件之間相對(duì)關(guān)系進(jìn)行排布浮声,這在 Flutter 中是幾乎沒有直接與之相對(duì)的實(shí)現(xiàn)方式虚婿,不過(guò)你可以通過(guò)組合 Row、Column 和 Stack 等小部件去實(shí)現(xiàn)泳挥,也可以為部件構(gòu)造函數(shù)指定規(guī)則去規(guī)定孩子部件與父類部件的相對(duì)關(guān)系然痊。
【3】 ScrollView
在 Flutter 中 a ListView widget 是包含 ScrollView 和 Android 中的 ListView.
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
Text('Row One'),
Text('Row Two'),
Text('Row Three'),
Text('Row Four'),
],
);
}
7. Gesture detection and touch event handling 手勢(shì)和觸屏事件處理
第一,如何處理手機(jī)屏幕轉(zhuǎn)動(dòng)的問(wèn)題屉符?
In AndroidManifest.xml
android:configChanges="orientation|screenSize"
第二剧浸,如何小部件的點(diǎn)擊事件?
情況1矗钟,小部件默認(rèn)有點(diǎn)擊事件唆香,例如 RaisedButton.
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
print("click");
},
child: Text("Button"));
}
情況2,小部件沒有自帶點(diǎn)擊點(diǎn)擊事件吨艇,在部件外層添加 GestureDetector 躬它,增加 onTap 方法。
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GestureDetector(
child: FlutterLogo(
size: 200.0,
),
onTap: () {
print("tap");
},
),
));
}
}
8. Listviews & adapters
在 Android 中使用 Listviews东涡,首先創(chuàng)建一個(gè) adapter 冯吓,然后將 adapter 傳入 listview 倘待,listview 的每一行與 adapter 返回的相對(duì)應(yīng)。然而组贺,你必須確保你有回收你的行凸舵,不然會(huì)出現(xiàn)很多的小故障和記憶問(wèn)題。
在 Flutter 中失尖,由于小部件是不可變模式啊奄,即靜態(tài)模式,要?jiǎng)討B(tài)添加數(shù)據(jù)填充雹仿,你需要增加一系列的小部件到 listview . 而 Flitter 負(fù)責(zé)保證滑動(dòng)的流暢性和速度增热。
同樣的,如果需要為 listview 添加點(diǎn)擊事件呢胧辽,應(yīng)該如何操作峻仇?
在小部件外層增加一個(gè) GestureDetector
。
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView(children: _getListData()),
);
}
_getListData() {
List<Widget> widgets = [];
for (int i = 0; i < 100; i++) {
widgets.add(GestureDetector(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row $i")),
onTap: () {
print('row tapped');
},
));
}
return widgets;
}
}
9. Working with text 文本框
[1] 設(shè)置字體
1. In the pubspec.yaml file
fonts:
- family: MyCustomFont
fonts:
- asset: fonts/MyCustomFont.ttf
- style: italic
2. assign the font to your Text widget
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(
child: Text(
'This is a custom font text',
style: TextStyle(fontFamily: 'MyCustomFont'),
),
),
);
}
[2] 設(shè)置風(fēng)格
* color
* decoration
* decorationColor
* decorationStyle
* fontFamily
* fontSize
* fontStyle
* fontWeight
* hashCode
* height
* inherit
* letterSpacing
* textBaseline
* wordSpacing
10. Form input 輸入框
[1] 增加提示 hint
body: Center(
child: TextField(
decoration: InputDecoration(hintText: "Please Input number"),
)
)
[2] 設(shè)置允許輸入字符類型
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
String _errorText;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(
child: TextField(
onSubmitted: (String text) {
setState(() {
if (!isEmail(text)) {
_errorText = 'Error: This is not an email';
} else {
_errorText = null;
}
});
},
decoration: InputDecoration(hintText: "This is a hint", errorText: _getErrorText()),
),
),
);
}
_getErrorText() {
return _errorText;
}
bool isEmail(String em) {
String emailRegexp =
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regExp = RegExp(emailRegexp);
return regExp.hasMatch(em);
}
}
11. Themes 主題
最鐘意的環(huán)節(jié)終于來(lái)了邑商,我真是一個(gè)有審美要求的程序媛摄咆!(隔壁穿著格子衫,牛仔褲的她投來(lái)鄙視的一眼??)
曾經(jīng)我為了讓我的 app 運(yùn)用 Material Design 風(fēng)格的組件人断,把所有與之發(fā)生沖突的依賴包換成了手造輪子或者找另外的開源替換吭从。
對(duì)于 Flutter ,開箱即用的各種 Material Design Style 小部件恶迈,先看看怎么用吧涩金。
在 Flutter 最上層部件聲明 theme ,就像這樣??
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
textSelectionColor: Colors.red
),
home: SampleAppPage(),
);
}
}
12. Databases and local storage 數(shù)據(jù)庫(kù)和本地存儲(chǔ)
[1] Shared Preferences
在 Android 中暇仲, 以 key-value 的形式把小規(guī)模的數(shù)據(jù)存入步做。
在 Flutter 中,需要加入 shared_preferences 0.5.3+ 插件奈附,它包括 Android 的 Shared Preferences 和 IOS 的 NSUserDefaults.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: Center(
child: RaisedButton(
onPressed: _incrementCounter,
child: Text('Increment Counter'),
),
),
),
),
);
}
_incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int counter = (prefs.getInt('counter') ?? 0) + 1;
print('Pressed $counter times.');
prefs.setInt('counter', counter);
}
[2] SQLite
同樣的全度,需要使用 sqflite 1.1.5 插件,具體用法參考 sqflite 1.1.5 .
13. 總結(jié)
文章內(nèi)容來(lái)自學(xué)習(xí) Flutter官網(wǎng) 的知識(shí)斥滤,僅做學(xué)習(xí)記錄的用處将鸵,不做商業(yè)用途。文中的中文是自己翻譯的佑颇,由于本人水平有限顶掉,故把部分經(jīng)典英文原文也貼出,免得誤人子弟挑胸,故若有不當(dāng)之處請(qǐng)指出一喘,謝謝!
作者:Emily CH
2019年6月7日