用Flutter構(gòu)建漂亮的UI界面 - 基礎(chǔ)組件篇

1. 前言

Flutter作為時(shí)下最流行的技術(shù)之一良姆,憑借其出色的性能以及抹平多端的差異優(yōu)勢(shì)爵赵,早已引起大批技術(shù)愛(ài)好者的關(guān)注垮媒,甚至一些閑魚(yú)吮成,美團(tuán)玻驻,騰訊等大公司均已投入生產(chǎn)使用竹捉。雖然目前其生態(tài)還沒(méi)有完全成熟违帆,但身靠背后的Google加持挣磨,其發(fā)展速度已經(jīng)足夠驚人豪直,可以預(yù)見(jiàn)將來(lái)對(duì)Flutter開(kāi)發(fā)人員的需求也會(huì)隨之增長(zhǎng)劣摇。

無(wú)論是為了現(xiàn)在的技術(shù)嘗鮮還是將來(lái)的潮流趨勢(shì),都9102年了弓乙,作為一個(gè)前端開(kāi)發(fā)者末融,似乎沒(méi)有理由不去嘗試它。正是帶著這樣的心理暇韧,筆者也開(kāi)始學(xué)習(xí)Flutter勾习,同時(shí)建了一個(gè)用于練習(xí)的倉(cāng)庫(kù),后續(xù)所有代碼都會(huì)托管在上面懈玻,歡迎star巧婶,一起學(xué)習(xí)。這是我寫的Flutter系列文章:

今天分享的是Flutter中最常用到的一些基礎(chǔ)組件涂乌,它們是構(gòu)成UI界面的基礎(chǔ)元素:容器艺栈,湾盒,絕對(duì)定位布局湿右,文本圖片圖標(biāo)等罚勾。

2. 基礎(chǔ)組件

2.1 Container(容器組件)

Container組件是最常用的布局組件之一毅人,可以認(rèn)為它是web開(kāi)發(fā)中的div吭狡,rn開(kāi)發(fā)中的View。其往往可以用來(lái)控制大小堰塌、背景顏色赵刑、邊框、陰影场刑、內(nèi)外邊距和內(nèi)容排列方式等般此。我們先來(lái)看下其構(gòu)造函數(shù):

Container({
  Key key,
  double width,
  double height,
  this.margin,
  this.padding,
  Color color,
  this.alignment,
  BoxConstraints constraints,
  Decoration decoration,
  this.foregroundDecoration,
  this.transform,
  this.child,
})

2.1.1 widthheight牵现,margin铐懊,padding

這些屬性的含義和我們已經(jīng)熟知的并沒(méi)有區(qū)別。唯一需要注意的是瞎疼,marginpadding的賦值不是一個(gè)簡(jiǎn)單的數(shù)字科乎,因?yàn)槠溆?code>left, top, right, bottom四個(gè)方向的值需要設(shè)置。Flutter提供了EdgeInsets這個(gè)類贼急,幫助我們方便地生成四個(gè)方向的值茅茂。通常情況下,我們可能會(huì)用到EdgeInsets的4種構(gòu)造方法:

  • EdgeInsets.all(value): 用于設(shè)置4個(gè)方向一樣的值太抓;
  • EdgeInsets.only(left: val1, top: val2, right: val3, bottom: val4): 可以單獨(dú)設(shè)置某個(gè)方向的值空闲;
  • EdgeInsets.symmetric(horizontal: val1, vertical: val2): 用于設(shè)置水平/垂直方向上的值;
  • EdgeInsets.fromLTRB(left, top, right, bottom): 按照左上右下的順序設(shè)置4個(gè)方向的值走敌。

2.1.2 color

該屬性的含義是背景顏色碴倾,等同于web/rn中的backgroundColor。需要注意的是Flutter中有一個(gè)專門表示顏色的Color類掉丽,而非我們常用的字符串跌榔。不過(guò)我們可以非常輕松地進(jìn)行轉(zhuǎn)換,舉個(gè)栗子:

