Flutter開(kāi)發(fā)(3)- 第一個(gè)Flutter項(xiàng)目

一. 創(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)

? ? ? ? ],

? ? ? ),

? ? );

? }

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容