Flutter(六)之Flutter開發初體驗

前言一:接下來一段時間我會陸續更新一些列Flutter文字教程前端

更新進度: 每週至少兩篇;vue

更新地點: 首發於公衆號,次日更新於掘金、思否、開發者頭條等地方;算法

更多交流: 能夠添加個人微信 372623326,關注個人微博:coderwhyshell

但願你們能夠 幫忙轉發,點擊在看,給我更多的創做動力。bash

一. 建立Flutter項目

建立Flutter項目有兩種方式:經過命令行建立經過開發工具建立微信

1.1. 經過命令行建立

經過命令行建立很是簡單,在終端輸入如下命令便可:數據結構

  • **注意:**Flutter的名稱不要包含特殊的字符,另外不可使用駝峯標識
  • 建立完以後使用本身喜歡的開發工具打開便可
flutter create learn_flutter
複製代碼

image-20190915164546394

1.2. 經過開發工具建立

我這裏也能夠直接經過Android Studio來進行建立:app

  • 選擇Start a new Flutter project,以後填寫相關的信息便可,這裏再也不贅述

image-20190901200434719

1.3. 默認程序分析

咱們講建立的應用起來跑在模擬器上(我這裏選擇iPhone模擬器,Android也能夠),會看到以下效果:less

image-20190901200718627

默認項目分析:ide

  • 咱們以前已經分析過目錄結構了,在目錄下有一個lib文件夾,裏面會存放咱們編寫的Flutter代碼;
  • 打開發現裏面有一個main.dart,它是咱們Flutter啓動的入口文件,裏面有main函數

默認代碼分析:

  • 這是一個計數器的案例程序,點擊右下角的 + 符號,上面顯示的數字會遞增;
  • 可是咱們第一次接觸main.dart中的代碼,可能會發現不少不認識的代碼,不知道這個內容是如何編寫出來的;

做爲初學者,個人建議是將其中全部的代碼所有刪除掉,從零去建立裏面的代碼,這樣咱們才能對Flutter應用程序的結構很是清晰;

二. 開始Flutter代碼

2.1. Hello World

2.1.1. Hello World的需求

作任何的開發,咱們都是從祖傳的Hello World開始,那麼如今咱們的需求來了:

  • 在界面中心位置,顯示一個Hello World;

2.1.2. Hello World的實現

下面,咱們就動手開始編寫Hello World:

import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(Text("Hello World", textDirection: TextDirection.ltr));
}
複製代碼

image-20190916212127281

固然,上面的代碼咱們已經實現了在界面上顯示Hello World:

  • 可是沒有居中字體也有點小
  • 這些問題,咱們放到後面再來解決,先搞懂目前的幾行代碼;

上面的代碼咱們有一些比較熟悉,有一些並不清楚是什麼:

  • 好比咱們知道Dart程序的入口都是main函數,而Flutter是Dart編寫的,因此入口也是main函數;
  • 可是咱們導入的Material是什麼呢
  • 另外,咱們在main函數中調用了一個runApp()函數又是什麼呢?

下面,咱們對不認識的代碼進行一些分析。

2.2. 代碼分析

2.2.1. runApp和Widget

runApp是Flutter內部提供的一個函數,當咱們啓動一個Flutter應用程序時就是從調用這個函數開始的

  • 咱們能夠點到runApp的源碼,查看到該函數
  • 咱們暫時不分析具體的源碼(由於我發現過多的理論,對於初學者來講並不友好)
void runApp(Widget app) {
  ...省略代碼
}
複製代碼

該函數讓咱們傳入一個東西:Widget

咱們先說Widget的翻譯

  • Widget在國內有不少的翻譯;
  • 作過Android、iOS等開發的人羣,喜歡將它翻譯成控件
  • 作過Vue、React等開發的人羣,喜歡將它翻譯成組件
  • 若是咱們使用Google,Widget翻譯過來應該是小部件
  • 沒有說哪一種翻譯必定是對的,或者必定是錯的,可是我我的更傾向於小部件或者組件

Widget到底什麼東西呢?

  • 咱們學習Flutter,從一開始就能夠有一個基本的認識:Flutter中萬物皆Widget(萬物皆可盤)
  • 在咱們iOS或者Android開發中,咱們的界面有不少種類的劃分:應用(Application)、視圖控制器(View Controller)、活動(Activity)、View(視圖)、Button(按鈕)等等;
  • 可是在Flutter中,這些東西都是不一樣的Widget而已
  • 也就是咱們整個應用程序中所看到的內容幾乎都是Widget,甚至是內邊距的設置,咱們也須要使用一個叫Padding的Widget來作;