在web/rn中我們會(huì)用'#FF0000''red'來(lái)表示紅色捶障,而在Flutter中僧须,我們可以用Color(0xFFFF0000)Colors.red來(lái)表示。

2.1.3 alignment

該屬性是用來(lái)決定Container組件的子組件將以何種方式進(jìn)行排列(PS:再也不用為怎么居中操心了)残邀。其可選值通常會(huì)用到:

  • Alignment.topLeft: 左上
  • Alignment.topCenter: 上中
  • Alignment.topRight: 右上
  • Alignment.centerLeft: 左中
  • Alignment.center: 居中
  • Alignment.centerRight: 右中
  • Alignment.bottomLeft: 左下
  • Alignment.bottomCenter: 下中
  • Alignment.bottomRight: 右下

2.1.4 constraints

在web/rn中我們通常會(huì)用minWidth/maxWidth/minHeight/maxHeight等屬性來(lái)限制容器的寬高皆辽。在Flutter中,你需要使用BoxConstraints(盒約束)來(lái)實(shí)現(xiàn)該功能芥挣。

// 容器的大小將被限制在[100*100 ~ 200*200]內(nèi)
BoxConstraints(
  minWidth: 100,
  maxWidth: 200,
  minHeight: 100,
  maxHeight: 200,
)

2.1.5 decoration

該屬性非常強(qiáng)大驱闷,字面意思是裝飾,因?yàn)橥ㄟ^(guò)它你可以設(shè)置邊框空免,陰影空另,漸變圓角等常用屬性蹋砚。BoxDecoration繼承自Decoration類扼菠,因此我們通常會(huì)生成一個(gè)BoxDecoration實(shí)例來(lái)設(shè)置這些屬性摄杂。

1) 邊框

可以用Border.all構(gòu)造函數(shù)直接生成4條邊框,也可以用Border構(gòu)造函數(shù)單獨(dú)設(shè)置不同方向上的邊框循榆。不過(guò)令人驚訝的是官方提供的邊框竟然不支持虛線issue在這里)析恢。

// 同時(shí)設(shè)置4條邊框:1px粗細(xì)的黑色實(shí)線邊框
BoxDecoration(
  border: Border.all(color: Colors.black, width: 1, style: BorderStyle.solid)
)

// 設(shè)置單邊框:上邊框?yàn)?px粗細(xì)的黑色實(shí)線邊框,右邊框?yàn)?px粗細(xì)的紅色實(shí)線邊框
BoxDecoration(
  border: Border(
    top: BorderSide(color: Colors.black, width: 1, style: BorderStyle.solid),
    right: BorderSide(color: Colors.red, width: 1, style: BorderStyle.solid),
  ),
)

2) 陰影

陰影屬性和web中的boxShadow幾乎沒(méi)有區(qū)別秧饮,可以指定x映挂,yblur盗尸,spread柑船,color等屬性。

BoxDecoration(
  boxShadow: [
    BoxShadow(
      offset: Offset(0, 0),
      blurRadius: 6,
      spreadRadius: 10,
      color: Color.fromARGB(20, 0, 0, 0),
    ),
  ],
)

3) 漸變

如果你不想容器的背景顏色是單調(diào)的泼各,可以嘗試用gradient屬性鞍时。Flutter同時(shí)支持線性漸變徑向漸變

// 從左到右,紅色到藍(lán)色的線性漸變
BoxDecoration(
  gradient: LinearGradient(
    begin: Alignment.centerLeft,
    end: Alignment.centerRight,
    colors: [Colors.red, Colors.blue],
  ),
)

// 從中心向四周擴(kuò)散扣蜻,紅色到藍(lán)色的徑向漸變
BoxDecoration(
  gradient: RadialGradient(
    center: Alignment.center,
    colors: [Colors.red, Colors.blue],
  ),
)

