Flutter入門二: 熟悉Widget、文字樣式、ListView

Flutter入門 學習大綱

上一節,我們完成了Flutter的環境搭建。本節,我們開始搭建項目,簡單了解Flutter及其基礎組件。

  1. 項目創建
    1.1 命令行創建
    1.2 Android Studio創建
  2. 熟悉工程
    2.1 簡單實現
    2.2 熟悉widget
    2.3 Text
    2.4 MaterialApp
    2.5 ListView 列表視圖
  3. 常用基礎組件
    3.1 基礎文本
    3.2 富文本
    3.3 基礎容器Container

1. 項目創建

1.1 命令行創建

以前不支持 駝峰寫法,需要通過小寫字母+下劃線_命名,但是現在支持

flutter create flutter_demo
  • 創建成功
image.png
  • 按照指令,到指定文件夾運行項目
cd FlutterDemo
flutter run

注意:

  • 如果此時未打開模擬器,會提示需要選擇一個模擬器。
  • 如果此時打開了多個模擬器,也會提示您選中一個模擬器運行。
    image.png
  • 選中模擬器后,flutter會自動使用Xcode工具進行編譯
    image.png
  • 如果需要真機調試,我們需要手動打開項目工程,去Xcode配置證書

終端運行Flutter命令鍵

Flutter run key commands.
r Hot reload. ?????? 熱重載(比對被修改部分,更新被修改代碼)
R Hot restart.      熱重啟(所有資源重新加載)
h Repeat this help message.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
s Save a screenshot to flutter.png.
b Toggle the platform brightness setting (dark and light mode).                   (debugBrightnessOverride)
w Dump widget hierarchy to the console.                                                      (debugDumpApp)
t Dump rendering tree to the console.                                                 (debugDumpRenderTree)
L Dump layer tree to the console.                                                      (debugDumpLayerTree)
S Dump accessibility tree in traversal order.                                          (debugDumpSemantics)
U Dump accessibility tree in inverse hit test order.                                   (debugDumpSemantics)
i Toggle widget inspector.                                         (WidgetsApp.showWidgetInspectorOverride)
I Toggle oversized image inversion ???.                                        (debugInvertOversizedImages)
p Toggle the display of construction lines.                                         (debugPaintSizeEnabled)
o Simulate different operating systems.                                             (defaultTargetPlatform)
z Toggle elevation checker.
g Run source code generators.
M Write SkSL shaders to a unique file in the project directory.
v Launch DevTools.
P Toggle performance overlay.                                           (WidgetsApp.showPerformanceOverlay)
a Toggle timeline events for all widget build methods.
  • 如果使用安卓模擬器,可以在頂部控制欄配置模擬器
    image.png
  • 選中模擬器,運行:


    image.png
  • 由于個人習慣,我更喜歡Xcode開發,在終端使用命令運行。執行flutter run時,會讓我們選擇想使用的模擬器(后續我主要使用iPhone模擬器)。

1.2 Android Studio創建

  • 除了使用flutter create命令創建項目,我們還可使用Android Studio創建,也可以使用VSCode創建,它們都有flutter插件。
  • 啥? 你問Xcode是否可以創建?
    想想蘋果谷歌的競爭者關系,就知道蘋果不可能做這樣的支持插件的。??

1.2.1 使用Android Studio創建

image.png
  • 四種創建方式:
image.png
  • 項目基本信息

    image.png

  • 項目唯一標識支持平臺

    image.png

  • Finish后,會進行網絡請求拉取資源,創建成功。
    (如果沒配置鏡像,拉取資源非常緩慢。 上一節有介紹如何配置鏡像

    image.png

  • 坑點
    如果Android Studio 正在運行項目,我們command + Q強制退出。下次打開Android Studio時,會回到當時的緩存。如果緩存成功找回,會運行正常。如果緩存找不到,會導致無法運行,而且新建工程無法運行

  • 處理方式:
    刪除flutter目錄下cache緩存文件夾中的lockfile文件,再運行項目即可。(相當于XCodeClean操作)

# pwd請替換為自己flutter的文件目錄
rm /pwd/flutter/bin/cache/lockfile

2. 熟悉工程

  • 創建項目后,可以看到main.dart很多代碼。我們最快熟悉的方式是: 全部刪除手動實現分析
    image.png

2.1 簡單實現

Flutter中,我們使用的開發語言Dart,現在我們先體驗,完整的Dart語法,我們可以去官網了解

  • 手動實現:
// 以下代碼,均使用Dart語言編寫

// 資源包  (可以理解為我們iOS的UIKit)
import 'package:flutter/material.dart';

// main入口函數  (就像iOS main.m中的main函數)
void main() {
  // runApp(app); // runApp就像iOS的 UIApplication, 而app就像我們的根控制器
  // flutter中沒有控制器和視圖的概念,都是widget組件。
  runApp(
    Center(  //我們用Center部件,自動居中展示。這是Center的構造函數
      // child就像subView
      // Text是一種文本框樣式,設置默認值和顯示方向(ltf: left to right 從左到右)
      child: Text('Hello', textDirection: TextDirection.ltr,),
    )
  );
}
  • flutter運行,文本框成功展示
    image.png

2.2 熟悉widget

Widget: 作用類似于OC中的UIView,是小部件。分為兩大類:

  • StatelessWidget:無狀態組件。快捷鍵stless。
    創建后決定了樣式,狀態更改需要手動創建新widget。

  • StatefulWidget: 有狀態組件。快捷鍵stful
    本質上也是無狀態組件渲染無狀態,因為UI本身無狀態的。但它會保留組件狀態,記錄組件狀態的屬性改變時,會自動重新渲染。直到該組件完全銷毀,才會釋放記錄的屬性

2.2.1 無狀態組件(StatelessWidget)
  • 我們將main入口的Center組件改成自定義MyWidget無狀態組件。
import 'package:flutter/material.dart';

void main() {
  runApp(
    MyWidget(), //()是構造函數,類似C++。
  );
}

// 無狀態Widget快捷鍵是 stless
// 一個Widget,就是一個class類
class MyWidget extends StatelessWidget {
  @override
  // build:會將小部件放到渲染樹中去
  // 渲染樹: 會從main入口的第一個widget開始渲染,然后以樹結構依次向下渲染組件
  // 所有widget都必須有build方法,build返回的是什么,MyWidget在外界渲染的就是什么
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        'Hello Flutter2',
        textDirection: TextDirection.ltr,
      ),
    );
  }
}
  • 選擇模擬器,debug運行,模擬器啟動后,可以修改文本內容,點擊熱重載按鈕,感受熱重載的強大。
image.png
  • reload熱重載功能,是通過比較新舊代碼變化,來更新被改動部分代碼的。 但是有些場景熱重載失敗的,只能restart重啟才可以。

具體場景,參考Flutter官網介紹

Dart語言簡寫:

  • Dart語言中,如果函數只有一行內容時,可以使用=>縮寫:
// 改動前
//void main() {
//  runApp(MyWidget());
//}
// 簡寫:
void main() => runApp(MyWidget()); 

2.3 Text

  • Text是一個文本組件,是StatelessWidget不可變組件。
2.3.1 構造函數與參數
  • Text為例,構造函數是Text(),包含必選參數可選值,可選值可以賦默認值
    image.png
2.3.2 final和const修飾符

finalconst都類似于Swiftlet,是不可變的。

  • final可以不賦初始值,運行時賦值。
  • const必須在創建時就賦值

比如Text使用頻率最高styletextAlign,都是final聲明,因為Text本身是StatelessWidget不可變的組件。

image.png

  • Text加入樣式,使用變量創建TextStyle,_下劃線表示私有變量
import 'package:flutter/material.dart';

// 入口,展示MyWidget組件
void main() => runApp(MyWidget());

class MyWidget extends StatelessWidget {
  @override
  // build 確定組件返回的內容
  Widget build(BuildContext context) {

    // final創建一個_textStyle不可變變量,_開頭的屬性,表示私有屬性
    final _textStyle = TextStyle(color: Colors.red, fontSize: 40.0, fontWeight: FontWeight.bold);

    return Center(
      child: Text(
        'Hello Flutter',
        textDirection: TextDirection.ltr,
        style: _textStyle, // 直接使用變量
      ),
    );
  }
}

TextTextStyle相關屬性參數,可以Command + 鼠標左鍵查看

image.png

