Flutter 與 Dart 語法初探 什麼是flutter 環境搭建(MAC環境) Dart 語法 Flutter 最後 About me

什麼是flutter

  • google 推出的跨平臺UI框架

環境搭建(MAC環境)

  • Flutter 依賴下面這些命令行工具
bash, mkdir, rm, git, curl, unzip, which

獲取Flutter SDK

  • flutter官網下載其最新可用的安裝包

  • 安裝包下載完成則可以進行解壓

unzip /指定解壓目錄/flutter_macos_v1.9.1+hotfix.4-stable.zip
  • 解壓完成之後我們會在解壓好的目錄下會多出一個flutter目錄,獲取並記住該目錄路徑,下一步我們會用到
# 使用pwd 命令查看目錄路徑
/Users/XXXXX/development/flutter

設置環境變量

  • 設置環境變量目的是以便我們可以運行flutter命令在任何終端會話中

  • 確定Flutter SDK的目錄,上一步我們解壓獲取了flutter的路徑/Users/XXXXX/development/flutter

  • 打開(或創建) HOME/.bash_profile. 文件路徑和文件名可能在您的機器上不同(注意HOME 指的是 路徑是 /Users/用戶名XX/ )

vim $HOME/.bash_profile
  • 文件加入環境變量
export PUB_HOSTED_URL=https://pub.flutter-io.cn //國內用戶需要設置
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn //國內用戶需要設置
//
export PATH=/Users/XXXXX/development/flutter/bin:$PATH
  • 最後我們執行創建好的.bash_profile文件
source $HOME/.bash_profile
注意: 如果你使用的是zsh,終端啓動時 ~/.bash_profile 將不會被加載,解決辦法就是修改 ~/.zshrc ,在其中添加:source ~/.bash_profile

執行 flutter docter 檢查本機軟件環境,沒安裝的插件根據提示安裝就行了

flutter doctor

手動升級 Flutter

  • flutter 版本升級迭代很快,前面我們下載SDK默認是stable分支,也就是穩定版,如何手動升級呢?只需要下面一條flutter命令
flutter upgrade

Dart 語法

  • 我們知道Flutter框架是使用Dart 語言來編寫的,Dart 是一個面向對象編程語言, 每個對象都是一個類的實例,所有的類都繼承於 Object.如果熟悉Java,語言是很容易上手的。首先來熟悉一下Dart語法

Dart 變量

  • var 聲明變量,和 kt 、js語法很像,需要注意的是如下示例 name 只要複製字符串,則他就是String類型,number就是int 類型,不能再更改它的類型,而沒有初始化的變量自動獲取一個默認值爲 null。
var name = 'maoqitian';
var number = 1;

Dart 常量

  • final 和 const聲明都是表示常量,一個 final 變量只能賦值一次,可以省略變量類型,如下聲明一個存放WordPair值的List 數組
final List _suggestions = new List<WordPair>();

final _suggestions = <WordPair>[];

  • const 關鍵字不僅僅只用來定義常量, 也可以用來創建不變的值
//如下定義一個字體大小的值一直都是 18 ,不會改變
final _biggerFont = const TextStyle(fontSize: 18.0)

final 和 const區別

  • const 的值在編譯期確定,final 的值要到運行時才確定

Dart 函數方法

  • Dart 是一個真正的面嚮對象語言,方法也是對象他的類型是 Function。 這意味着,方法可以賦值給變量,也可以當做其他方法的參數。