4) 圓角

通常情況下逆巍,你可能會(huì)用到BorderRadius.circular構(gòu)造函數(shù)來(lái)同時(shí)設(shè)置4個(gè)角的圓角,或是BorderRadius.only構(gòu)造函數(shù)來(lái)單獨(dú)設(shè)置某幾個(gè)角的圓角:

// 同時(shí)設(shè)置4個(gè)角的圓角為5
BoxDecoration(
  borderRadius: BorderRadius.circular(5),
)

// 設(shè)置單圓角:左上角的圓角為5莽使,右上角的圓角為10
BoxDecoration(
  borderRadius: BorderRadius.only(
    topLeft: Radius.circular(5),
    topRight: Radius.circular(10),
  ),
)

2.1.6 transform

transform屬性和我們?cè)趙eb/rn中經(jīng)常用到的基本也沒(méi)有差別蒸苇,主要包括:平移縮放吮旅、旋轉(zhuǎn)傾斜。在Flutter中味咳,封裝了矩陣變換類Matrix4幫助我們進(jìn)行變換:

  • translationValues(x, y, z): 平移x, y, z庇勃;
  • rotationX(radians): x軸旋轉(zhuǎn)radians弧度;
  • rotationY(radians): y軸旋轉(zhuǎn)radians弧度槽驶;
  • rotationZ(radians): z軸旋轉(zhuǎn)radians弧度责嚷;
  • skew(alpha, beta): x軸傾斜alpha度,y軸傾斜beta度掂铐;
  • skewX(alpha): x軸傾斜alpha度罕拂;
  • skewY(beta): y軸傾斜beta度;

2.1.7 小結(jié)

Container組件的屬性很豐富全陨,雖然有些用法上和web/rn有些許差異爆班,但基本上大同小異,所以過(guò)渡起來(lái)也不會(huì)有什么障礙辱姨。另外柿菩,由于Container組件是單子節(jié)點(diǎn)組件,也就是只允許子節(jié)點(diǎn)有一個(gè)雨涛。所以在布局上枢舶,很多時(shí)候我們會(huì)用RowColumn組件進(jìn)行/布局懦胞。

2.2 Row/Column(行/列組件)

RowColumn組件其實(shí)和web/rn中的Flex布局(彈性盒子)特別相似,或者我們可以就這么理解凉泄。使用Flex布局的同學(xué)對(duì)主軸次軸的概念肯定都已經(jīng)十分熟悉躏尉,Row組件的主軸就是橫向,Column組件的主軸就是縱向后众。且它們的構(gòu)造函數(shù)十分相似(已省略不常用屬性):

Row({
  Key key,
  MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
  CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
  MainAxisSize mainAxisSize = MainAxisSize.max,
  List<Widget> children = const <Widget>[],
})

Column({
  Key key,
  MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
  CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
  MainAxisSize mainAxisSize = MainAxisSize.max,
  List<Widget> children = const <Widget>[],
})

2.2.1 mainAxisAlignment

該屬性的含義是主軸排列方式胀糜,根據(jù)上述構(gòu)造函數(shù)可以知道RowColumn組件在主軸方向上默認(rèn)都是從start開(kāi)始,也就是說(shuō)Row組件默認(rèn)從左到右開(kāi)始排列子組件吼具,Column組件默認(rèn)從上到下開(kāi)始排列子組件僚纷。

當(dāng)然,你還可以使用其他的可選值:

  • MainAxisAlignment.start
  • MainAxisAlignment.end
  • MainAxisAlignment.center
  • MainAxisAlignment.spaceBetween
  • MainAxisAlignment.spaceAround
  • MainAxisAlignment.spaceEvenly

2.2.2 crossAxisAlignment

該屬性的含義是次軸排列方式拗盒,根據(jù)上述構(gòu)造函數(shù)可以知道RowColumn組件在次軸方向上默認(rèn)都是居中怖竭。

