帶你一步一步探索Flutter(一)-- Flutter初體驗以及認識常用的Widget

CoderSpr1ngHall發表於2019-06-30

前言

上一篇我們進行了Flutter的環境搭建。整個過程相比大家應該也很順利。在專案搭建完畢之後,建立專案的時候會有一個簡單的Demo供開發者去體驗。那麼這一篇文章主要要做的就是,簡單的介紹一下Flutter的一些簡單的使用、常用的widget以及dart的語法。因為我是一名iOS開發工程師,所以我還會類比一下iOS開發中控制元件,這樣方便我們的吸收和記憶。這裡所有的程式碼都是用Android Studio開發的,沒有用VSCode的原因就是AS的相容性要更加的強大一些。

下面Demo的地址:github.com/Spr1ngHall/…

建立並執行專案

首先我們在建立專案之前,為了確保環境是ok的,我們在命令列敲

flutter doctor
複製程式碼

用來檢察一下環境是否配置ok。

 薛立恆@xuelihengdeMacBookPro  ~/Desktop  flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, v1.5.4-hotfix.2, on Mac OS X 10.14.5 18F132, locale
    zh-Hans-CN)

[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[✓] iOS toolchain - develop for iOS devices (Xcode 10.2.1)
[✓] Android Studio (version 3.4)
[✓] VS Code (version 1.35.1)
[✓] Connected device (1 available)
複製程式碼

介面顯示這個列印資訊才能說明你的所有環境配置都是ok的,如果不ok,就參考我的前一篇文章。

這給打擊介紹兩種建立專案的方法,一種是命令列的形式,一種是手動。

1、命令列形式建立專案

  • 首先cd到你想要建立專案的路徑資料夾
cd /Users/xueliheng/Desktop/Flutter/FlutterDemo
複製程式碼

接下來我們執行一句命令

flutter create hello_flutter
複製程式碼

那麼我們就在相應的資料夾裡面能看到我們建立的hello_flutter。這裡可能有一些人就要問了,為什麼這裡建立工程的名字不用大寫?這裡就要說明一下,Flutter在建立專案的時候,是跟iOS的命名規則不一樣的,他們所有的資料夾,包括專案中建立的檔名都不是不能含有大寫字母的,如果含有大寫字母,會報下面的錯誤。

螢幕快照 2019-06-30 13.23.21.png

接著我們來認識一下建立的專案:

螢幕快照 2019-06-30 13.36.30.png

  • androidios就分別是不同的兩個工程的工程檔案,這個一般不會動,需要動的時候,大概也是的到專案混合開發的階段,到時候再說吧。
  • 然後lib檔案就是裝.dart為結尾的程式碼檔案的。這裡面也就是我們需要寫的flutter工程原始碼。
  • 然後test是自動化測試用的。
  • 這裡的pubspec.lockpubspec.yaml這兩個檔案,大家可以直接聯想成為iOS裡面的podspecpodfile.lock檔案,功能很類似。

接下來我們進入到hello_flutter這個Demo裡面去:

cd hello_flutter
複製程式碼

繼續敲:

flutter run
複製程式碼

那麼專案就會自動開啟。這裡還是要說明一下,如果你同時開啟了兩個模擬器,那麼這個時候敲擊上面這個命令的時候,flutter會報一個錯誤,會讓你選擇一個具體的模擬器去執行專案。同時也把模擬器的資訊都列印出來了,這個時候你只需要選擇一個執行就行了:

flutter run -d 'iPhone Xʀ' 
複製程式碼

或者是執行到所有的模擬器上面:

flutter run -d all
複製程式碼

我們再執行專案之後,模擬器的列印如下:

 薛立恆@xuelihengdeMacBookPro  ~/Desktop/Flutter/FlutterDemo/hello_flutter  flutter run
Launching lib/main.dart on iPhone Xʀ in debug mode...
Running Xcode build...
 ├─Assembling Flutter resources...                           1.3s
 └─Compiling, linking and signing...                         3.7s
Xcode build done.                                            6.4s
Syncing files to device iPhone Xʀ...                             1,650ms

?  To hot reload changes while running, press "r". To hot restart (and rebuild
state), press "R".
An Observatory debugger and profiler on iPhone Xʀ is available at:
http://127.0.0.1:62574/dpTTmbSopM8=/
For a more detailed help message, press "h". To detach, press "d"; to quit,
press "q".
複製程式碼

這裡有一個提示,讓你敲r或者R,其實意思就是如果你需要重新build一下專案,就輸入R,如果你敲r的意思就是,啟動熱過載。熱過載是flutter的特色功能,能在不build專案的同時也能看到模擬器上面的東西在變動,所見即所得,這個真是iOS開發的一個福音啊!按q的話就是退出。

2、手動形式建立專案

  • 開啟Android Studio會發現這裡多了一個選項
    螢幕快照 2019-06-30 13.58.09.png

這裡會有下面幾個選項

螢幕快照 2019-06-30 14.06.47.png

分別來介紹一下選項吧

  • Flutter Application:顧名思義就是建立一個Flutter的專案,這裡不用說肯定選他;
  • Flutter Plugin:如果說你開發出來的專案既要用到iOS原生也要用到Android原生,那麼這個時候你就要選擇給他們開發一個外掛;
  • Flutter Package:如果說你開發出來的專案是隻給Dart語言使用的,那麼這個專案就可以建立一個package,其實plugin和package都差不太多,只是建立不同模式而已。
  • Flutter Module:這個是混合開發的時候會用到的,這裡先不講,後面研究研究在來說。 點選第一個選項,然後一步步的填寫專案名稱就ok了,這裡太簡單就不贅述了。

但是這裡是有一個坑的,如果你在建立專案的時候,如果選擇了一箇中文路徑的話,AS會報錯,這裡AS是不支援在中文路徑下面建立專案的,如果你非要在中文路徑下面建立專案,就只能用第一種命令列的形式去建立專案了。

編寫專案

介紹了這麼多前戲,終於來到正題了。刪掉main.dart裡面所有的程式碼。我們重頭開始。

首先引入基礎元件庫:

import 'package:flutter/material.dart';
複製程式碼

我們可以看作就是UIKit

建立main函式:

void main() {
  runApp(Center(
    child: Text(
      'Hello',
      textDirection: TextDirection.ltr,
    ),
  ));
}
複製程式碼

這個main函式跟iOS中的main函式其實是一個道理,runApp就相當於UIApplicationCenter裡面的意思就是,其中的child元件按照居中對齊的方式排列。child當然就好理解了,就是iOS中的subView的意思。所以就可以得出,runApp後面的這一段程式碼,其實就是在設定一個根控制器。然後設定一下Text元件的一些屬性。

什麼是Widget

講到這裡,我們都知道了又一個child是指的subView的意思,那麼UIView是什麼呢?

那麼這裡,我們就要說到WidgetWidget翻譯過來就是小部件的意思。我們可以理解為一個小控制元件。就像一個UIView一樣。 然後Widget分為兩種。一種是Stateful(有狀態的),一種是Stateless(無狀態的)。他們分別有什麼用呢?無狀態的就表示這個Widget建立出來是什麼樣子就是什麼樣子,狀態是不可改變的。相反,有狀態的其實也是一個特殊的無狀態的Widget,但是這個Widget帶有一個狀態類,去標識這個widget的一些狀態。有狀態的Widget在渲染的時候,也是渲染成了一個無狀態的Widget。

建立一個Widget類

我們現在建立一個MyWidget類,也就是一個widget控制元件,:

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return null;
  }
}
複製程式碼

