Flutter 學習筆記:再次重新學習Flutter

gclove2000發表於2024-05-21

目錄
  • 前言
  • 相關連結
  • 環境安裝
    • 常見問題
    • 環境編譯成功
  • 分析專案
    • 程式入口
    • 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 配置起來也比較的麻煩。至少不影響執行,還能接受。

相關文章