這里有一點(diǎn)需要特別注意:由于Column組件次軸方向上(即水平)默認(rèn)是居中對(duì)齊,所以水平方向上不會(huì)撐滿其父容器陡蝇,此時(shí)需要指定CrossAxisAlignment.stretch才可以痊臭。

另外,crossAxisAlignment其他的可選值有:

  • crossAxisAlignment.start
  • crossAxisAlignment.end
  • crossAxisAlignment.center
  • crossAxisAlignment.stretch
  • crossAxisAlignment.baseline

2.2.3 mainAxisSize

字面意思上來(lái)說(shuō)登夫,該屬性指的是在主軸上的尺寸广匙。其實(shí)就是指在主軸方向上,是包裹其內(nèi)容恼策,還是撐滿其父容器鸦致。它的可選值有MainAxisSize.minMainAxisSize.max。由于其默認(rèn)值都是MainAxisSize.max涣楷,所以主軸方向上默認(rèn)大小都是盡可能撐滿父容器的分唾。

2.2.4 小結(jié)

由于Row/Column組件和我們熟悉的Flex布局非常相似,所以上手起來(lái)非常容易狮斗,幾乎零學(xué)習(xí)成本绽乔。

2.3 Stack/Positoned(絕對(duì)定位布局組件)

絕對(duì)定位布局在web/rn開(kāi)發(fā)中也是使用頻率較高的一種布局方式,Flutter也提供了相應(yīng)的組件實(shí)現(xiàn)碳褒,需要將StackPositioned組件搭配在一起使用折砸。比如下方的這個(gè)例子就是創(chuàng)建了一個(gè)黃色的盒子,并且在其四個(gè)角落放置了4個(gè)紅色的小正方形沙峻。Stack組件就是絕對(duì)定位的容器睦授,Positioned組件通過(guò)lefttop专酗,right睹逃,bottom四個(gè)方向上的屬性值來(lái)決定其在父容器中的位置。

Container(
  height: 100,
  color: Colors.yellow,
  child: Stack(
    children: <Widget>[
      Positioned(
        left: 10,
        top: 10,
        child: Container(width: 10, height: 10, color: Colors.red),
      ),
      Positioned(
        right: 10,
        top: 10,
        child: Container(width: 10, height: 10, color: Colors.red),
      ),
      Positioned(
        left: 10,
        bottom: 10,
        child: Container(width: 10, height: 10, color: Colors.red),
      ),
      Positioned(
        right: 10,
        bottom: 10,
        child: Container(width: 10, height: 10, color: Colors.red),
      ),
    ],
  ),
)

2.4 Text(文本組件)

Text組件也是日常開(kāi)發(fā)中最常用的基礎(chǔ)組件之一,我們通常用它來(lái)展示文本信息沉填。來(lái)看下其構(gòu)造函數(shù)(已省略不常用屬性):

const Text(
  this.data, {
  Key key,
  this.style,
  this.textAlign,
  this.softWrap,
  this.overflow,
  this.maxLines,
})
  • data: 顯示的文本信息疗隶;
  • style: 文本樣式,Flutter提供了一個(gè)TextStyle類翼闹,最常用的fontSize斑鼻,fontWeightcolor猎荠,backgroundColorshadows等屬性都是通過(guò)它設(shè)置的坚弱;
  • textAlign: 文字對(duì)齊方式,常用可選值有TextAlignleft关摇,right荒叶,centerjustify
  • softWrap: 文字是否換行输虱;
  • overflow: 當(dāng)文字溢出的時(shí)候些楣,以何種方式處理(默認(rèn)直接截?cái)啵宪睹?蛇x值有TextOverflowclip愁茁,fadeellipsisvisible亭病;
  • maxLines: 當(dāng)文字超過(guò)最大行數(shù)還沒(méi)顯示完的時(shí)候鹅很,就會(huì)根據(jù)overflow屬性決定如何截?cái)嗵幚怼?/li>