runApp函數讓咱們傳入的就是一個Widget:

  • 可是咱們如今沒有Widget,怎麼辦呢?
  • 咱們能夠導入Flutter默認已經給咱們提供的Material庫,來使用其中的不少內置Widget;

2.2.2. Material設計風格

material是什麼呢?

  • material是Google公司推行的一套設計風格,或者叫設計語言設計規範等;
  • 裏面有很是多的設計規範,好比顏色文字的排版響應動畫與過分填充等等;
  • 在Flutter中高度集成了Material風格的Widget
  • 在咱們的應用中,咱們能夠直接使用這些Widget來建立咱們的應用(後面會用到不少);

Text小部件分析

  • 咱們可使用Text小部件來完成文字的顯示;
  • 咱們發現Text小部件繼承自StatelessWidget,StatelessWidget繼承自Widget;
  • 因此咱們能夠將Text小部件傳入到runApp函數中
  • 屬性很是多,可是咱們已經學習了Dart語法,因此你會發現只有this.data屬性是必須傳入的。
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,
    this.textWidthBasis,
  });
}
複製代碼

StatelessWidget簡單介紹:

  • StatelessWidget繼承自Widget;
  • 後面我會更加詳細的介紹它的用法;
abstract class StatelessWidget extends Widget {
	// ...省略代碼
}
複製代碼

2.3. 代碼改進

2.3.1. 改進界面樣式

咱們發現如今的代碼並非咱們想要的最終結果:

  • 咱們可能但願文字居中顯示,而且能夠大一些;
  • 居中顯示: 須要使用另一個Widget,Center
  • 文字大一些: 須要給Text文本設置一些樣式;

咱們修改代碼以下:

  • 咱們在Text小部件外層包裝了一個Center部件,讓Text做爲其child;
  • 而且,咱們給Text組件設置了一個屬性:style,對應的值是TextStyle類型;
import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(
    Center(
      child: Text(
        "Hello World",
        textDirection: TextDirection.ltr,
        style: TextStyle(fontSize: 36),
      ),
    )
  );
}
複製代碼

image-20190916215857058

2.3.2. 改進界面結構

目前咱們雖然能夠顯示HelloWorld,可是咱們發現最底部的背景是黑色,而且咱們的頁面並不夠結構化。

  • 正常的App頁面應該有必定的結構,好比一般都會有導航欄,會有一些背景顏色

在開發當中,咱們並不須要從零去搭建這種結構化的界面,咱們可使用Material庫,直接使用其中的一些封裝好的組件來完成一些結構的搭建。

咱們經過下面的代碼來實現:

import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("CODERWHY"),
        ),
        body: Center(
          child: Text(
            "Hello World",
            textDirection: TextDirection.ltr,
            style: TextStyle(fontSize: 36),
          ),
        ),
      ),
    )
  );
}
複製代碼

image-20190916221014543

在最外層包裹一個MaterialApp

  • 這意味着整個應用咱們都會採用MaterialApp風格的一些東西,方便咱們對應用的設計,而且目前咱們使用了其中兩個屬性;
  • title:這個是定義在Android系統中打開多任務切換窗口時顯示的標題;(暫時能夠不寫)
  • home:是該應用啓動時顯示的頁面,咱們傳入了一個Scaffold;

Scaffold是什麼呢?

  • 翻譯過來是腳手架,腳手架的做用就是搭建頁面的基本結構;
  • 因此咱們給MaterialApp的home屬性傳入了一個Scaffold對象,做爲啓動顯示的Widget;
  • Scaffold也有一些屬性,好比appBarbody
  • appBar是用於設計導航欄的,咱們傳入了一個title屬性
  • body是頁面的內容部分,咱們傳入了以前已經建立好的Center中包裹的一個Text的Widget;

2.3.3. 進階案例實現

咱們可讓界面中存在更多的元素:

  • 寫到這裏的時候,你可能已經發現嵌套太多了,不要着急,咱們後面會對代碼重構的
import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("CODERWHY"),
        ),
        body: Center(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Checkbox(
                value: true,
                onChanged: (value) => print("Hello World")),
              Text(
                "贊成協議",
                textDirection: TextDirection.ltr,
                style: TextStyle(fontSize: 20),
              )
            ],
          ),
        ),
      ),
    )
  );
}
複製代碼