這裡重寫了一個build方法,這個方法是幹嘛的呢? 實際上,這個方法就是將你現在自定義的這個小控制元件放到控制元件的渲染的樹中去。這個return返回的是什麼,那麼這個控制元件就是什麼。他會從你的main函式中的runApp中的第一個控制元件去渲染,然後逐步的去渲染裡面內部的控制元件。 (**tips:**這裡建立的時候跟前面建立檔名是不一樣的,這裡建立類名是需要運用駝峰命名法的,並且首字母是大寫。這裡注意區分一下。) 那麼我們現在可以把runApp中的Text控制元件換成我們的自己自定義的控制元件了,但是前提是我們要重寫一下build方法。

void main() {
  runApp(Center(
    child: MyWidget(),
  ));
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Center(
      child: Text(
        'Hello Flutter',
        textDirection: TextDirection.ltr,
      ),
    );
  }
}
複製程式碼

當然,其實我們也可以在main方法中去自定義一個function,然後function返回的是一個元件,這種方式也是可以的,程式碼如下:

  Widget func () {
    return Text('Hello');
  }
複製程式碼

但是我個人覺得,如果說是比較複雜的控制元件的話,還是定義一個類去封裝控制元件比較好,因為可以把控制元件分裝到不同的檔案中,供別人使用。