2.4 MaterialApp

  • MaterialApp:Flutter推薦方式,提供快速構建APP的方式(包括導航欄、內容主題等)
  • home: 類似于根控制器,需要指定一個widget
    Scaffold: 類似于導航控制器NavigationController,包含導航欄相關屬性。是一個可變組件statefulWidget
    appBar:導航欄
    body: 內容
  • theme主題,可控制主題色
import 'package:flutter/material.dart';

// 入口,展示MyWidget組件
void main() => runApp(App());

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // MaterialApp:Flutter推薦方式,提供快速構建APP的方式(包括導航欄、內容、主題等)
    return MaterialApp(
      // home: 類似于根控制器,也是需要指定一個widget
      // Scaffold: 類似于導航控制器NavigationController,包含導航欄相關屬性。是一個可變組件statefulWidget
      home: Scaffold(
        // appBar:導航欄
        appBar: AppBar(
              title: Text('Flutter Demo'),
            ),
        // body: 內容
        body: MyWidget(),
      ),
      // theme:主題,可控制主題色
      theme: ThemeData(
        primaryColor: Colors.yellow
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  @override
  // build 確定組件返回的內容
  Widget build(BuildContext context) {

    // final創建一個_textStyle不可變變量,_開頭的屬性,表示私有屬性
    final _textStyle = TextStyle(color: Colors.red, fontSize: 40.0, fontWeight: FontWeight.bold);

    return Center(
      child: Text(
        'Hello Flutter',
        textDirection: TextDirection.ltr,
        style: _textStyle, // 直接使用變量
      ),
    );
  }
}
image.png

2.5 ListView 列表視圖

  • 列表視圖:類似iOS的UITableView,但是沒有Section的概念。
2.5.1 準備Model

在布局之前,先準備數據

  • 新建Model文件夾,新建car.dart文件:
    image.png
// 不需要導入material.dart,因為Car直接繼承Object

// Car 模型
class Car {
  // 構造函數: {}內是可選值
  const Car({
    this.name,
    this.imageUrl,
  });

  // 名稱
  final String name;
  // 圖片鏈接
  final String imageUrl;
}

final List<Car> datas = [
  Car(
    name: '保時捷918 Spyder',
    imageUrl:
    'https://upload-images.jianshu.io/upload_images/2990730-7d8be6ebc4c7c95b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',
  ),
  Car(
    name: '蘭博基尼Aventador',
    imageUrl:
    'https://upload-images.jianshu.io/upload_images/2990730-e3bfd824f30afaac?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',
  ),
  Car(
    name: '法拉利Enzo',
    imageUrl:
    'https://upload-images.jianshu.io/upload_images/2990730-a1d64cf5da2d9d99?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',
  ),
  Car(
    name: 'Zenvo ST1',
    imageUrl:
    'https://upload-images.jianshu.io/upload_images/2990730-bf883b46690f93ce?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',
  ),
  Car(
    name: '邁凱倫F1',
    imageUrl:
    'https://upload-images.jianshu.io/upload_images/2990730-5a7b5550a19b8342?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',
  ),
  Car(
    name: '薩林S7',
    imageUrl:
    'https://upload-images.jianshu.io/upload_images/2990730-2e128d18144ad5b8?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',
  )
];
2.5.2 設置ListView
  • 如果直接導入Car模型,需要導入頭文件

三種導入頭文件的方式

  1. 直接手寫,在頂部import
  2. 左鍵點擊Car,出現紅色小燈泡,點擊import Library
  3. 鼠標光標放在Car上,按住Option+Enter鍵,再按一次Enter鍵,可快捷導入import Library

final List<Car> datas = []

  • Container: 類似于htmldiv,也類似于iOSUIView,就是用來放東西的。需要設置大小(也可以被子控件撐出大?。?/li>
  • Column: 內容垂直排列的容器
  • row: 內容水平排列的容器
  • stack: 內容重疊的容器
  • Image圖片可變組件network加載網絡圖片
  • SizedBox: 空容器,有大小。(有時為了便于內部插入元素,會直接使用Container
  • MaterialAppdebugShowCheckedModeBanner隱藏導航欄Debug角標
import 'package:flutter/material.dart';

import 'Model/car.dart';

// 入口,展示MyWidget組件
void main() => runApp(App());

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // MaterialApp:Flutter推薦方式,提供快速構建APP的方式(包括導航欄、內容、主題等)
    return MaterialApp(
      // 隱藏導航欄Debug角標
      debugShowCheckedModeBanner: false,
      // home: 類似于根控制器,需要指定一個widget
      home: Home(),
      // theme:主題,可控制主題色
      theme: ThemeData(
        primaryColor: Colors.yellow
      ),
    );
  }
}

// Home 組件
class Home extends StatelessWidget {

  // 回調函數,返回widget組件
  Widget _cellForRow(BuildContext context, int index) {
    // Container類似于html的div,也類似于iOS的UIView,就是用來放東西的
    // 需要大?。ㄒ部梢员蛔涌丶纬龃笮。?    return Container(
        color: Colors.white,
        // height: 20,
        margin:  EdgeInsets.all(10), //EdgeInsets.only(top: 1),
        // 子控件
        // Column 內容垂直排列的容器 row 內容水平排列的容器 stack 內容重疊的容器
        // Image圖片可變組件,network加載網絡圖片
        child: Column(
          children: <Widget>[
            Image.network(datas[index].imageUrl),
            SizedBox(height: 8,),
            Text(
              datas[index].name,
              style: TextStyle(
                  fontWeight: FontWeight.bold,
                fontSize: 18.0,
                fontStyle: FontStyle.italic),),
            SizedBox(height: 8,)
          ]
        )
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[100],
      appBar: AppBar(
        title: Text("Flutter Demo"),
      ),
      // ListView 列表組件(沒有iOS的Section概念)
      body: ListView.builder(
        // cell個數
        itemCount: datas.length,
        // cell內容(等同與cellForRow)build 是渲染
        // iOS中是使用代理和協議完成,這里是直接使用回調函數,有兩個入參
        itemBuilder: _cellForRow,
      ),
    );
  }
}

