Flutter 入門指北(Part 2)之基礎(chǔ)部件

該文已授權(quán)公眾號(hào) 「碼個(gè)蛋」,轉(zhuǎn)載請(qǐng)指明出處

上一節(jié)介紹了 Dart 的一些語法,以及配置環(huán)境的網(wǎng)址,這節(jié)我們就可以開始了解下 Flutter

主要包括 MaterialApp、Scaffold、Text、Image、Icon、Button 以及 AppBar 部分內(nèi)容,準(zhǔn)備出發(fā)~

Flutter runApp

新建 flutter 項(xiàng)目后,可以看到 lib 下的 main.dart 中 void main() => runApp(MyApp()); 這句就是程序的入口了。這里可以簡(jiǎn)單看下源碼

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..attachRootWidget(app)
    ..scheduleWarmUpFrame();
}

///....
static WidgetsBinding ensureInitialized() {
  if (WidgetsBinding.instance == null)
    WidgetsFlutterBinding();
  return WidgetsBinding.instance;
}

///....
void attachRootWidget(Widget rootWidget) {
  _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
    container: renderView,
    debugShortDescription: '[root]',
    child: rootWidget
  ).attachToRenderTree(buildOwner, renderViewElement);
}

首先會(huì)創(chuàng)建一個(gè) WidgetsBinding 單例對(duì)象,然后把傳入的 App 添加到 rootWidget 中,scheduleWarmUpFrame 方法比較長(zhǎng),這邊看下對(duì)該方法的注釋第一句就能了解方法的主要功能了

Schedule a frame to run as soon as possible

「安排框架盡快運(yùn)行起來」(原諒我這渣英語,只能看懂不會(huì)翻譯..大概就是「快速啟動(dòng)框架」的意思吧)

Flutter App

接著看下 MyApp 這個(gè)類,繼承自 StatelessWidget 并在 build 方法返回一個(gè) MaterialApp 實(shí)例,(偷偷講下,其實(shí)這邊還可以返回 CupertinoApp,這是一個(gè) iOS 風(fēng)格的 widget,基本上你看到部件帶 「Cupertino」的都是 iOS 風(fēng)格的 widget,這里先不講 iOS 風(fēng)格的部件,目前 flutter 對(duì) Cupertino 系列的 widget 支持不是很好,包括部件的廣度,多語言的支持等等方面都不是很友好,所以我們還是繼續(xù)看 MD 風(fēng)格的 Android 部件吧~),這里先看下 MaterialApp 的構(gòu)造函數(shù),介紹一些常用的參數(shù)

const MaterialApp({
    Key key,
    this.navigatorKey,
    this.home, // 主界面的內(nèi)容 widget
    this.routes = const <String, WidgetBuilder>{}, // 帶 router 和路由跳轉(zhuǎn)有關(guān)
    this.initialRoute,
    this.onGenerateRoute,
    this.onUnknownRoute,
    this.navigatorObservers = const <NavigatorObserver>[], 
    this.builder,
    this.title = '', // *類似標(biāo)題
    this.onGenerateTitle, // 主要用于多語言情況下,需要根據(jù)當(dāng)前語言替換 title,需要使用該值
    this.color, // 主題色,如果該值未設(shè)置,取 theme.primaryColor,未設(shè)置 theme 則取藍(lán)色
    this.theme, // App 的主題風(fēng)格,包括主題色,按鈕默認(rèn)顏色等等
    this.locale, // 帶 locale 的和多語言適配相關(guān)
    this.localizationsDelegates,
    this.localeListResolutionCallback,
    this.localeResolutionCallback,
    this.supportedLocales = const <Locale>[Locale('en', 'US')],
    this.debugShowMaterialGrid = false, 
    this.showPerformanceOverlay = false,
    this.checkerboardRasterCacheImages = false,
    this.checkerboardOffscreenLayers = false,
    this.showSemanticsDebugger = false,
    this.debugShowCheckedModeBanner = true, // debug 模式下,是否顯示 DEBUG 標(biāo)示橫幅
  })

MaterialApp 繼承自 StatefulWidget,它和 MyApp 所繼承的類 StatelessWidget,就是日常開發(fā)中,自定義部件通常繼承的抽象類了。

  1. StatelessWidget 是狀態(tài)不可變部件,通過其構(gòu)建的部件一般用來展示固定內(nèi)容,例如需要展示固定的功能按鈕列表,不需要根據(jù)不同界面狀態(tài)進(jìn)行修改其展示內(nèi)容
  2. StatefulWidget 是可改變狀態(tài)的部件,比如我們需要通過網(wǎng)絡(luò)或者數(shù)據(jù)庫獲取數(shù)據(jù),然后修改部件鎖展示的數(shù)據(jù)內(nèi)容,則需要通過 StatefulWidget 來構(gòu)建。當(dāng)然,不是說 StatelessWidget 不能實(shí)現(xiàn)修改界面數(shù)據(jù)的功能,這就需要涉及到 狀態(tài)管理 的概念了,后面有機(jī)會(huì)再講,這邊先埋坑【坑1】