FlutterText組件足夠靈活,提供了各種屬性讓我們定制罪帖,不過(guò)一般情況下促煮,我們更多地只需下方幾行代碼就足夠了:

Text(
  '這是測(cè)試文本',
  style: TextStyle(
    fontSize: 13,
    fontWeight: FontWeight.bold,
    color: Color(0xFF999999),
  ),
)

除了上述的應(yīng)用場(chǎng)景外,有時(shí)我們還會(huì)遇到富文本的需求(即一段文本中整袁,可能需要不同的字體樣式)污茵。比如在一些UI設(shè)計(jì)中經(jīng)常會(huì)遇到表示價(jià)格的時(shí)候,符號(hào)比金額的字號(hào)小點(diǎn)葬项。對(duì)于此類需求,我們可以用Flutter提供的Text.rich構(gòu)造函數(shù)來(lái)創(chuàng)建相應(yīng)的文本組件:

Text.rich(TextSpan(
  children: [
    TextSpan(
      '¥',
      style: TextStyle(
        fontSize: 12,
        color: Color(0xFFFF7528),
      ),
    ),
    TextSpan(
      '258',
      style: TextStyle(
        fontSize: 15,
        color: Color(0xFFFF7528),
      ),
    ),
  ]
))

2.5 Image(圖片組件)

Image圖片組件作為豐富內(nèi)容的基礎(chǔ)組件之一迹蛤,日常開(kāi)發(fā)中的使用頻率也非常高民珍。看下其構(gòu)造函數(shù)(已省略不常用屬性):

Image({
  Key key,
  @required this.image,
  this.width,
  this.height,
  this.color,
  this.fit,
  this.repeat = ImageRepeat.noRepeat,
})
  • image: 圖片源盗飒,最常用到主要有兩種(AssetImageNetworkImage)嚷量。使用AssetImage之前,需要在pubspec.yaml文件中聲明好圖片資源逆趣,然后才能使用蝶溶;而NextworkImage指定圖片的網(wǎng)絡(luò)地址即可,主要是在加載一些網(wǎng)絡(luò)圖片時(shí)會(huì)用到;
  • width: 圖片寬度抖所;
  • height: 圖片高度梨州;
  • color: 圖片的背景顏色,當(dāng)網(wǎng)絡(luò)圖片未加載完畢之前田轧,會(huì)顯示該背景顏色暴匠;
  • fit: 當(dāng)我們希望圖片根據(jù)容器大小進(jìn)行適配而不是指定固定的寬高值時(shí),可以通過(guò)該屬性來(lái)實(shí)現(xiàn)傻粘。其可選值有BoxFitfill每窖,containcover弦悉,fitWidth窒典,fitHeightnonescaleDown稽莉;
  • repeat: 決定當(dāng)圖片實(shí)際大小不足指定大小時(shí)是否使用重復(fù)效果瀑志。

另外,Flutter還提供了Image.networkImage.asset構(gòu)造函數(shù)肩祥,其實(shí)是語(yǔ)法糖后室。比如下方的兩段代碼結(jié)果是完全一樣的:

Image(
  image: NetworkImage('https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1402367109,4157195964&fm=27&gp=0.jpg'),
  width: 100,
  height: 100,
)

Image.network(
  'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1402367109,4157195964&fm=27&gp=0.jpg',
  width: 100,
  height: 100,
)

2.6 Icon(圖標(biāo)組件)

Icon圖標(biāo)組件相比于圖片有著放大不會(huì)失真的優(yōu)勢(shì),在日常開(kāi)發(fā)中也是經(jīng)常會(huì)被用到混狠。Flutter更是直接內(nèi)置了一套Material風(fēng)格的圖標(biāo)(你可以在這里預(yù)覽所有的圖標(biāo)類型)岸霹。看下構(gòu)造函數(shù):

