一. 創(chuàng)建Flutter項(xiàng)目
創(chuàng)建Flutter項(xiàng)目有兩種方式:通過(guò)命令行創(chuàng)建?和?通過(guò)開(kāi)發(fā)工具創(chuàng)建
1.1. 通過(guò)命令行創(chuàng)建
????通過(guò)命令行創(chuàng)建非常簡(jiǎn)單,在終端輸入以下命令即可:
????**注意:**Flutter的名稱不要包含特殊的字符,另外不可以使用駝峰標(biāo)識(shí)
創(chuàng)建完之后使用自己喜歡的開(kāi)發(fā)工具打開(kāi)即可
????flutter create learn_flutter
1.2. 通過(guò)開(kāi)發(fā)工具創(chuàng)建
我這里也可以直接通過(guò)Android Studio來(lái)進(jìn)行創(chuàng)建:
????選擇Start a new Flutter project,之后填寫(xiě)相關(guān)的信息即可,這里不再贅述
1.3. 默認(rèn)程序分析
????我們講創(chuàng)建的應(yīng)用起來(lái)跑在模擬器上(我這里選擇iPhone模擬器,Android也可以),會(huì)看到如下效果:
默認(rèn)項(xiàng)目分析:
????我們之前已經(jīng)分析過(guò)目錄結(jié)構(gòu)了,在目錄下有一個(gè)lib文件夾,里面會(huì)存放我們編寫(xiě)的Flutter代碼;
????打開(kāi)發(fā)現(xiàn)里面有一個(gè)main.dart,它是我們Flutter啟動(dòng)的入口文件,里面有main函數(shù);
默認(rèn)代碼分析:
????這是一個(gè)計(jì)數(shù)器的案例程序,點(diǎn)擊右下角的?+?符號(hào),上面顯示的數(shù)字會(huì)遞增;
????但是我們第一次接觸main.dart中的代碼,可能會(huì)發(fā)現(xiàn)很多不認(rèn)識(shí)的代碼,不知道這個(gè)內(nèi)容是如何編寫(xiě)出來(lái)的;
? ??作為初學(xué)者,我的建議是將其中所有的代碼全部刪除掉,從零去創(chuàng)建里面的代碼,這樣我們才能對(duì)Flutter應(yīng)用程序的結(jié)構(gòu)非常清晰;
二. 開(kāi)始Flutter代碼
2.1. Hello World
2.1.1. Hello World的需求
????做任何的開(kāi)發(fā),我們都是從祖?zhèn)鞯腍ello World開(kāi)始,那么現(xiàn)在我們的需求來(lái)了:
????在界面中心位置,顯示一個(gè)Hello World;
import 'package:flutter/material.dart';
main(List<String> args) {
? runApp(Text("Hello World", textDirection: TextDirection.ltr));
}
當(dāng)然,上面的代碼我們已經(jīng)實(shí)現(xiàn)了在界面上顯示Hello World:
????但是沒(méi)有居中,字體也有點(diǎn)??;
????這些問(wèn)題,我們放到后面再來(lái)解決,先搞懂目前的幾行代碼;
上面的代碼我們有一些比較熟悉,有一些并不清楚是什么:
????比如我們知道Dart程序的入口都是main函數(shù),而Flutter是Dart編寫(xiě)的,所以入口也是main函數(shù);
????但是我們導(dǎo)入的Material是什么呢?
????另外,我們?cè)趍ain函數(shù)中調(diào)用了一個(gè)runApp()函數(shù)又是什么呢?
下面,我們對(duì)不認(rèn)識(shí)的代碼進(jìn)行一些分析。
2.2. 代碼分析
2.2.1. runApp和Widget
runApp是Flutter內(nèi)部提供的一個(gè)函數(shù),當(dāng)我們啟動(dòng)一個(gè)Flutter應(yīng)用程序時(shí)就是從調(diào)用這個(gè)函數(shù)開(kāi)始的
????我們可以點(diǎn)到runApp的源碼,查看到該函數(shù)
????我們暫時(shí)不分析具體的源碼(因?yàn)槲野l(fā)現(xiàn)過(guò)多的理論,對(duì)于初學(xué)者來(lái)說(shuō)并不友好)
void runApp(Widget app) {
? ...省略代碼
}
該函數(shù)讓我們傳入一個(gè)東西:Widget?
我們先說(shuō)Widget的翻譯:
????Widget在國(guó)內(nèi)有很多的翻譯;
????做過(guò)Android、iOS等開(kāi)發(fā)的人群,喜歡將它翻譯成控件;
????做過(guò)Vue、React等開(kāi)發(fā)的人群,喜歡將它翻譯成組件;
????如果我們使用Google,Widget翻譯過(guò)來(lái)應(yīng)該是小部件;
????沒(méi)有說(shuō)哪種翻譯一定是對(duì)的,或者一定是錯(cuò)的,但是我個(gè)人更傾向于小部件或者組件;
Widget到底什么東西呢?
????我們學(xué)習(xí)Flutter,從一開(kāi)始就可以有一個(gè)基本的認(rèn)識(shí):Flutter中萬(wàn)物皆Widget(萬(wàn)物皆可盤);
????在我們iOS或者Android開(kāi)發(fā)中,我們的界面有很多種類的劃分:應(yīng)用(Application)、視圖控制器(View Controller)、活動(dòng)(Activity)、View(視圖)、Button(按鈕)等等;
????但是在Flutter中,這些東西都是不同的Widget而已;
????也就是我們整個(gè)應(yīng)用程序中所看到的內(nèi)容幾乎都是Widget,甚至是內(nèi)邊距的設(shè)置,我們也需要使用一個(gè)叫Padding的Widget來(lái)做;
runApp函數(shù)讓我們傳入的就是一個(gè)Widget:
????但是我們現(xiàn)在沒(méi)有Widget,怎么辦呢?
????我們可以導(dǎo)入Flutter默認(rèn)已經(jīng)給我們提供的Material庫(kù),來(lái)使用其中的很多內(nèi)置Widget;
2.2.2. Material設(shè)計(jì)風(fēng)格
material是什么呢?
????material是Google公司推行的一套設(shè)計(jì)風(fēng)格,或者叫設(shè)計(jì)語(yǔ)言、設(shè)計(jì)規(guī)范等;
????里面有非常多的設(shè)計(jì)規(guī)范,比如顏色、文字的排版、響應(yīng)動(dòng)畫(huà)與過(guò)度、填充等等;
????在Flutter中高度集成了Material風(fēng)格的Widget;
????在我們的應(yīng)用中,我們可以直接使用這些Widget來(lái)創(chuàng)建我們的應(yīng)用(后面會(huì)用到很多);
Text小部件分析:
????我們可以使用Text小部件來(lái)完成文字的顯示;
????我們發(fā)現(xiàn)Text小部件繼承自StatelessWidget,StatelessWidget繼承自Widget;
????所以我們可以將Text小部件傳入到runApp函數(shù)中
????屬性非常多,但是我們已經(jīng)學(xué)習(xí)了Dart語(yǔ)法,所以你會(huì)發(fā)現(xiàn)只有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簡(jiǎn)單介紹:
????StatelessWidget繼承自Widget;
????????后面我會(huì)更加詳細(xì)的介紹它的用法;
abstract class StatelessWidget extends Widget {
// ...省略代碼
}
2.3. 代碼改進(jìn)
2.3.1. 改進(jìn)界面樣式
我們發(fā)現(xiàn)現(xiàn)在的代碼并不是我們想要的最終結(jié)果:
????我們可能希望文字居中顯示,并且可以大一些;
????居中顯示: 需要使用另外一個(gè)Widget,Center;
????文字大一些: 需要給Text文本設(shè)置一些樣式;
我們修改代碼如下:
????我們?cè)赥ext小部件外層包裝了一個(gè)Center部件,讓Text作為其child;
????并且,我們給Text組件設(shè)置了一個(gè)屬性:style,對(duì)應(yīng)的值是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. 改進(jìn)界面結(jié)構(gòu)
目前我們雖然可以顯示HelloWorld,但是我們發(fā)現(xiàn)最底部的背景是黑色,并且我們的頁(yè)面并不夠結(jié)構(gòu)化。
????正常的App頁(yè)面應(yīng)該有一定的結(jié)構(gòu),比如通常都會(huì)有導(dǎo)航欄,會(huì)有一些背景顏色等
在開(kāi)發(fā)當(dāng)中,我們并不需要從零去搭建這種結(jié)構(gòu)化的界面,我們可以使用Material庫(kù),直接使用其中的一些封裝好的組件來(lái)完成一些結(jié)構(gòu)的搭建。
我們通過(guò)下面的代碼來(lái)實(shí)現(xiàn):
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),
? ? ? ? ? ),
? ? ? ? ),
? ? ? ),
? ? )
? );
}
在最外層包裹一個(gè)MaterialApp
????這意味著整個(gè)應(yīng)用我們都會(huì)采用MaterialApp風(fēng)格的一些東西,方便我們對(duì)應(yīng)用的設(shè)計(jì),并且目前我們使用了其中兩個(gè)屬性;
????title:這個(gè)是定義在Android系統(tǒng)中打開(kāi)多任務(wù)切換窗口時(shí)顯示的標(biāo)題;(暫時(shí)可以不寫(xiě))
????home:是該應(yīng)用啟動(dòng)時(shí)顯示的頁(yè)面,我們傳入了一個(gè)Scaffold;
Scaffold是什么呢?
????翻譯過(guò)來(lái)是腳手架,腳手架的作用就是搭建頁(yè)面的基本結(jié)構(gòu);
????所以我們給MaterialApp的home屬性傳入了一個(gè)Scaffold對(duì)象,作為啟動(dòng)顯示的Widget;
????Scaffold也有一些屬性,比如appBar和body;
????appBar是用于設(shè)計(jì)導(dǎo)航欄的,我們傳入了一個(gè)title屬性;
????body是頁(yè)面的內(nèi)容部分,我們傳入了之前已經(jīng)創(chuàng)建好的Center中包裹的一個(gè)Text的Widget;
2.3.3. 進(jìn)階案例實(shí)現(xiàn)
我們可以讓界面中存在更多的元素:
????寫(xiě)到這里的時(shí)候,你可能已經(jīng)發(fā)現(xiàn)嵌套太多了,不要著急,我們后面會(huì)對(duì)代碼重構(gòu)的
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(
? ? ? ? ? ? ? ? "同意協(xié)議",
? ? ? ? ? ? ? ? textDirection: TextDirection.ltr,
? ? ? ? ? ? ? ? style: TextStyle(fontSize: 20),
? ? ? ? ? ? ? )
? ? ? ? ? ? ],
? ? ? ? ? ),
? ? ? ? ),
? ? ? ),
? ? )
? );
}
2.4. 代碼重構(gòu)
2.4.1. 創(chuàng)建自己的Widget
????很多學(xué)習(xí)Flutter的人,都會(huì)被Flutter的嵌套勸退,當(dāng)代碼嵌套過(guò)多時(shí),結(jié)構(gòu)很容易看不清晰。
這里有兩點(diǎn)我先說(shuō)明一下:
????1、Flutter整個(gè)開(kāi)發(fā)過(guò)程中就是形成一個(gè)Widget樹(shù),所以形成嵌套是很正常的。
????2、關(guān)于Flutter的代碼縮進(jìn),更多開(kāi)發(fā)中我們使用的是2個(gè)空格(前端開(kāi)發(fā)2個(gè)空格居多,你喜歡4個(gè)也沒(méi)問(wèn)題)
但是,我們開(kāi)發(fā)一個(gè)這么簡(jiǎn)單的程序就出現(xiàn)如此多的嵌套,如果應(yīng)用程序更復(fù)雜呢?
????我們可以對(duì)我們的代碼進(jìn)行封裝,將它們封裝到自己的Widget中,創(chuàng)建自己的Widget;
如何創(chuàng)建自己的Widget呢?
在Flutter開(kāi)發(fā)中,我們可以繼承自StatelessWidget或者StatefulWidget來(lái)創(chuàng)建自己的Widget類;
????StatelessWidget: 沒(méi)有狀態(tài)改變的Widget,通常這種Widget僅僅是做一些展示工作而已;
????StatefulWidget: 需要保存狀態(tài),并且可能出現(xiàn)狀態(tài)改變的Widget;
在上面的案例中對(duì)代碼的重構(gòu),我們使用StatelessWidget即可,所以我們接下來(lái)學(xué)習(xí)一下如果利用StatelessWidget來(lái)對(duì)我們的代碼進(jìn)行重構(gòu);
StatefulWidget我們放到后面的一個(gè)案例中來(lái)學(xué)習(xí);
2.4.2. StatelessWidget
StatelessWidget通常是一些沒(méi)有狀態(tài)(State,也可以理解成data)需要維護(hù)的Widget:
????它們的數(shù)據(jù)通常是直接寫(xiě)死(放在Widget中的數(shù)據(jù),必須被定義為final,為什么呢?我在下一個(gè)章節(jié)講解StatefulWidget會(huì)講到);
????從parent widget中傳入的而且一旦傳入就不可以修改;
????從InheritedWidget獲取來(lái)使用的數(shù)據(jù)(這個(gè)放到后面會(huì)講解);
我們來(lái)看一下創(chuàng)建一個(gè)StatelessWidget的格式:
????1、讓自己創(chuàng)建的Widget繼承自StatelessWidget;
????2、StatelessWidget包含一個(gè)必須重寫(xiě)的方法:build方法;
class MyStatelessWidget extends StatelessWidget {
? @override
? Widget build(BuildContext context) {
? ? return <返回我們的Widget要渲染的Widget,比如一個(gè)Text Widget>;
? }
}
build方法的解析:
????Flutter在拿到我們自己創(chuàng)建的StatelessWidget時(shí),就會(huì)執(zhí)行它的build方法;
????我們需要在build方法中告訴Flutter,我們的Widget希望渲染什么元素,比如一個(gè)Text Widget;
????StatelessWidget沒(méi)辦法主動(dòng)去執(zhí)行build方法,當(dāng)我們使用的數(shù)據(jù)發(fā)生改變時(shí),build方法會(huì)被重新執(zhí)行;
build方法什么情況下被執(zhí)行呢?:
????1、當(dāng)我們的StatelessWidget第一次被插入到Widget樹(shù)中時(shí)(也就是第一次被創(chuàng)建時(shí));
????2、當(dāng)我們的父Widget(parent widget)發(fā)生改變時(shí),子Widget會(huì)被重新構(gòu)建;
????3、如果我們的Widget依賴InheritedWidget的一些數(shù)據(jù),InheritedWidget數(shù)據(jù)發(fā)生改變時(shí);
2.4.3. 重構(gòu)案例代碼
現(xiàn)在我們就可以通過(guò)StatelessWidget來(lái)對(duì)我們的代碼進(jìn)行重構(gòu)了
????因?yàn)槲覀兊恼麄€(gè)代碼都是一些數(shù)據(jù)展示,沒(méi)有數(shù)據(jù)的改變,使用StatelessWidget即可;
????另外,為了體現(xiàn)更好的封裝性,我對(duì)代碼進(jìn)行了兩層的拆分,讓代碼結(jié)構(gòu)看起來(lái)更加清晰;(具體的拆分方式,我會(huì)在后面的案例中不斷的體現(xiàn)出來(lái),目前我們先拆分兩層)
重構(gòu)后的代碼如下:
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(
? ? ? ? ? ? "同意協(xié)議",
? ? ? ? ? ? textDirection: TextDirection.ltr,
? ? ? ? ? ? style: TextStyle(fontSize: 20),
? ? ? ? ? )
? ? ? ? ],
? ? ? ),
? ? );
? }
}
三. 案例練習(xí)
3.1. 案例最終效果
我們先來(lái)看一下案例的最終展示效果:
????這個(gè)效果中我們會(huì)使用很多沒(méi)有接觸的Widget;
????沒(méi)有關(guān)系,后面這些常用的Widget我會(huì)一個(gè)個(gè)講解;
????這個(gè)案例最主要的目的還是讓大家更加熟悉Flutter的開(kāi)發(fā)模式以及自定義Widget的封裝過(guò)程;
3.2. 自定義Widget
在我們的案例中,很明顯一個(gè)產(chǎn)品的展示就是一個(gè)大的Widget,這個(gè)Widget包含如下Widget:
????標(biāo)題的Widget:使用一個(gè)Text Widget完成;
????描述的Widget:使用一個(gè)Text Widget完成;
????圖片的Widget:使用一個(gè)Image Widget完成;
????上面三個(gè)Widget要垂直排列,我們可以使用一個(gè)Column的Widget(上一個(gè)章節(jié)中我們使用了一次Row是水平排列的)
另外,三個(gè)展示的標(biāo)題、描述、圖片都是不一樣的,所以我們可以讓Parent Widget來(lái)決定內(nèi)容:
????創(chuàng)建三個(gè)成員變量保存父Widget傳入的數(shù)據(jù)
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. 列表數(shù)據(jù)展示
現(xiàn)在我們就可以創(chuàng)建三個(gè)ProductItem來(lái)讓他們展示了:
????MyApp和上一個(gè)章節(jié)是一致的,沒(méi)有任何改變;
????HomeContent中,我們使用了一個(gè)Column,因?yàn)槲覀儎?chuàng)建的三個(gè)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"),
? ? ? ],
? ? );
? }
}
運(yùn)行效果如下:
????錯(cuò)誤信息:下面出現(xiàn)了黃色的斑馬線;
????這是因?yàn)樵贔lutter的布局中,內(nèi)容是不能超出屏幕范圍的,當(dāng)超出時(shí)不會(huì)自動(dòng)變成滾動(dòng)效果,而是會(huì)報(bào)下面的錯(cuò)誤;
如何可以解決這個(gè)問(wèn)題呢?
????我們將Column換成ListView即可;
????ListView可以讓自己的子Widget變成滾動(dòng)的效果;
3.4. 案例細(xì)節(jié)調(diào)整
3.4.1. 界面整體邊距
如果我們希望整個(gè)內(nèi)容距離屏幕的邊緣有一定的間距,怎么做呢?
????我們需要使用另外一個(gè)Widget:Padding,它有一個(gè)padding屬性用于設(shè)置邊距大??;
????沒(méi)錯(cuò),設(shè)置內(nèi)邊距也是使用Widget,這個(gè)Widget就是Padding;
3.4.2. 商品內(nèi)邊距和邊框
我們現(xiàn)在希望給所有的商品也添加一個(gè)內(nèi)邊距,并且還有邊框,怎么做呢?
????我們可以使用一個(gè)Container的Widget,它里面有padding屬性,并且可以通過(guò)decoration來(lái)設(shè)置邊框;
????Container我們也會(huì)在后面詳細(xì)來(lái)講,我們先用起來(lái);
3.4.3. 文字圖片的間距
我們希望給圖片和文字之間添加一些間距,怎么做呢?
????方式一:給圖片或者文字添加一個(gè)向上的內(nèi)邊距或者向下的內(nèi)邊距;
????方式二:使用SizedBox的Widget,設(shè)置一個(gè)height屬性,可以增加一些距離;
3.5. 最終實(shí)現(xiàn)代碼
最后,我給出最終實(shí)現(xiàn)代碼:
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)
? ? ? ? ],
? ? ? ),
? ? );
? }
}