//定義一個返回 bool(布爾)類型的方法 
bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}
//轉換如下可以忽略類型定義
isNoble(atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

//只有一個表達式的方法,你可以選擇 使用縮寫語法來定義
// => expr 語法是 { return expr; } 形式的縮寫
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

Dart 方法參數

  • 方法可以定義兩種類型的參數:必需的和可選的。 必需的參數在參數列表前面, 後面是可選參數,必選參數沒啥好說的,我們來了解可選參數。可選參數可以是自己命名參數或者基於可選位置的參數,但是這兩種參數不能同時當做可選參數來一起用。

可選命名參數

  • 調用可選命名參數方法的時候可以使用 paramName: value (key:value形式,只不是過key 是參數名稱)來指定參數值
//調用有可選命名參數方法 playGames
playGames(bold: true, hidden: false);

//playGames 方法
playGames({bool bold, bool hidden}) {
  // ...
}

可選位置參數

  • 方法參數列表中用[]修飾的參數就是可選位置參數
// 定義可選位置參數方法
String playGames (String from, String msg, [String sports]) {
  var result = '$from suggest $msg';
  if (sports != null) {
    result = '$result playing $sports together';
  }
  return result;
}
 
// 不用可選參數 
playGames('Bob', 'Howdy'); // 返回值 Bob suggest Howdy

//使用可選參數
playGames('I', 'Xiao Ming', 'basketball'); //返回值 I suggest Xiao Ming playing basketball together.

Dart 方法參數默認值

  • 在定義方法的時候,可以使用 = 來定義可選參數的默認值。 默認值只能是編譯時常量。 如果沒有提供默認值,則默認值爲 null
// 定義可選位置參數方法
String playGames (String from , String msg, [String sports = 'football']) {
  var result = '$from suggest $msg';
  if (sports != null) {
    result = '$result playing $sports together';
  }
  return result;
}

playGames('I', 'Xiao Ming'); //返回值 I suggest Xiao Ming playing football together.

入口函數(The main() function)

  • 每個應用都需要有個頂級的 main() 入口方法才能執行
// Android studio 創建Demo 項目  main.dart 文件開頭 
void main() => runApp(MyApp());

//可以轉換爲
void main(){
    runApp(MyApp());
}

異步操作

  • async 方法和 await 異步操作,直接看看一個網絡請求例子就能夠了解
static Future<ArticleListData> getArticleData(int pageNum) async{
    String path = '/article/list/$pageNum/json';
    Response response = await HttpUtils.get(Api.BASE_URL+path);
    ArticleBaseData articleBaseData = ArticleBaseData.fromJson(response.data);
    return articleBaseData.data;
  }

先了解這麼多,更多Dart 相關內容可以查看Dart語言官網

Flutter

官方文檔

Flutter Hello world

  • 在開始Flutter Hello world程序之前,作爲一名Android 開發者,首先我們要認識到Flutter中沒有原生開發的XML,所有界面和邏輯代碼都在.dart文件中,Flutter給我提供了一套視覺、結構、平臺、和交互式的Widgets,所以在Flutter中一構架的一切界面都是Widgets。接下來我們先看一個簡單的Hello World Flutter應用。

  • Android Studio 新建Flutter demo

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new MaterialApp(
      theme: ThemeData(
        primaryColor: Colors.blueAccent,
      ),
      home: new Scaffold(
        appBar: new AppBar(
            title: new Center(
              child: new Text("Welcome to Flutter"),
            )
        ),
        body: DemoStatelessWidget("Flutter Hello World ! 無狀態的Widget"), 
      ),
    );
  }

}

//無狀態 Widget
class DemoStatelessWidget extends StatelessWidget{

  final String text;

  //構造方法傳入 text 值
  DemoStatelessWidget(this.text);
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Container(
      constraints: BoxConstraints.expand(
        height: Theme.of(context).textTheme.display1.fontSize * 1.1 + 200.0,
      ),
      padding: const EdgeInsets.all(8.0),
      color: Colors.blue[600],
      alignment: Alignment.center,
      child: Text(text,
          style: Theme.of(context)
              .textTheme
              .display1
              .copyWith(color: Colors.white)),
      transform: Matrix4.rotationZ(0.1),
    );
  }
}
  • 運行結果
  • 如上代碼所示,首先main函數運行MyApp,MyApp 是一個DemoStatelessWidget,字面可以理解爲一個沒有狀態的Widget,他的build 方法創建了MaterialApp這個Widget才使得應用可以跑在手機上,接着創建了Scaffold 這個Widget,可以讓我們創建AppBar和頁面內容,body 的頁面內容又包含了一個無狀態的DemoStatelessWidget,通過構造方法可以傳入我們想要現實的頁面內容,該widget包含白色背景和一個現實文字的Text widget。

Flutter ListView

  • 上個小例子中我們提到無狀態 StatelessWidget,想必也能猜到,肯定會有一個有狀態的widget,這個widget就是StatefulWidget,該widget爲何說是有狀態的呢,主要是在其管理的State中我們可以調用setState來動態改變頁面顯示。接着我們看一個顯示數據列表的例子,並加入一個可以點擊收藏的按鈕。
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

void main() => runApp(MyApp());

// StatelessWidget 無狀態的widget
class MyApp extends StatelessWidget { //Stateless widgets是不可變的, 這意味着它們的屬性不能改變 - 所有的值都是最終的.
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      //title: 'Welcome to Flutter',
      theme: ThemeData(
        primaryColor: Colors.blueAccent,
      ),
     home: new RandomWords(),
    );
  }
}

//StatefulWidget 有狀態的widget
class RandomWords extends StatefulWidget{
  @override
  createState() => new RandomWordsState();
}