const Icon(
  this.icon, {
  Key key,
  this.size,
  this.color,
})
  • icon: 圖標(biāo)類型将饺;
  • size: 圖標(biāo)大泄北堋;
  • color: 圖標(biāo)顏色予弧。

3. 布局實(shí)戰(zhàn)

通過(guò)上一節(jié)的介紹刮吧,我們對(duì)ContainerRow掖蛤,Column杀捻,StackPositioned蚓庭,Text致讥,ImageIcon組件有了初步的認(rèn)識(shí)。接下來(lái)器赞,就讓我們通過(guò)一個(gè)實(shí)際的例子來(lái)加深理解和記憶垢袱。

3.1 準(zhǔn)備工作 - 數(shù)據(jù)類型

根據(jù)上述卡片中的內(nèi)容,我們可以定義一些字段港柜。為了規(guī)范開(kāi)發(fā)流程请契,我們先給卡片定義一個(gè)數(shù)據(jù)類型的類,這樣在后續(xù)的開(kāi)發(fā)過(guò)程中也能更好地對(duì)數(shù)據(jù)進(jìn)行Mock和管理:

class PetCardViewModel {
  /// 封面地址
  final String coverUrl;

  /// 用戶頭像地址
  final String userImgUrl;

  /// 用戶名
  final String userName;

  /// 用戶描述
  final String description;

  /// 話題
  final String topic;

  /// 發(fā)布時(shí)間
  final String publishTime;

  /// 發(fā)布內(nèi)容
  final String publishContent;

  /// 回復(fù)數(shù)量
  final int replies;

  /// 喜歡數(shù)量
  final int likes;

  /// 分享數(shù)量
  final int shares;

  const PetCardViewModel({
    this.coverUrl,
    this.userImgUrl,
    this.userName,
    this.description,
    this.topic,
    this.publishTime,
    this.publishContent,
    this.replies,
    this.likes,
    this.shares,
  });
}

3.2 搭建骨架,布局拆分

根據(jù)給的視覺(jué)圖爽锥,我們可以將整體進(jìn)行拆分涌韩,一共劃分成4個(gè)部分:CoverUserInfo救恨,PublishContentInteractionArea贸辈。為此,我們可以搭起代碼的基本骨架:

class PetCard extends StatelessWidget {
  final PetCardViewModel data;

  const PetCard({
    Key key,
    this.data,
  }) : super(key: key);

  Widget renderCover() {
    
  }

  Widget renderUserInfo() {
    
  }

  Widget renderPublishContent() {
  
  }

  Widget renderInteractionArea() {
   
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(8),
        boxShadow: [
          BoxShadow(
            blurRadius: 6,
            spreadRadius: 4,
            color: Color.fromARGB(20, 0, 0, 0),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          this.renderCover(),
          this.renderUserInfo(),
          this.renderPublishContent(),
          this.renderInteractionArea(),
        ],
      ),
    );
  }
}

3.3 封面區(qū)域

為了更好的凸現(xiàn)圖片的效果肠槽,這里加了一個(gè)蒙層擎淤,所以此處剛好可以用得上Stack/Positioned布局和LinearGradient漸變,Dom結(jié)構(gòu)如下:

Widget renderCover() {
  return Stack(
    fit: StackFit.passthrough,
    children: <Widget>[
      ClipRRect(
        borderRadius: BorderRadius.only(
          topLeft: Radius.circular(8),
          topRight: Radius.circular(8),
        ),
        child: Image.network(
          data.coverUrl,
          height: 200,
          fit: BoxFit.fitWidth,
        ),
      ),
      Positioned(
        left: 0,
        top: 100,
        right: 0,
        bottom: 0,
        child: Container(
          decoration: BoxDecoration(
            gradient: LinearGradient(
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
              colors: [
                Color.fromARGB(0, 0, 0, 0),
                Color.fromARGB(80, 0, 0, 0),
              ],
            ),
          ),
        ),
      ),
    ],
  );
}