**tips:**這裡我們發現MyWidget方法返回的也是一個Center (),那麼我們其實是可以把runApp中的Center方法省略掉。並且如果一個方法裡面,只有一句程式碼,dart語言是可以簡寫成如下的:

void main() => runApp(MyWidget());
複製程式碼

這個是不是很熟悉,這就是我們剛開始建立專案時,預設的工程裡面,main函式的程式碼就是這樣寫的。這個在JS ES6裡面好像也有。前端同學估計會熟悉一些。 我們點到Text裡面去看原始碼的時候,能看到如下簡化程式碼:

class Text extends StatelessWidget {
  const Text(
    this.data, {
    Key key,
    this.style,
    this.strutStyle,
    this.textAlign,
    this.textDirection,
    this.locale,
    this.softWrap,
    this.overflow,
    this.textScaleFactor,
    this.maxLines,
    this.semanticsLabel,
  }) : assert(
         data != null,
         'A non-null String must be provided to a Text widget.',
       ),
       textSpan = null,
       super(key: key);
  const Text.rich(
    this.textSpan, {
    Key key,
    this.style,
    this.strutStyle,
    this.textAlign,
    this.textDirection,
    this.locale,
    this.softWrap,
    this.overflow,
    this.textScaleFactor,
    this.maxLines,
    this.semanticsLabel,
  }) : assert(
         textSpan != null,
         'A non-null TextSpan must be provided to a Text.rich widget.',
       ),
       data = null,
       super(key: key);
  final String data;
  final TextSpan textSpan;
  final TextStyle style;
  final StrutStyle strutStyle;
  final TextAlign textAlign;
  final TextDirection textDirection;
  final Locale locale;
  final bool softWrap;
  ...
複製程式碼

this後面的很好理解,就是這個類的可選引數,那麼下面的final定義的是什麼呢?也好理解,就是屬性唄。 為什麼用final定義呢? 原因是Text是一個Stateless的Widget,那麼建立出來之後就是固定了的,屬性也是同樣的道理。那麼這裡就肯定是final修飾,而不是var修飾。這個final其實可以類比Swift或者JS裡的let。 接下來,我們建立一個_textStyle物件,去設定一些我們需要設定的Style:

final _textStyle = TextStyle(
      color: Colors.red,
      fontSize: 40.0,
    );
複製程式碼

然後把這個_textStyle賦值給Text裡面的style

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final _textStyle = TextStyle(
      color: Colors.red,
      fontSize: 40.0,
    );
    return Center(
      child: Text(
        'Hello Flutter',
        textDirection: TextDirection.ltr,
        style: _textStyle,
      ),
    );
  }
}
複製程式碼

這種方式同樣只是一種技巧,可以把Style裡面的東西提取出來。這就跟CSS有一些類似了。

認識MaterialApp

這一次直接上程式碼吧

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

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Demo'),
        ),
        body: MyWidget(),
      ),
      theme: ThemeData(
        primaryColor: Colors.yellow,
      ),
    );
  }
}
...
複製程式碼