Flutter Scaffold

進(jìn)入 App 后就需要構(gòu)建界面了,Flutter 提供了 Scaffold 來快速構(gòu)建一個(gè) MaterialDesign 風(fēng)格的界面,還是先看下 Scaffold 的構(gòu)造函數(shù)吧,了解幾個(gè)比較常用的部分。

const Scaffold({
    Key key,
    this.appBar, // 界面頂部的那條欄,這邊需要返回一個(gè) AppBar 實(shí)例
    this.body, // 界面的內(nèi)容部分
    this.floatingActionButton, // 懸浮部分,可以通過 floatingActionButtonLocation 設(shè)置位置
    this.floatingActionButtonLocation,
    this.floatingActionButtonAnimator,
    this.persistentFooterButtons,
    this.drawer, // 側(cè)滑抽屜部分,從左側(cè)滑出(應(yīng)該和語言有關(guān),和文字方向同向)
    this.endDrawer, // 側(cè)滑抽屜部分,從右側(cè)滑出
    this.bottomNavigationBar, // 底部導(dǎo)航欄,就是通??吹降牡撞?TAB 切換部件
    this.bottomSheet, // 展示從底部彈出的,起到提示作用的,通過 showModalBottomSheet 展示
    this.backgroundColor, // 界面的背景色
    this.resizeToAvoidBottomPadding = true, // 避免 body 被底部彈出部件填充,例如輸入法鍵盤
    this.primary = true, // 當(dāng)前的 Scaffold 是否需要被展示在屏幕最上層
  })

來張圖吧,簡(jiǎn)潔明了

scaffold分布.png

了解完 Scaffold 的整體構(gòu)造后,我們從上到下,通過構(gòu)造函數(shù)來了解下各個(gè) Widget 的使用方法

AppBar
AppBar({
    Key key,
    this.leading, // 用于設(shè)置 AppBar 前置的按鈕,例如設(shè)置返回我們需要的返回按鈕等
    this.automaticallyImplyLeading = true, // 是否使用系統(tǒng)默認(rèn)生成的按鈕,如果替換 leading 的默認(rèn)按鈕,最好將該屬性設(shè)置成 false
    this.title, // AppBar 所需要展示的組件,傳入一個(gè) Widget 實(shí)例,通常使用 Text 展示一個(gè)標(biāo)題
    this.actions, // AppBar 末尾懸浮的一些操作組件,例如常見的會(huì)在末尾設(shè)置一個(gè)「...」按鈕,點(diǎn)擊彈出一個(gè) menue 提供給用戶操作選擇
    this.flexibleSpace, // AppBar 的背景,可以設(shè)置顏色,背景圖等等 
    this.bottom, // bottom 用于展示頂部導(dǎo)航 TAB
    this.elevation = 4.0,
    this.backgroundColor, // AppBar 的背景色,如果只需要修改顏色,可以不通過 flexibleSpace 修改
    this.brightness,
    this.iconTheme, // 按鈕的默認(rèn)樣式
    this.textTheme, // 文字的默認(rèn)樣式
    this.primary = true,
    this.centerTitle, // 是否將展示的 title 居中
    this.titleSpacing = NavigationToolbar.kMiddleSpacing, // AppBar title 兩側(cè)的空白間隔
    this.toolbarOpacity = 1.0,
    this.bottomOpacity = 1.0,
  })

在展示 AppBardemo 之前,我們先學(xué)習(xí)幾個(gè)基本的組件 TextImage、Icon、Button 分布用于展示文字,圖片,圖標(biāo),按鈕

Text
const Text(this.data, { // Text 需要展示的文字
    Key key,
    this.style, // 文字的樣式,包括顏色,大小,間距等等屬性,這邊就不繼續(xù)展示 TextStyle 構(gòu)造函數(shù)了,不然我怕大家都不想繼續(xù)看了,稍后通過例子來說明
    this.textAlign, // 文字的對(duì)齊方式,包括左對(duì)齊,右對(duì)齊,居中等,詳見 TextAlign 類
    this.textDirection, // 文字方向,ltr(left to right) 或者 rtl(right to left)
    this.locale, 
    this.softWrap, // 當(dāng)文字一行顯示不完是否換行
    this.overflow, // 如果超出限制的行數(shù),以哪種方式省略未展示的內(nèi)容
    this.textScaleFactor, // 文字縮放比例
    this.maxLines, // 最多展示的行數(shù)
    this.semanticsLabel,
  })