// 模型數組
// 沒導入頭文件時,會提示需要導入Car頭文件
// 三種導入頭文件的方式
// 1. 直接手寫,在頂部import
// 2. 左鍵點擊Car,出現紅色小燈泡,點擊import Library
// 3. 鼠標光標放在Car上,按住Option+Enter鍵,再按一次Enter鍵,可快捷導入import Library
// final List<Car> datas = []
  • 效果展示:


    image.png
  • 實際開發中,我們可以將ListView內容抽離出來,做成單獨文件listView_demo.dart:

import 'package:flutter/material.dart';

import 'car.dart';

class ListViewDemo extends StatelessWidget {

  // 回調函數,返回widget組件
  Widget _cellForRow(BuildContext context, int index) {
    return Container(
        color: Colors.white,
        margin:  EdgeInsets.all(10)
        child: Column(
            children: <Widget>[
              Image.network(datas[index].imageUrl),
              SizedBox(height: 8,),
              Text(
                datas[index].name,
                style: TextStyle(
                    fontWeight: FontWeight.bold,
                    fontSize: 18.0,
                    fontStyle: FontStyle.italic),),
              SizedBox(height: 8,)
            ]
        )
    );
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      // cell個數
      itemCount: datas.length,
      itemBuilder: _cellForRow,
    );
  }
}
  • main.dart中,直接使用我們封裝的ListViewDemo導入頭文件)即可:
import 'package:flutter/material.dart';

import 'Model/listView_demo.dart';

// 入口,展示MyWidget組件
void main() => runApp(App());

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // MaterialApp:Flutter推薦方式,提供快速構建APP的方式(包括導航欄、內容、主題等)
    return MaterialApp(
      // 隱藏導航欄Debug角標
      debugShowCheckedModeBanner: false,
      // 根組件
      home: Home(),
      // 主題
      theme: ThemeData(
        primaryColor: Colors.yellow
      ),
    );
  }
}

// Home 組件
class Home extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[100],
      appBar: AppBar(
        title: Text("Flutter Demo"),
      ),
      // 列表
      body: ListViewDemo(),
    );
  }
}

3. 常用基礎組件

  • 上面講了簡單封裝,我們現在對幾個基礎組件進行簡單封裝