執行結果是這樣的:

螢幕快照 2019-06-30 17.49.15.png

這裡MaterialApp其實上就是Flutter封裝的一些便於我們去搭建APP的一系列元件。Scaffold實際上我們可以理解為UINavigationControllre。其中也包含了AppBar,也就是導航條,body就是實際顯示在手機中的內容。theme就是一些主題,可以讓我們自己去設定導航欄的顏色啊等等東西。這一點上來說比iOS確實是方便了很多。

建立一個Model

我們再建立一個名叫animal.dart的檔案,然後敲入如下程式碼:

class Animal {
//  建構函式
  const Animal({
    this.name,
    this.imageUrl,
  });

  final String name;
  final String imageUrl;
}
複製程式碼

這裡定義一個Animal的類,const Animal()就是建構函式,下面的final定義的都是屬性,在建構函式裡面賦值nameimageUrl。這就構成了一個Animal的模型。

建立資料來源

我們建立完模型之後,才應該建立一下資料來源。我們在animal.dart檔案中定義一下模型陣列:

//定義一個模型陣列
final List<Animal> datas = [
  Animal(
    name: '兔子',
    imageUrl:
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561905982563&di=c69bd273942564d09f5eb8ca4eaa1943&imgtype=0&src=http%3A%2F%2Fs15.sinaimg.cn%2Fmw690%2F00328H1Nzy74f5vBmKG8e%26690',
  ),
  Animal(
    name: '鴨子',
    imageUrl:
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561906404967&di=80e4b6c937176ff9a17bcd8bc377de28&imgtype=0&src=http%3A%2F%2Fphotocdn.sohu.com%2F20120305%2FImg336680797.jpg',
  ),
  Animal(
    name: '金錢豹',
    imageUrl:
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561906421196&di=ba764154104591d2f9da67c89d6fd36b&imgtype=0&src=http%3A%2F%2Fphotocdn.sohu.com%2F20130611%2FImg378599972.jpg',
  ),
  Animal(
    name: '獅子',
    imageUrl:
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561906438918&di=e2202a99c9931aa0d76a3e2de25e435b&imgtype=0&src=http%3A%2F%2Fhbimg.b0.upaiyun.com%2F615f13c5ff460d568c7b632846a2b04f00cf6509b47e-NhJ9FI_fw658',
  ),
  Animal(
    name: '老虎',
    imageUrl:
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561906452597&di=766795e10f0d9afc7d11c173f08aaf9c&imgtype=0&src=http%3A%2F%2Fimg18.3lian.com%2Fd%2Ffile%2F201710%2F09%2F02b420dddc4db52a75f7cbbaed83644b.jpg',
  ),
  Animal(
    name: '袋鼠',
    imageUrl:
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561906467233&di=c31bd84ae874f2ad767ca0a76287a8eb&imgtype=0&src=http%3A%2F%2Fimages.china.cn%2Fattachement%2Fjpg%2Fsite1000%2F20130319%2F001aa0ba5c7712b1f5005e.jpg',
  ),
  Animal(
    name: '大象',
    imageUrl:
        'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561906481939&di=16bf1c9ea3c78bc46dff56e30919da53&imgtype=0&src=http%3A%2F%2Fphotocdn.sohu.com%2F20130702%2FImg380495405.jpg',
  ),
  Animal(
    name: '公雞',
    imageUrl:
        'https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=3634424173,2840985996&fm=26&gp=0.jpg',
  ),
];
複製程式碼

按快捷鍵Option+return快速匯入Animal的標頭檔案。

建立ListView

我們建立一個新的類名叫Home,然後在App中將home中的Scaffold替換成新建的類。

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Home(),
      theme: ThemeData(
        primaryColor: Colors.yellow,
      ),
    );
  }
}

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
  ...
複製程式碼

接著我們重寫Homebuild方法,並且返回的是一個Scaffold,然後設定一下標題:

class Home extends StatelessWidget {
  Widget _cellForRow (BuildContext context, int index) {
    return Text('123');
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Demo'),
      ),
    );
  }
}
複製程式碼

這裡都不用多說。然後我們就需要設定我們的body了,程式碼如下:

Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Demo'),
      ),
      body: ListView.builder(
        itemCount: datas.length,
        itemBuilder: _cellForRow,
      ),
    );
  }
複製程式碼

這裡就是建立一個ListView,這裡的itemCount很明顯,就跟iOS中的numberOfRowsInSection方法是一個道理,意思就是這個ListView有多少行。itemBuilder很顯然也就是cellForRowAtIndexPath,既然iOS裡面我們用的是代理去實現的,這裡我們為了更加的貼心iOS,我們把這裡的實現抽離出來:

Widget _cellForRow (BuildContext context, int index) {
    return Text('123');
  }
複製程式碼

我們定義了一個_cellForRow的Widget,這個Widget返回的就是一個row所對應顯示的內容。 **tips:**這裡說明一下:

  • 我們在定義一個屬性的時候,如果加了字首“_”,就標識這是一個私有的。外面死不能使用的,如果沒有加,那麼說明外面是可以使用的。
  • ListView中是沒有section這個概念的,我們在需要需要分組的時候,必須得自己去一行行的實現了(這一點我覺得iOS做的要好很多,當然目前還不好說,後面慢慢來看)。

我們執行一下專案,就能看到如下的顯示

螢幕快照 2019-06-30 20.33.16.png

在Row中新增檢視

我們上面只在row裡面新增了一個Text,這在實際開發過程中是遠遠不夠的,那麼我們要怎麼去新增別的檢視在row中呢? 這裡就要用到Container了。話不多說,線上程式碼:

Widget _cellForRow(BuildContext context, int index) {
    return Container(
      color: Colors.grey[100],
      margin: EdgeInsets.all(10),
      child: Image.network(datas[index].imageUrl),
    );
  }
複製程式碼

這裡的Container就是指的容器,這一點上來說,我們可以類比前端的div,也可以類比iOS中的UIView。包括其中的佈局方式也跟FlexBox很類似,這一點我們再下一篇文章中會來針對性的講一下。Container裡面的實現就不用多說了,設定顏色為100度灰、設定外邊距統一為10、設定子檢視為一張Image並且是網路請求的,請求的url是從datas的陣列中取得。

這裡如果要多加一個Text到row怎麼辦呢?這裡也可以的:

Widget _cellForRow(BuildContext context, int index) {
    return Container(
      color: Colors.grey[100],
      margin: EdgeInsets.all(10),
      child: Column(//這裡還有Row可以Stack佈局
        children: <Widget>[
          Image.network(datas[index].imageUrl),
          Text(datas[index].name),
        ],
      ),
    );
  }
複製程式碼

我們把Image替換成一個children就行了,這個children裡面是一個Widget的陣列,那麼理論上我們就可以無限制的往裡面新增Widget了。並且誰最先執行,哪個控制元件就在最上面。

執行的結果是這樣的

螢幕快照 2019-06-30 21.15.49.png

除了Column佈局之外,還有Row佈局和Stack佈局,我們分別看看效果

螢幕快照 2019-06-30 21.18.46.png

上面是Row佈局的,其實就是橫向的從左至右的佈局方式,這裡圖片太長了,已經把文字都擠出去了。

螢幕快照 2019-06-30 21.19.03.png

上面是Stack佈局的,意思就是說把各個控制元件層疊起來擺放。

如果你現在要在文字和圖片之間弄一個間距,我們可以直接加一個SizeBox

SizedBox(
            height: 20,
          ),
複製程式碼

SizeBox也加入到children中去,並且加到文字個圖片之間,這樣文字跟圖片之間就會有間距了。