說了那么多,相信很多小伙伴都要急著擼代碼了吧,接著來展示一些 Text 的示例,接下來的例子都會(huì)直接替換 HomePage 內(nèi)的展示內(nèi)容,其余都是相同的,接下來請(qǐng)關(guān)注 Text 別的部件先忽略,后面會(huì)介紹,這邊先埋坑【坑2】

import 'package:flutter/material.dart';

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

class DemoApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(primarySwatch: Colors.lightBlue),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(),
        body: Container(
          padding: const EdgeInsets.only(top: 10.0),
          child: Center(
              child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('綠色背景黑色文字展示',
                  style: TextStyle(
                      color: Colors.black, // 設(shè)置文字顏色,不可和 foreground 同時(shí)設(shè)置
                      fontSize: 24.0, // 字體大小
                      letterSpacing: 2.0, // 每個(gè)字符之間的間隔
                      background: Paint()..color = Colors.green)), // 背景色
              Text('這是一個(gè)帶紅色下劃線的文字展示',
                  style: TextStyle(
                      color: Colors.black,
                      fontSize: 24.0,
                      // 文字裝飾線,除了 underline 還有 overline, lineThrough,
                      // 不同的樣式小伙伴可以通過自己修改代碼來查看
                      decoration: TextDecoration.underline,
                      // 文字裝飾線的類型,除了 solid 還有 double,dotted,dashed,wavy 可選
                      decorationStyle: TextDecorationStyle.solid,
                      // 裝飾線的顏色
                      decorationColor: Colors.red))
            ],
          )),
        ));
  }
}

該部分代碼查看源碼 text_main.dart 文件

最后的展示效果如下圖:

text 展示.png
Image

/壞笑 按照慣例,我們還是先看下 Image 的構(gòu)造函數(shù)吧

const Image({
    Key key,
    // 一個(gè) ImageProvider 實(shí)例,但是 ImageProvider 是一個(gè)抽象類,F(xiàn)lutter 已經(jīng)給我們提供如下
    // AssetImage,NetworkImage,F(xiàn)ileImage,MemoryImage 這四種圖片加載器,為了方便調(diào)用
    // 我們可以直接通過 Image.asset, Image.network, Image.file, Image.memory 簡(jiǎn)化,
    // 通過方法名,可以看出分別從 asset 文件,網(wǎng)絡(luò),文件,內(nèi)存中加載圖片
    @required this.image, 
    this.semanticLabel,
    this.excludeFromSemantics = false,
    this.width, // 圖片寬度
    this.height, // 圖片高度
    this.color, // 圖片背景色
    this.colorBlendMode, // color 和圖片的混合模式(這個(gè)值比較多,可以一個(gè)個(gè)嘗試)
    this.fit, // 圖片填充方式 fill, cover, contain, fillWidth, fillHeight, scaleDown, none
    this.alignment = Alignment.center, // 對(duì)齊方式
    this.repeat = ImageRepeat.noRepeat, // 若未填充滿空間,重復(fù)展示的方式
    this.centerSlice,
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
    this.filterQuality = FilterQuality.low,
  })

好了好了,我知道你們又想自己寫代碼嘗試下了,在這之前,需要你先準(zhǔn)備一張本地圖片,然后在項(xiàng)目的根目錄,也就是 lib 文件夾同層,創(chuàng)建一個(gè)新的文件夾,命名為 images,把你準(zhǔn)備好的圖片放到這個(gè)目錄下。放好之后打開 pubspec.yaml 把圖片資源文件注冊(cè)下

# The following section is specific to Flutter.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # 這邊注冊(cè)資源文件,以后有圖片文件也可以只注冊(cè) images 文件夾,會(huì)自動(dòng)讀取內(nèi)部的文件
  assets:
    - images/ali.jpg

注冊(cè)完成后,就可以繼續(xù)愉快的擼代碼了~