image-20190916221925380

2.4. 代碼重構

2.4.1. 建立本身的Widget

不少學習Flutter的人,都會被Flutter的嵌套勸退,當代碼嵌套過多時,結構很容易看不清晰。

這裏有兩點我先說明一下:

  • 一、Flutter整個開發過程當中就是造成一個Widget樹,因此造成嵌套是很正常的。
  • 二、關於Flutter的代碼縮進,更多開發中咱們使用的是2個空格(前端開發2個空格居多,你喜歡4個也沒問題)

可是,咱們開發一個這麼簡單的程序就出現如此多的嵌套,若是應用程序更復雜呢?

  • 咱們能夠對咱們的代碼進行封裝,將它們封裝到本身的Widget中,建立本身的Widget;

如何建立本身的Widget呢?

  • 在Flutter開發中,咱們能夠繼承自StatelessWidget或者StatefulWidget來建立本身的Widget類;
  • StatelessWidget: 沒有狀態改變的Widget,一般這種Widget僅僅是作一些展現工做而已;
  • StatefulWidget: 須要保存狀態,而且可能出現狀態改變的Widget;

在上面的案例中對代碼的重構,咱們使用StatelessWidget便可,因此咱們接下來學習一下若是利用StatelessWidget來對咱們的代碼進行重構;

StatefulWidget咱們放到後面的一個案例中來學習;

2.4.2. StatelessWidget

StatelessWidget一般是一些沒有狀態(State,也能夠理解成data)須要維護的Widget:

  • 它們的數據一般是直接寫死(放在Widget中的數據,必須被定義爲final,爲何呢?我在下一個章節講解StatefulWidget會講到);
  • 從parent widget中傳入的並且一旦傳入就不能夠修改;
  • 從InheritedWidget獲取來使用的數據(這個放到後面會講解);

咱們來看一下建立一個StatelessWidget的格式:

  • 一、讓本身建立的Widget繼承自StatelessWidget;
  • 二、StatelessWidget包含一個必須重寫的方法:build方法;
class MyStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return <返回咱們的Widget要渲染的Widget,好比一個Text Widget>;
  }
}
複製代碼

build方法的解析:

  • Flutter在拿到咱們本身建立的StatelessWidget時,就會執行它的build方法;
  • 咱們須要在build方法中告訴Flutter,咱們的Widget但願渲染什麼元素,好比一個Text Widget;
  • StatelessWidget沒辦法主動去執行build方法,當咱們使用的數據發生改變時,build方法會被從新執行;

build方法什麼狀況下被執行呢?:

  • 一、當咱們的StatelessWidget第一次被插入到Widget樹中時(也就是第一次被建立時);
  • 二、當咱們的父Widget(parent widget)發生改變時,子Widget會被從新構建;
  • 三、若是咱們的Widget依賴InheritedWidget的一些數據,InheritedWidget數據發生改變時;

2.4.3. 重構案例代碼

如今咱們就能夠經過StatelessWidget來對咱們的代碼進行重構了

  • 由於咱們的整個代碼都是一些數據展現,沒有數據的改變,使用StatelessWidget便可;
  • 另外,爲了體現更好的封裝性,我對代碼進行了兩層的拆分,讓代碼結構看起來更加清晰;(具體的拆分方式,我會在後面的案例中不斷的體現出來,目前咱們先拆分兩層)

重構後的代碼以下:

import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("CODERWHY"),
        ),
        body: HomeContent(),
      ),
    )
  }
}

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Checkbox(
              value: true,
              onChanged: (value) => print("Hello World")),
          Text(
            "贊成協議",
            textDirection: TextDirection.ltr,
            style: TextStyle(fontSize: 20),
          )
        ],
      ),
    );
  }
}
複製代碼

image-20190917091913208

三. 案例練習

3.1. 案例最終效果

咱們先來看一下案例的最終展現效果:

  • 這個效果中咱們會使用不少沒有接觸的Widget;
  • 沒有關係,後面這些經常使用的Widget我會一個個講解;
  • 這個案例最主要的目的仍是讓你們更加熟悉Flutter的開發模式以及自定義Widget的封裝過程;

image-20190917151554241

3.2. 自定義Widget

