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

前言

上一篇咱們進行了Flutter的環境搭建。整個過程相比你們應該也很順利。在項目搭建完畢以後,建立項目的時候會有一個簡單的Demo供開發者去體驗。那麼這一篇文章主要要作的就是,簡單的介紹一下Flutter的一些簡單的使用、經常使用的widget以及dart的語法。由於我是一名iOS開發工程師,因此我還會類比一下iOS開發中控件,這樣方便咱們的吸取和記憶。這裏全部的代碼都是用Android Studio開發的,沒有用VSCode的緣由就是AS的兼容性要更加的強大一些。前端

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

建立並運行項目

首先咱們在建立項目以前,爲了確保環境是ok的,咱們在命令行敲ios

flutter doctor
複製代碼

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

薛立恆@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,就參考個人前一篇文章。github

這給打擊介紹兩種建立項目的方法,一種是命令行的形式,一種是手動。數組

一、命令行形式建立項目

  • 首先cd到你想要建立項目的路徑文件夾
cd /Users/xueliheng/Desktop/Flutter/FlutterDemo
複製代碼

接下來咱們運行一句命令bash

flutter create hello_flutter
複製代碼

那麼咱們就在相應的文件夾裏面能看到咱們建立的hello_flutter。這裏可能有一些人就要問了,爲何這裏建立工程的名字不用大寫?這裏就要說明一下,Flutter在建立項目的時候,是跟iOS的命名規則不同的,他們全部的文件夾,包括項目中建立的文件名都不是不能含有大寫字母的,若是含有大寫字母,會報下面的錯誤。網絡

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

接着咱們來認識一下建立的項目: app

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

  • androidios就分別是不一樣的兩個工程的工程文件,這個通常不會動,須要動的時候,大概也是的到項目混合開發的階段,到時候再說吧。
  • 而後lib文件就是裝.dart爲結尾的代碼文件的。這裏面也就是咱們須要寫的flutter工程源碼。
  • 而後test是自動化測試用的。
  • 這裏的pubspec.lockpubspec.yaml這兩個文件,你們能夠直接聯想成爲iOS裏面的podspecpodfile.lock文件,功能很相似。

接下來咱們進入到hello_flutter這個Demo裏面去:less

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的話就是退出。

二、手動形式建立項目

  • 打開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度灰、設置外邊距統一爲十、設置子視圖爲一張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

相關文章
相關標籤/搜索