3.1 基礎文本

  • 新建一個base_widget.dart文件,創建TextDemo組件:
  1. 通過屬性聲明變量內容樣式),_開頭的屬性為私有屬性。
  2. $ + 屬性名快捷插入變量內容。 如果后面有其他字符等信息,可使用{}包裹起來。
  3. Text可設置maxLines最大行數,超出部分樣式通過overflow設置(ellipsis尾部省略號
import 'package:flutter/material.dart';

//【普通文本 Demo】
class TextDemo extends StatelessWidget {
  // 文本樣式(私有屬性)
  final TextStyle _textStyle = TextStyle(
    fontSize: 24.0,
  );

  final String _title = 'Flutter入門';
  final String _author = 'HT';

  @override
  Widget build(BuildContext context) {

    // $ + 屬性名: 快捷插入變量內容。 如果后面有其他字符等信息,可使用{}包裹起來。
    return Text(
      '《$_title》這是一個TextDemo,使用Flutter開發。由iOS開發者${_author}開發,快速配置,簡易上手的零基礎學習方式。歡迎閱讀和上手練習,不懂之處,留言交流',
      textAlign: TextAlign.center,
      style: _textStyle,
      maxLines: 4, // 最多4行
      overflow: TextOverflow.ellipsis, // 超出顯示省略號
    );
  }
}
  • main.dart中指定bodyTextDemo:
    image.png
  • 展示樣式:


    image.png

3.2 富文本

  • 使用RichText組件,通過給text指定TextSpan類型,添加children:<TextSpan>數組,數組內創建TextSpan,并賦值樣式即可:
import 'package:flutter/material.dart';

//【富文本 Demo】
class RichTextDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RichText(
      //基礎元素
      text: TextSpan(
          text: '《Flutter 入門》',
          style: TextStyle(
            fontSize: 30,
            color: Colors.black,
          ) ,
          // 子元素
          children:<TextSpan>[
            // 元素一
            TextSpan(
              text: 'HT',
              style: TextStyle(
                fontSize: 20,
                color: Colors.blue,
              ),
            ),
            // 元素二
            TextSpan(
              text: '666',
              style: TextStyle(
                fontSize: 40,
                color: Colors.red,
              ),
            ),
          ]),
    );
  }
}
  • main.dart中指定bodyRichTextDemo:
    image.png
  • 展示樣式:


    image.png

3.3 基礎容器Container

  • 創建基礎組件BaseWidgetDemo,返回Container組件:
//【基礎組件Demo】
class BaseWidgetDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 容器背景藍色(沒有給定大小時,根據子視圖大小撐開)
    return Container(
      color: Colors.blue,
      // 子視圖橫向布局
      child: Row(
        children: <Widget>[
          Container(
            // 內邊距
            padding: EdgeInsets.only(left: 10,top: 10,right: 10,bottom: 10),
            // 外邊距
            margin: EdgeInsets.only(left: 10,top: 10,right: 10,bottom: 10),
            // 子元素紅色
            color: Colors.red,
            // 子元素內容包含?號圖片
            child: Icon(Icons.add),
          ),
          Container(
            // 子元素紅色
            color: Colors.red,
            // 子元素內容包含?號圖片
            child: Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}
image.png
  • 展示樣式:


    image.png
  • Dart中沒有iOSButton概念:
    因為Button實際是圖片 + 文字 + 手勢 + 狀態組成,以及封裝相應的響應事件。我們一般使用手勢小部件,包裝圖片文字即可。

【快捷鍵】
command + option + L:自動格式化
command + -:折疊當前函數
command + shift + -:折疊全部函數
command + {:回到上一步
command + }: 回到下一步
command + shift + O: 快速到指定文件
stless:不可變組件
stful: 可變組件


本節主要是Flutter基礎體驗,通過本節,其實我們已經感受到了Flutter便捷
下一節,Flutter入門三:自動布局(Row/Column/Stack)與兩種Widget

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 相關帖子收藏 windows 打包上傳ios到app store 作為android開發者,直接用studio即可...
    有點健忘閱讀 1,759評論 0 1
  • Content: Flutter框架概況發展概述發展歷史框架特性框架結構 快速入門安裝Flutter在Mac OS...
    EchoZuo閱讀 6,482評論 3 54
  • 夜鶯2517閱讀 127,749評論 1 9
  • 版本:ios 1.2.1 亮點: 1.app角標可以實時更新天氣溫度或選擇空氣質量,建議處女座就不要選了,不然老想...
    我就是沉沉閱讀 6,939評論 1 6
  • 我是一名過去式的高三狗,很可悲,在這三年里我沒有戀愛,看著同齡的小伙伴們一對兒一對兒的,我的心不好受。怎么說呢,高...
    小娘紙閱讀 3,414評論 4 7