// 返回 顯示單詞對的ListView Widget
class RandomWordsState extends State<RandomWords> {
  //保存建議的單詞對列表(變量以下劃線(_)開頭,在Dart語言中使用下劃線前綴標識符,會強制其變成私有的)  final _suggestions = <WordPair>[];
  final List _suggestions = new List<WordPair>();
  //設置字體大小的變量
  final _biggerFont = const TextStyle(fontSize: 18.0);
  // 保存喜歡單詞組的集合 set 集合不允許值重複
  final Set _saved = new Set<WordPair>();

  /// State 生命週期方法
  @override
  void initState() {
    // state 初始化
    super.initState();
  }

  @override
  void didChangeDependencies() {
    // 在 initState 之後調用,此時可以獲取其他 State
    super.didChangeDependencies();
  }

  @override
  void dispose() {
    // state 銷燬
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    //return new Text(new WordPair.random().asPascalCase);
    //返回單詞對的ListView。
    return new Scaffold(
      appBar: new AppBar(
        title:new Center( //居中顯示
          child: new Text('Flutter ListView'),
        ),
      ),
      body: _buildSuggestions(),
    );
  }
  
  //構建顯示建議單詞對的ListView。
  Widget _buildSuggestions(){
    return new ListView.builder(
        padding: const EdgeInsets.all(16.0),
        // 對於每個建議的單詞對都會調用一次itemBuilder,然後將單詞對添加到ListTile行中
        // 在偶數行,該函數會爲單詞對添加一個ListTile row.
        // 在奇數行,該函數會添加一個分割線widget,來分隔相鄰的詞對。

        //itemBuilder 值是一個匿名回調函數, 接受兩個參數- BuildContext和行迭代器i。迭代器從0開始,
        // 每調用一次該函數,i就會自增1,對於每個建議的單詞對都會執行一次。該模型允許建議的單詞對列表在用戶滾動時無限增長。
          itemBuilder: (context,i){
          // 在每一列之前,添加一個1像素高的分隔線widget
          if(i.isOdd) return new Divider();
          // 語法 "i ~/ 2" 表示i除以2,但返回值是整形(向下取整),比如i爲:1, 2, 3, 4, 5
          // 時,結果爲0, 1, 1, 2, 2, 這可以計算出ListView中減去分隔線後的實際單詞對數量
          final index = i~/2;
          
          if(index >= _suggestions.length){
            //  如果是建議單詞列表中最後一個單詞對 接着再生成10個單詞對,然後添加到建議列表
            _suggestions.addAll(generateWordPairs().take(10));
          }

          return _buildRow(_suggestions[index]);
        }
    );
  }
  //創建 ListTile中顯示每個新詞對
  Widget _buildRow(WordPair suggestion) {
    //獲取是否保存了該單詞狀態
    final isSaved = _saved.contains(suggestion);

    return new ListTile(
      // 設置 標題
      title: new Text(
        suggestion.asPascalCase,
        style: _biggerFont,
      ),
      //圖標
      trailing: new Icon(
        //星型圖標狀態
        isSaved ? Icons.favorite : Icons.favorite_border,
        color: isSaved ? Colors.deepOrange : null ,
      ),
      onTap: (){  // 當用戶點擊 ListTile 擊時, ListTile 會調用它的onTap回調
        setState(() { //調用setState() 會爲State對象觸發build()方法,從而導致對UI的更新
          if(isSaved){
             _saved.remove(suggestion);
          }else{
            _saved.add(suggestion);
          }
        });
      },
    );
  }
}
  • 運行效果
  • 如上代碼,將原來的無狀態Widget改成了StatefulWidget,並在build中構建ListView,到此你可能有疑惑,不是說有狀態的Widget,怎麼還是創建Widget,有狀態如何體現呢? 別急,我們看到_buildRow方法,方法中構建了ListTile 這個Widget,它響應點擊事件回到爲onTap方法,也就是當我們點擊ListTile,我們在onTap方法中就可以調用setState方法來動態改變頁面顯示,也就是改變桃心收藏按鈕變化(注意setState方法需要在State類中才能調用)。

  • State 是有周期的,其中包括三個函數:

  1. initState():初始化方法
  2. didChangeDependencies():在 initState 之後調用,此時可以獲取其他 State
  3. dispose():state 銷燬
  • 如果你使用Android Studio 我們可以使用快捷鍵快速創建StatelessWidget和StatefulWidget,創建StatelessWidget快捷鍵爲stl,創建StatefulWidget快捷鍵爲stf

小節總結

  • Flutter 頁面都是由一個個 widget 搭建而來的
  • widget類型有兩種,一種是無狀態頁面內容固定的StatelessWidget,一種是頁面內容可以動態改變的 StatefulWidget

Widget 速覽

  • 在開始開發實戰之前,我們需要對基本常用的Widget有個大概的認識。

layout Widget

  • 在剛開始寫原生界面的時候,我們最先了解的也應該是佈局,Flutter 也提供了不少佈局widget,接下來列舉一些常用的佈局widget