class HomePage extends StatelessWidget {
  final String _assetAli = 'images/ali.jpg';
  final String _picUrl =
      'https://timg05.bdimg.com/timg?wapbaike&quality=60&size=b1440_952&cut_x=143&cut_y=0&cut_w=1633&'
      'cut_h=1080&sec=1349839550&di=cbbc175a45ccec5482ce2cff09a3ae34&'
      'src=http://imgsrc.baidu.com/baike/pic/item/4afbfbedab64034f104872baa7c379310b551d80.jpg';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(),
        body: Container(
          padding: const EdgeInsets.only(top: 10.0),
          child: Center(
              child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              // 這種展示圖片方式和下一種會(huì)有相同的效果
              Image(image: AssetImage(_assetAli), width: 80.0, height: 80.0),
              // 接下來加載圖片都會(huì)使用這些比較方便的方法
              Image.asset(_assetAli, width: 80.0, height: 80.0),
              // 加載一張網(wǎng)絡(luò)圖片
              Image.network(_picUrl,
                  height: 80.0,
                  // 橫向重復(fù)
                  repeat: ImageRepeat.repeatX,
                  // MediaQuery.of(context).size 獲取到的為上層容器的寬高
                  width: MediaQuery.of(context).size.width),
              // 通過設(shè)置混合模式,可以看到圖片展示的樣式已經(jīng)修改
              Image.asset(_assetAli,
                  width: 80.0, height: 80.0, color: Colors.green, colorBlendMode: BlendMode.colorDodge),
              // 會(huì)優(yōu)先加載指定的 asset 圖片,然后等網(wǎng)絡(luò)圖片讀取成功后加載網(wǎng)絡(luò)圖片,會(huì)通過漸隱漸現(xiàn)方式展現(xiàn)
              // cover 方式按照較小的邊布滿,較大的給切割
              // contain 會(huì)按照最大的邊布滿,較小的會(huì)被留白
              // fill 會(huì)把較大的一邊壓縮
              // fitHeight, fitWidth 分別按照長(zhǎng)寬來布滿
              FadeInImage.assetNetwork(
                  placeholder: _assetAli, image: _picUrl, width: 120.0, height: 120.0, fit: BoxFit.cover),
              // Icon 相對(duì)屬性少了很多,需要傳入一個(gè) IconData 實(shí)例,flutter 提供了很多圖標(biāo),
              // 但是實(shí)際情況我們需要加入我們自己的圖標(biāo),這邊再埋坑【坑3】
              // size 為圖標(biāo)顯示的大小,color 為圖標(biāo)的顏色,這邊通過 Theme 獲取主題色調(diào)
              Icon(Icons.android, size: 40.0, color: Theme.of(context).primaryColorDark)
            ],
          )),
        ));
  }
}

該部分代碼查看源碼 image_main.dart 文件

最后的效果如下:

Image 展示.png
Button

Flutter 提供了各種類型的 Button 幾乎是大同小異的,這邊就抽取一些比較常用的展示下效果,常用的主要有 RaisedButton 、FlatButton、IconButtonOutlineButton、MaterialButton、FloatActionButton、FloatingActionButton.extended

Button 都有一個(gè) onPress 參數(shù),是 VoidCallback 類型的參數(shù),通過查看源碼可以知道 VoidCallback 是無參無返回值的一種類型參數(shù)。如果該參數(shù)傳入的值為 null 那么這個(gè)按鈕的就不可點(diǎn)擊狀態(tài),無點(diǎn)擊效果,等會(huì)可以在例子中查看。還有就是 child 參數(shù),這里就是傳入你需要展示的內(nèi)容,比如 Text、Icon 等等。別的參數(shù)基本可以通過參數(shù)名了解,這邊不擴(kuò)展了(再看源碼我怕你們都不想繼續(xù)看下去了...)

Talk is cheap, show me the code

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        padding: const EdgeInsets.only(top: 10.0),
        child: Center(
            child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              onPressed: () {
                print('This is a Rased Button can be clicked');
              },
              child: Text('Raised Enable'),
            ),
            RaisedButton(onPressed: null, child: Text('Raised Disable')),
            FlatButton(
              onPressed: () => print('This is a Flat Button can be clicker'),
              child: Text('Flat Enable'),
            ),
            FlatButton(onPressed: null, child: Text('Flat Disable')),
            IconButton(icon: Icon(Icons.android), onPressed: () {}),
            IconButton(icon: Icon(Icons.android), onPressed: null),
            MaterialButton(onPressed: () {}, child: Text('Material Enable')),
            MaterialButton(onPressed: null, child: Text('Material Disable')),
            OutlineButton(onPressed: () {}, child: Text('Outline Enable')),
            OutlineButton(onPressed: null, child: Text('Outline Enable')),
          ],
        )),
      ),
      floatingActionButton:
          FloatingActionButton.extended(onPressed: () {}, icon: Icon(Icons.android), label: Text('Android')),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }
}

該部分代碼查看源碼 button_main.dart 部分

最終的效果圖:

button 展示.png

這篇終于到末尾了,最后留了 3 個(gè)坑等以后解決

最后代碼的地址還是要的:

  1. 文章中涉及的代碼:demos

  2. 基于郭神 cool weather 接口的一個(gè)項(xiàng)目,實(shí)現(xiàn) BLoC 模式,實(shí)現(xiàn)狀態(tài)管理:flutter_weather

  3. 一個(gè)課程(當(dāng)時(shí)買了想看下代碼規(guī)范的,代碼更新會(huì)比較慢,雖然是跟著課上的一些寫代碼,但是還是做了自己的修改,很多地方看著不舒服,然后就改成自己的實(shí)現(xiàn)方式了):flutter_shop

如果對(duì)你有幫助的話,記得給個(gè) Star,先謝過,你的認(rèn)可就是支持我繼續(xù)寫下去的動(dòng)力~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容