在咱們的案例中,很明顯一個產品的展現就是一個大的Widget,這個Widget包含以下Widget:

  • 標題的Widget:使用一個Text Widget完成;
  • 描述的Widget:使用一個Text Widget完成;
  • 圖片的Widget:使用一個Image Widget完成;
  • 上面三個Widget要垂直排列,咱們可使用一個Column的Widget(上一個章節中咱們使用了一次Row是水平排列的)

另外,三個展現的標題、描述、圖片都是不同的,因此咱們可讓Parent Widget來決定內容:

  • 建立三個成員變量保存父Widget傳入的數據
class ProductItem extends StatelessWidget {
  final String title;
  final String desc;
  final String imageURL;

  ProductItem(this.title, this.desc, this.imageURL);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Text(title, style: TextStyle(fontSize: 24)),
        Text(desc, style: TextStyle(fontSize: 18)),
        Image.network(imageURL)
      ],
    );
  }
}
複製代碼

3.3. 列表數據展現

如今咱們就能夠建立三個ProductItem來讓他們展現了:

  • MyApp和上一個章節是一致的,沒有任何改變;
  • HomeContent中,咱們使用了一個Column,由於咱們建立的三個ProductItem是垂直排列的
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primaryColor: Colors.blueAccent
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text("CODERWHY"),
        ),
        body: HomeContent(),
      ),
    );
  }
}

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        ProductItem("Apple1", "Macbook Product1", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg"),
        ProductItem("Apple2", "Macbook Product2", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imm9u5zj30u00k0adf.jpg"),
        ProductItem("Apple3", "Macbook Product3", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imqlouhj30u00k00v0.jpg"),
      ],
    );
  }
}

複製代碼

運行效果以下:

  • 錯誤信息:下面出現了黃色的斑馬線;
  • 這是由於在Flutter的佈局中,內容是不能超出屏幕範圍的,當超出時不會自動變成滾動效果,而是會報下面的錯誤;

image-20190917153343504

如何能夠解決這個問題呢?

  • 咱們將Column換成ListView便可;
  • ListView可讓本身的子Widget變成滾動的效果;

image-20190917153558093

3.4. 案例細節調整

3.4.1. 界面總體邊距

若是咱們但願整個內容距離屏幕的邊緣有必定的間距,怎麼作呢?

  • 咱們須要使用另一個Widget:Padding,它有一個padding屬性用於設置邊距大小;
  • 沒錯,設置內邊距也是使用Widget,這個Widget就是Padding;

image-20190917153942819

3.4.2. 商品內邊距和邊框

咱們如今但願給全部的商品也添加一個內邊距,而且還有邊框,怎麼作呢?

  • 咱們可使用一個Container的Widget,它裏面有padding屬性,而且能夠經過decoration來設置邊框;
  • Container咱們也會在後面詳細來說,咱們先用起來;

image-20190917154359364

3.4.3. 文字圖片的間距

咱們但願給圖片和文字之間添加一些間距,怎麼作呢?

  • 方式一:給圖片或者文字添加一個向上的內邊距或者向下的內邊距;
  • 方式二:使用SizedBox的Widget,設置一個height屬性,能夠增長一些距離;

image-20190917154630828

3.5. 最終實現代碼

最後,我給出最終實現代碼:

import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primaryColor: Colors.blueAccent
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text("CODERWHY"),
        ),
        body: HomeContent(),
      ),
    );
  }
}

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: ListView(
        children: <Widget>[
          ProductItem("Apple1", "Macbook Product1", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg"),
          ProductItem("Apple2", "Macbook Product2", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imm9u5zj30u00k0adf.jpg"),
          ProductItem("Apple3", "Macbook Product3", "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imqlouhj30u00k00v0.jpg"),
        ],
      ),
    );
  }
}

class ProductItem extends StatelessWidget {
  final String title;
  final String desc;
  final String imageURL;

  ProductItem(this.title, this.desc, this.imageURL);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(20),
      decoration: BoxDecoration(
        border: Border.all()
      ),
      child: Column(
        children: <Widget>[
          Text(title, style: TextStyle(fontSize: 24)),
          Text(desc, style: TextStyle(fontSize: 18)),
          SizedBox(height: 10,),
          Image.network(imageURL)
        ],
      ),
    );
  }
}

複製代碼

備註:全部內容首發於公衆號,以後除了Flutter也會更新其餘技術文章,TypeScript、React、Node、uniapp、mpvue、數據結構與算法等等,也會更新一些本身的學習心得等,歡迎你們關注

公衆號
相關文章
相關標籤/搜索