佈局名稱 特點描述
Container 擁有單個子元素的佈局widget,可以靈活設置
Padding 擁有單個子元素,給其子widget添加指定的填充
Center 將其子widget居中顯示
Align 將其子widget對齊,並可以根據子widget的大小自動調整大小。
Row 可以擁有多個子元素,在水平方向上排列子widget的列表,和原生控件 LinerLayout orientation="horizontal" 類似
Column 可以擁有多個子元素,在豎直方向上排列子widget的列表,和原生控件 LinerLayout orientation="vertical" 類似
Stack 可以擁有多個子元素,允許其子widget簡單的堆疊在一起
Flow 實現流式佈局算法的widget
ListView 可滾動的列表控件

界面 Widget

  • 有了佈局,還需要在佈局中填放各種控件,才最終組成我們的頁面,比如我們開發Material Design 風格的App,Flutter 就給我們提供了Material Components Widgets,接下來選取一些常用的控件來了解。瞭解更多的widget請參考 Material Components Widgets 目錄
Widget名稱 特點描述
MaterialApp 封裝了應用程序實現Material Design所需要的一些widget,由前面demo可以發現它一般爲應用頂層入口widget
Scaffold Material Design佈局結構的基本實現。此類提供了用於顯示drawer、snackbar和底部sheet的API。
Appbar 一般和Scaffold結合使用,可以設置頁面標題和各種按鈕等(Toolbar)
BottomNavigationBar 底部導航條,可以很容易地在tap之間切換和瀏覽頂級視圖
Drawer 和Scaffold結合使用,從Scaffold邊緣水平滑動以顯示應用程序中導航鏈接的Material Design面板
RaisedButton Material Design中的button,響應點擊事件(button)
IconButton 一個Material圖標按鈕,可以設置icon,點擊時會有水波動畫
TextField 文本輸入框 (EditText)
image 顯示圖片的widget(ImageView)
Text 單一格式的文本 (TextView)

導航欄返回按鈕監聽

  • WillPopScope ,Flutter中可以通過WillPopScope來實現返回按鈕攔截
  • 以下示例提供兩種效果,雙擊返回Toast提示(Toast庫 fluttertoast: ^3.1.3),或者彈出提示dialog是否退出。
import 'package:flutter/material.dart';

class AppPage extends StatefulWidget {
  @override
  _AppPageState createState() => _AppPageState();
}

class _AppPageState extends State<AppPage> {
    @override
  Widget build(BuildContext context) {
    return WillPopScope( ///通過WillPopScope 嵌套,可以用於監聽處理 Android 返回鍵的邏輯。 WillPopScope 並不是監聽返回按鍵,只是當前頁面將要被pop時觸發的回調
        child: Container(),
        onWillPop: () async{
           return _doubleExitApp();
        }
    );
  }
  
  //雙擊返回 退出應用
  bool _doubleExitApp(){
    if (_lastPressedAt == null ||
        DateTime.now().difference(_lastPressedAt) > Duration(seconds: 1)) {
      ToolUtils.ShowToast(msg: "再點一次退出應用");
      //兩次點擊間隔超過1秒則重新計時
      _lastPressedAt = DateTime.now();
      return false;
    }
    //應用關閉直接取消 Toast
    Fluttertoast.cancel();
    return true;
}


  ///如果返回 return new Future.value(false); popped 就不會被處理
  ///如果返回 return new Future.value(true); popped 就會觸發
  ///這裏可以通過 showDialog 彈出確定框,在返回時通過 Navigator.of(context).pop(true);決定是否退出
  /// 單擊提示退出
  Future<bool> _dialogExitApp(BuildContext context) {
    return showDialog(
        context: context,
        builder: (context) => new AlertDialog(
          content: new Text("是否退出"),
          actions: <Widget>[
            new FlatButton(onPressed: () => Navigator.of(context).pop(false), child:  new Text("取消")),
            new FlatButton(
                onPressed: () {
                  Navigator.of(context).pop(true);
                },
                child: new Text("確定"))
          ],
        ));
  }
  
}

最後

  • 萬事開頭難,對於Flutter到此可以說是邁出了第一步,對於未嘗試的東西,開始可能會有畏懼心理,但只要敢於嘗試,敢於邁出第一步,相信難不難只在於自己的用心程度而言。本篇文章就先到這裏,文章中如果有錯誤,請大家給我提出來,大家一起學習進步,如果覺得我的文章給予你幫助,也請給我一個喜歡和關注,同時也歡迎訪問我的個人博客
  • Flutter完整開源項目: https://github.com/maoqitian/flutter_wanandroid

參考

About me

blog:

mail:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章