- 前言
- 相關連結
- 環境安裝
- 常見問題
- 環境編譯成功
- 分析專案
- 程式入口
- Wiget
- 動態更新
- 按照教程初始化專案
- 弱化Flutter編譯檢查
- 新增第一個按鈕
- 快速巢狀元件化
- 資料更新:ChangeNotifier和StatefulWidget
- Dart中的委託
- 修改好的程式碼
- 執行效果
- 總結
前言
作為一個開發了一年多的Uniapp的.NET 開發工程師,我打算去用FLutter完整的寫一個小的程式的Demo。經過兩年的程式設計開發學習,我作為一個普通的程式設計師,有我的技術選型的思考。
- 必須主流,不一定是最受歡迎的,但是也是市場佔比比較大的。比如前端的React,Vue2,Vue3。我認為你三個選擇哪一個都可以,你喜歡就行。跨平臺目前的主流技術是Uniapp,React Native和Flutter,我個人選擇了Flutter。
- 生態一定要很完善,Uniapp的生態就很差,雖然背靠Web生態,但是Uniapp的Web和普通的Web還是有區別的。而且Uniapp的社群很差,本身就是相容國內的小程式生態的。
- 選擇簡單易上手的方式,能用元件就用元件,能快速實現就快速實現。保持“不求甚解”的態度,先不用知道底層是怎麼寫的,程式碼能跑,需求能滿足就行。等你很瞭解,很熟練了之久,再去研究也不遲。
- 能真實的用到業務中。移動端開發的需求應該挺常見的,打算Flutter熟練使用之後,後面的開發都用Flutter了
相關連結
Flutter中文文件:https://docs.flutter.cn/get-started/install
構建您的第一個 Flutter 應用:https://codelabs.developers.google.com/codelabs/flutter-codelab-first?hl=zh-cn#8
Flutter 開發學習筆記(0):環境配置:https://blog.csdn.net/qq_44695769/article/details/137133411?spm=1001.2014.3001.5501
Flutter 開發學習筆記(1):第一個簡單的Flutter專案(上):https://blog.csdn.net/qq_44695769/article/details/137176032?spm=1001.2014.3001.5501
Flutter 開發學習筆記(2):第一個簡單的Flutter專案(下):https://blog.csdn.net/qq_44695769/article/details/137186682?spm=1001.2014.3001.5501
Flutter 開發學習筆記(3):第三方UI庫的引入:https://blog.csdn.net/qq_44695769/article/details/137266619?spm=1001.2014.3001.5501
環境安裝
因為好久沒寫Flutter了,打算從新開始再走一遍官方的新手教程
常見問題
Flutter新建專案執行報錯Exception in thread “main” java.net.ConnectException: Connection timed out: connect:https://www.cnblogs.com/chorkiu/p/14767567.html
Flutter執行第一個專案時出現javax.net.ssl.SSLHandshakeException的一些解決思路:https://blog.csdn.net/fwhdzh/article/details/106632072
Flutter卡在Running ‘gradle assembleDebug‘最完整解決:https://blog.csdn.net/qq_43596067/article/details/107710915
Flutter編譯卡在Running Gradle task ‘assembleDebug‘:https://blog.csdn.net/shiyangkai/article/details/124632441?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171169554516800215013010%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=171169554516800215013010&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-124632441-null-null.142%5Ev100%5Econtrol&utm_term=Flutter%20Running%20Gradle%20task%20assembleDebug...&spm=1018.2226.3001.4187
執行新建Flutter專案, 報錯Exception in thread “main“ java.net.ConnectException: Connection timed out: connect:https://blog.csdn.net/a18339063397/article/details/125506390?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171169533516800225518971%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=171169533516800225518971&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-125506390-null-null.142%5Ev100%5Econtrol&utm_term=Flutter%20Exception%20in%20thread%20main%20java.net.ConnectException%3A%20Connection%20refused%3A%20connect&spm=1018.2226.3001.4187
Android studio配置Flutter開發環境報錯問題解決:https://blog.csdn.net/lu202032/article/details/134421156?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171169787916800215067985%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=171169787916800215067985&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~times_rank-2-134421156-null-null.142%5Ev100%5Econtrol&utm_term=flutter%20jdk17&spm=1018.2226.3001.4187
環境編譯成功
Flutter 開發學習筆記(0):環境配置:https://blog.csdn.net/qq_44695769/article/details/137133411?spm=1001.2014.3001.5501
跟著我這個文章走下去,把裡面的地址替換成國內的映象地址,SDK換成本地SDK,編譯一遍透過就OK了。
專案啟動頁面
分析專案
程式入口
Flutter的程式入口是一個 main函式,在預設的示例裡面,指向的是一個MyApp。Flutter的程式編寫,更像是面向元件化的程式邏輯編寫,Flutter為什麼稱之為巢狀地獄,因為他特別喜歡用元件化的思想
Wiget
在Flutter中,所有的元件都是繼承Widget的,就像網頁的Div一樣,在Flutter中,一切皆widget
在Flutter中寫程式碼,感覺就像寫無限細分的Div元件一樣
<A>
</B>
</A>
...
<B>
</C>
</B>
...
<C>
</D>
</C>
...
<D>
</F>
</D>
動態更新
在Widget中,分為無狀態Widget和有狀態Widget。簡單來說,動態Widget中,會給你一個修改變數的鉤子,有點像React,讓你去修改Widget的中顯示的引數,其實就是一個setState的鉤子,這個和React或者Vue3的函數語言程式設計的感覺很像
深度理解Flutter:有狀態Widget與無狀態Widget的詳細對比:https://blog.csdn.net/HaiJun_Aion/article/details/135341129
如果你寫過WPF,就知道WPF裡面有一個依賴屬性。在Flutter裡面的狀態和這個依賴屬性差不多。狀態就是Flutter的共享記憶體空間,無狀態不代表Widget不可更改,而是Widget不維護記憶體空間,可以透過有狀態的父元件更新廣播,讓無狀態的子元件更新。
比如我們修改一下預設的啟動專案
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
// Text(
// '${_counter}',
// style: Theme.of(context).textTheme.headlineMedium,
// ),
TextWidget(text: '無狀態Widget [${_counter}]')
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class TextWidget extends StatelessWidget {
const TextWidget({Key? key, required this.text}) : super(key: key);
final String text;
@override
Widget build(BuildContext context) {
return Text(
text,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 40,
color: Colors.red,
),
);
}
}
如果不清楚該怎麼選擇,那就能用無狀態Widget,就用無狀態Widget
按照教程初始化專案
構建您的第一個 Flutter 應用,建立專案:https://codelabs.developers.google.com/codelabs/flutter-codelab-first?hl=zh-cn#2
相關的配置我就不展開了,大家進去看看好了。
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
return Scaffold(
body: Column(
children: [
Text('A random idea:'),
Text(appState.current.asLowerCase),
],
),
);
}
}
弱化Flutter編譯檢查
在analysis_options.yaml
中進行替換
include: package:flutter_lints/flutter.yaml
linter:
rules:
prefer_const_constructors: false
prefer_final_fields: false
use_key_in_widget_constructors: false
prefer_const_literals_to_create_immutables: false
prefer_const_constructors_in_immutables: false
avoid_print: false
新增第一個按鈕
注意,我這裡的程式碼可能和教程的有點區別,有部分更新
return Scaffold(
body: Column(
children: [
const Text('A random idea:'),
Text(appState.current.asLowerCase),
ElevatedButton(
onPressed: () {
debugPrint('Hello Flutter!');
},
child: const Text("Click me"))
],
),
);
快速巢狀元件化
由於我之前已經寫過一次新手教程了,我後面就挑我覺得重要的講了
有時候快捷鍵會卡住,得切換成英文輸入法才行
資料更新:ChangeNotifier和StatefulWidget
用C# 的思想,ChangeNotifier其實就是全域性變數。全域性變數是成為屎山的重要前提,能不用全域性變數就不用全域性變數。資料流的流通應該是元件和元件之間互相引用的。反正就是能用StatefulWidget就不要用ChangeNotifier。
Dart中的委託
C# 最重要的三大設計,委託,Task和Linq。委託更是重中之重。委託其實就是指標的封裝。
Dart中的委託,和C# 中的不太一樣
final Function getNext;//Dart
Action getNext;//C#
final bool Function(string msg) isFavoriate;//Dart
Function<bool,string> isFavoriate;//C#
修改好的程式碼
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => null,
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
),
home: MyHomePage(),
),
);
}
}
void showMsg(String msg) {
debugPrint("[main]: ${msg}");
}
class FavoriateModel {
var current = WordPair.random();
///喜歡列表
var favorites = <WordPair>[];
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _selectedIndex = 0;
FavoriateModel favorate = new FavoriateModel();
void getNext() {
setState(() {
favorate.current = WordPair.random();
});
}
/// 新增喜歡
void toggleFavorite() {
setState(() {
if (favorate.favorites.contains(favorate.current)) {
favorate.favorites.remove(favorate.current);
showMsg("remove => ${favorate.current}");
} else {
favorate.favorites.add(favorate.current);
showMsg("add => ${favorate.current}");
}
});
}
bool isFavoriate (WordPair str) {
return this.favorate.favorites.contains(str);
}
@override
Widget build(BuildContext context) {
Widget _page;
switch (_selectedIndex) {
case 0:
_page = GeneratorPage(
appState: this.favorate,
toggleFavorite: this.toggleFavorite,
getNext: this.getNext,
isFavoriate: this.isFavoriate,
);
break;
case 1:
_page = FavoraitePage(
appState: favorate,
);
break;
default:
throw UnimplementedError("no widget in ${_selectedIndex}");
}
return LayoutBuilder(builder: (context, constraints) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: NavigationRail(
extended: constraints.maxWidth >= 600,
destinations: const [
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.favorite),
label: Text('Favorites'),
),
],
selectedIndex: _selectedIndex,
onDestinationSelected: (value) {
showMsg('selected: $value');
setState(() {
_selectedIndex = value;
});
},
),
),
Expanded(
child: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: _page,
),
),
],
),
);
});
}
}
class FavoraitePage extends StatelessWidget {
const FavoraitePage({super.key, required this.appState});
final FavoriateModel appState;
@override
Widget build(BuildContext context) {
var _message = <String>[];
appState.favorites.forEach((item) {
_message.add(item.toString());
});
_message.insert(0, "message");
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: _message.map((e) => Text(e)).toList(),
),
);
}
}
class GeneratorPage extends StatelessWidget {
const GeneratorPage(
{super.key,
required this.appState,
required this.toggleFavorite,
required this.getNext,
required this.isFavoriate});
final Function toggleFavorite;
final Function getNext;
final bool Function(WordPair msg) isFavoriate;
final FavoriateModel appState;
@override
Widget build(BuildContext context) {
var pair = appState.current;
IconData icon;
if (this.isFavoriate(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
const SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () {
toggleFavorite();
},
icon: Icon(icon),
label: const Text('Like'),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () {
getNext();
},
child: const Text('Next'),
),
],
),
],
),
);
}
}
class BigCard extends StatelessWidget {
const BigCard({
super.key,
required this.pair,
});
final WordPair pair;
@override
Widget build(BuildContext context) {
final _theme = Theme.of(context); //獲取容器的樣式
return Center(
child: Card(
color: _theme.colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(pair.asLowerCase),
),
),
);
}
}
執行效果
總結
還是不習慣使用Dart,語法上來說還可以,就是編輯器比較噁心。在Anriod Studio 裡面,縮排是不能改的,預設2格。而且程式碼提示很垃圾,不說像Visual studio裡面一樣的智慧提示了,連'switch','required'這種關鍵詞有時候都提示不出來,怪不得Visual studio 用的人多。但是我又懶得配置VS Code,畢竟VS code 配置起來也比較的麻煩。至少不影響執行,還能接受。