3.4 用戶信息區(qū)域

用戶信息區(qū)域就非常適合使用RowColumn組件來(lái)進(jìn)行布局秸仙,Dom結(jié)構(gòu)如下:

Widget renderUserInfo() {
  return Container(
    margin: EdgeInsets.only(top: 16),
    padding: EdgeInsets.symmetric(horizontal: 16),
    child: Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: <Widget>[
        Row(
          children: <Widget>[
            CircleAvatar(
              radius: 20,
              backgroundColor: Color(0xFFCCCCCC),
              backgroundImage: NetworkImage(data.userImgUrl),
            ),
            Padding(padding: EdgeInsets.only(left: 8)),
            Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Text(
                  data.userName,
                  style: TextStyle(
                    fontSize: 15,
                    fontWeight: FontWeight.bold,
                    color: Color(0xFF333333),
                  ),
                ),
                Padding(padding: EdgeInsets.only(top: 2)),
                Text(
                  data.description,
                  style: TextStyle(
                    fontSize: 12,
                    color: Color(0xFF999999),
                  ),
                ),
              ],
            ),
          ],
        ),
        Text(
          data.publishTime,
          style: TextStyle(
            fontSize: 13,
            color: Color(0xFF999999),
          ),
        ),
      ],
    ),
  );
}

3.5 發(fā)布內(nèi)容區(qū)域

通過(guò)這塊區(qū)域的UI練習(xí)嘴拢,我們可以實(shí)踐Container組件設(shè)置不同的borderRadius,以及Text組件文本內(nèi)容超出時(shí)的截?cái)嗵幚砑偶停珼om結(jié)構(gòu)如下:

Widget renderPublishContent() {
  return Container(
    margin: EdgeInsets.only(top: 16),
    padding: EdgeInsets.symmetric(horizontal: 16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Container(
          margin: EdgeInsets.only(bottom: 14),
          padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
          decoration: BoxDecoration(
            color: Color(0xFFFFC600),
            borderRadius: BorderRadius.only(
              topRight: Radius.circular(8),
              bottomLeft: Radius.circular(8),
              bottomRight: Radius.circular(8),
            ),
          ),
          child: Text(
            '# ${data.topic}',
            style: TextStyle(
              fontSize: 12,
              color: Colors.white,
            ),
          ),
        ),
        Text(
          data.publishContent,
          maxLines: 2,
          overflow: TextOverflow.ellipsis,
          style: TextStyle(
            fontSize: 15,
            fontWeight: FontWeight.bold,
            color: Color(0xFF333333),
          ),
        ),
      ],
    ),
  );
}

3.6 互動(dòng)區(qū)域

在這個(gè)模塊席吴,我們會(huì)用到Icon圖標(biāo)組件,可以控制其大小和顏色等屬性捞蛋,Dom結(jié)構(gòu)如下:

Widget renderInteractionArea() {
  return Container(
    margin: EdgeInsets.symmetric(vertical: 16),
    padding: EdgeInsets.symmetric(horizontal: 16),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: <Widget>[
        Row(
          children: <Widget>[
            Icon(
              Icons.message,
              size: 16,
              color: Color(0xFF999999),
            ),
            Padding(padding: EdgeInsets.only(left: 6)),
            Text(
              data.replies.toString(),
              style: TextStyle(
                fontSize: 15,
                color: Color(0xFF999999),
              ),
            ),
          ],
        ),
        Row(
          children: <Widget>[
            Icon(
              Icons.favorite,
              size: 16,
              color: Color(0xFFFFC600),
            ),
            Padding(padding: EdgeInsets.only(left: 6)),
            Text(
              data.likes.toString(),
              style: TextStyle(
                fontSize: 15,
                color: Color(0xFF999999),
              ),
            ),
          ],
        ),
        Row(
          children: <Widget>[
            Icon(
              Icons.share,
              size: 16,
              color: Color(0xFF999999),
            ),
            Padding(padding: EdgeInsets.only(left: 6)),
            Text(
              data.shares.toString(),
              style: TextStyle(
                fontSize: 15,
                color: Color(0xFF999999),
              ),
            ),
          ],
        ),
      ],
    ),
  );
}