child: Column(
        //這裡還有Row可以Stack佈局
        children: <Widget>[
          Image.network(datas[index].imageUrl),
          SizedBox(
            height: 20,
          ),
          Text(
            datas[index].name,
            style: TextStyle(
              fontWeight: FontWeight.w800,
              fontSize: 18.0,
              fontStyle: FontStyle.values[1],
              color: Colors.blue,
            ),
          ),
          SizedBox(
            height: 20,
          ),
        ],
      ),
複製程式碼

那麼到這裡,我們的簡單的專案就算是完成了。接下來我們來簡單認識幾個常用的Widget。

常用Widget

Text

我們前面介紹了這個控制元件,那麼如果我們想要拼接字串怎麼弄呢?上程式碼:

class TextDemo extends StatelessWidget {
  final TextStyle _textStyle = TextStyle(
    fontSize: 16.0,

  );

  final String _title = '這是一個標題';
  final String _detail = '這是一個內容';

  @override
  Widget build(BuildContext context) {
    return Text(
      '《${_title}》-- $_detail。最近Flutter已經瘋狂的刷屏了各個技術部落格、技術網站,完全有一統天下的氣勢。所以最近也決定開始嚐嚐鮮,從零開始一步步的來探索Flutter的世界。就從環境搭建開始,記錄一下自己探索Flutter的過程。',
      textAlign: TextAlign.center,
      style: _textStyle,
    );
  }
}
複製程式碼

這裡我們看到這個標題是我們拼接到這個字串上面的,所以說拼接的語法就是:

$_title
//或者
${_title}
複製程式碼

Text中,我們除了可以設定textAlign以外,我們還可以設定maxLines,就是限制最大行數。設定了最大行數之後,如果字數超過了行數,接下來的是不顯示的,如圖所示

螢幕快照 2019-06-30 22.19.03.png

Text(
      '《${_title}》-- $_detail。最近Flutter已經瘋狂的刷屏了各個技術部落格、技術網站,完全有一統天下的氣勢。所以最近也決定開始嚐嚐鮮,從零開始一步步的來探索Flutter的世界。就從環境搭建開始,記錄一下自己探索Flutter的過程。',
      textAlign: TextAlign.center,
      style: _textStyle,
      maxLines: 3,
      overflow: TextOverflow.ellipsis,
    );
複製程式碼

螢幕快照 2019-06-30 22.22.24.png

後面就多了...的符號。

富文字

直接線上程式碼吧:

RichText(
      text: TextSpan(
        text: '<這是一個標題>',
        style: TextStyle(
          fontSize: 30,
          color: Colors.blue,
        ),
        children: <TextSpan>[
          TextSpan(
            text: 'xueliheng500@vip.qq.com',
            style: TextStyle(
              fontSize: 16,
              color: Colors.red,
            )
          ),
          TextSpan(
              text: '☺',
              style: TextStyle(
                fontSize: 16,
                color: Colors.red,
              )
          ),
          TextSpan(
              text: 'xueliheng500@vip.qq.com',
              style: TextStyle(
                fontSize: 16,
                color: Colors.red,
              )
          ),
        ],
      ),
    );
  }
複製程式碼

最後呈現的效果:

螢幕快照 2019-06-30 22.34.26.png

這裡可以總結一下:

  • 富文字用的Widget就是RichText
  • 我們需要新增富文字只需要新增children就可以了;
  • children是一個TextSpan的陣列;
  • 我們可以新增很多的TextSpan,並且自定義相應的TextSpan,來完成富文字的要求。

結語

今天我們從Flutter的基礎的main函式一直講到通過ListView去展示一些常用的介面,並且還介紹了一些常用的Widget,這當然不是全部,看完這個就以為自己已經完全搞懂Flutter的只能說太年輕了。後面我還會持續更新。預告一下,不出意外,下一篇應該會給大家介紹一些Flutter的佈局問題。上文中有提到,這裡先賣個關子。如果覺得文章對你有用,可以幫我點個贊!需要技術交流的,可以發郵件到我的郵箱:coderspr1nghall@gmail.com

相關文章