Flutter開發(3)- 第一個Flutter項目

一. 創建Flutter項目

創建Flutter項目有兩種方式:通過命令行創建?和?通過開發工具創建

1.1. 通過命令行創建

????通過命令行創建非常簡單,在終端輸入以下命令即可:

????**注意:**Flutter的名稱不要包含特殊的字符,另外不可以使用駝峰標識

創建完之后使用自己喜歡的開發工具打開即可

????flutter create learn_flutter

1.2. 通過開發工具創建

我這里也可以直接通過Android Studio來進行創建:

????選擇Start a new Flutter project,之后填寫相關的信息即可,這里不再贅述

1.3. 默認程序分析

????我們講創建的應用起來跑在模擬器上(我這里選擇iPhone模擬器,Android也可以),會看到如下效果:

默認項目分析:

????我們之前已經分析過目錄結構了,在目錄下有一個lib文件夾,里面會存放我們編寫的Flutter代碼;

????打開發現里面有一個main.dart,它是我們Flutter啟動的入口文件,里面有main函數;

默認代碼分析:

????這是一個計數器的案例程序,點擊右下角的?+?符號,上面顯示的數字會遞增;

????但是我們第一次接觸main.dart中的代碼,可能會發現很多不認識的代碼,不知道這個內容是如何編寫出來的;

? ??作為初學者,我的建議是將其中所有的代碼全部刪除掉,從零去創建里面的代碼,這樣我們才能對Flutter應用程序的結構非常清晰;

二. 開始Flutter代碼

2.1. Hello World

2.1.1. Hello World的需求

????做任何的開發,我們都是從祖傳的Hello World開始,那么現在我們的需求來了:

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

import 'package:flutter/material.dart';

main(List<String> args) {

? runApp(Text("Hello World", textDirection: TextDirection.ltr));

}

當然,上面的代碼我們已經實現了在界面上顯示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),

? ? ? ),

? ? )

? );

}

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),

? ? ? ? ? ),

? ? ? ? ),

? ? ? ),

? ? )

? );

}

在最外層包裹一個MaterialApp

????這意味著整個應用我們都會采用MaterialApp風格的一些東西,方便我們對應用的設計,并且目前我們使用了其中兩個屬性;

????title:這個是定義在Android系統中打開多任務切換窗口時顯示的標題;(暫時可以不寫)

????home:是該應用啟動時顯示的頁面,我們傳入了一個Scaffold;

Scaffold是什么呢?

????翻譯過來是腳手架,腳手架的作用就是搭建頁面的基本結構;

????所以我們給MaterialApp的home屬性傳入了一個Scaffold對象,作為啟動顯示的Widget;

????Scaffold也有一些屬性,比如appBar和body;

????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),

? ? ? ? ? ? ? )

? ? ? ? ? ? ],

? ? ? ? ? ),

? ? ? ? ),

? ? ? ),

? ? )

? );

}

2.4. 代碼重構

2.4.1. 創建自己的Widget

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

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

????1、Flutter整個開發過程中就是形成一個Widget樹,所以形成嵌套是很正常的。

????2、關于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的格式:

????1、讓自己創建的Widget繼承自StatelessWidget;

????2、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方法什么情況下被執行呢?:

????1、當我們的StatelessWidget第一次被插入到Widget樹中時(也就是第一次被創建時);

????2、當我們的父Widget(parent widget)發生改變時,子Widget會被重新構建;

????3、如果我們的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),

? ? ? ? ? )

? ? ? ? ],

? ? ? ),

? ? );

? }

}

三. 案例練習

3.1. 案例最終效果

我們先來看一下案例的最終展示效果:

????這個效果中我們會使用很多沒有接觸的Widget;

????沒有關系,后面這些常用的Widget我會一個個講解;

????這個案例最主要的目的還是讓大家更加熟悉Flutter的開發模式以及自定義Widget的封裝過程;

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的布局中,內容是不能超出屏幕范圍的,當超出時不會自動變成滾動效果,而是會報下面的錯誤;

如何可以解決這個問題呢?

????我們將Column換成ListView即可;

????ListView可以讓自己的子Widget變成滾動的效果;

3.4. 案例細節調整

3.4.1. 界面整體邊距

如果我們希望整個內容距離屏幕的邊緣有一定的間距,怎么做呢?

????我們需要使用另外一個Widget:Padding,它有一個padding屬性用于設置邊距大小;

????沒錯,設置內邊距也是使用Widget,這個Widget就是Padding;

3.4.2. 商品內邊距和邊框

我們現在希望給所有的商品也添加一個內邊距,并且還有邊框,怎么做呢?

????我們可以使用一個Container的Widget,它里面有padding屬性,并且可以通過decoration來設置邊框;

????Container我們也會在后面詳細來講,我們先用起來;

3.4.3. 文字圖片的間距

我們希望給圖片和文字之間添加一些間距,怎么做呢?

????方式一:給圖片或者文字添加一個向上的內邊距或者向下的內邊距;

????方式二:使用SizedBox的Widget,設置一個height屬性,可以增加一些距離;

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)

? ? ? ? ],

? ? ? ),

? ? );

? }

}

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容