3.7 小結(jié)

通過(guò)上面的一個(gè)例子孝冒,我們成功地把一個(gè)看起來(lái)復(fù)雜的UI界面一步步拆解,將之前提到的組件都用了個(gè)遍拟杉,并且最終得到了不錯(cuò)的效果庄涡。其實(shí),日常開(kāi)發(fā)中90%以上的需求都離不開(kāi)上述提到的基礎(chǔ)組件搬设。因此穴店,只要稍加練習(xí),熟悉了Flutter中的基礎(chǔ)組件用法拿穴,就已經(jīng)算是邁出了一大步哦~

這里還有銀行卡朋友圈的UI練習(xí)例子泣洞,由于篇幅原因就不貼代碼了,可以去github倉(cāng)庫(kù)看默色。

4. 總結(jié)

本文首先介紹了Flutter中構(gòu)建UI界面最常用的基礎(chǔ)組件(容器球凰,腿宰,絕對(duì)定位布局弟蚀,文本圖片圖標(biāo))用法酗失。接著,介紹了一個(gè)較復(fù)雜的UI實(shí)戰(zhàn)例子昧绣。通過(guò)對(duì)Dom結(jié)構(gòu)的層層拆解规肴,前文提到過(guò)的組件得到一個(gè)綜合運(yùn)用,也算是鞏固了前面所學(xué)的概念知識(shí)。

不過(guò)最后不得不吐槽一句:Flutter的嵌套真的很難受拖刃。删壮。。如果不對(duì)UI布局進(jìn)行模塊拆分兑牡,那絕對(duì)是噩夢(mèng)般的體驗(yàn)央碟。而且不像web/rn開(kāi)發(fā)樣式可以單獨(dú)抽離,Flutter這種將樣式當(dāng)做屬性的處理方式均函,一眼看去真的很難理清dom結(jié)構(gòu)亿虽,對(duì)于新接手代碼的開(kāi)發(fā)人員而言,需要費(fèi)點(diǎn)時(shí)間理解苞也。洛勉。。

本文所有代碼托管在這兒如迟,也可以關(guān)注我的Blog收毫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市殷勘,隨后出現(xiàn)的幾起案子此再,更是在濱河造成了極大的恐慌,老刑警劉巖玲销,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件输拇,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡痒玩,警方通過(guò)查閱死者的電腦和手機(jī)淳附,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蠢古,“玉大人奴曙,你說(shuō)我怎么就攤上這事〔菅龋” “怎么了洽糟?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)堕战。 經(jīng)常有香客問(wèn)我坤溃,道長(zhǎng),這世上最難降的妖魔是什么嘱丢? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任薪介,我火速辦了婚禮,結(jié)果婚禮上越驻,老公的妹妹穿的比我還像新娘汁政。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布刽射。 她就那樣靜靜地躺著吻谋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上低千,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天难审,我揣著相機(jī)與錄音,去河邊找鬼黔姜。 笑死五慈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的爷肝。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起通殃,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤已慢,失蹤者是張志新(化名)和其女友劉穎句葵,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體轻专,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宗收,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年匈勋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片璃俗。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡苟穆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出唱星,到底是詐尸還是另有隱情雳旅,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布间聊,位于F島的核電站攒盈,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏哎榴。R本人自食惡果不足惜型豁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望尚蝌。 院中可真熱鬧迎变,春花似錦、人聲如沸飘言。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)姿鸿。三九已至谆吴,卻和暖如春倒源,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背句狼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工笋熬, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人腻菇。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓突诬,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親芜繁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容