Flutter 佈局之企鵝電競

蒙娜lisa發表於2018-06-22

本人主要在知乎上釋出相關Flutter文章,知乎瞭解下:

https://www.zhihu.com/people/qiang-fu-5-67/activities

我們來實戰剖析下“企鵝電競”直播欄下怎麼實現:

程式碼我儘量寫的大家淺顯易懂,不裝逼,不多嗶嗶,點關注不迷路。

企鵝電競app截圖:

Flutter 佈局之企鵝電競

下面看下最終實現的效果圖:

Flutter 佈局之企鵝電競

佈局解析成下面一個簡單的圖例:

整體佈局結構如下:

Flutter 佈局之企鵝電競

我們把內容部分拆分成了幾個方法體:

_buildReminder()實現提醒內容區域==>預定按鈕那一行
_buildRecommedList()實現橫向的推薦列表==>吸金榜,周禮榜,真愛榜
_buildContentImageText()實現直播推薦下方的網格列表中的一個單元格內容
複製程式碼

在實踐過程中用了好幾個實現方式,

其中一種是ScrollView方式,在Flutter中通過CustomScrollView實現,小部件使用SliveFixedExtendList和SliverGrid。

這種實現方式寫完才發現,尼瑪grid不支援自定義title,不像Android中recycleView那麼好,這尼瑪就和android中的GridView大差不差,另外Fluterr中也有GridView,也是網格佈局。

然後推翻了這個實現方式。

另一箇中實現方式也差不多,也是gridView無法新增水平title,我實現的是ListView巢狀GridView,但是這裡要注意的是,你需要禁用GridView滑動,否則會和ListView滑動衝突。

所有滾動元件都有一個叫physics的屬性,我們增加一個

physics: new NeverScrollableScrollPhysics(),//禁用滾動
複製程式碼

這個是一開始實現的方案。

最後還是使用ListView實現吧,圖片效果網格部分列表怎麼實現呢?使用一個標題加4個網格單元當成一個item實現就行了。

針對本章節運用到的新控制元件和方法進行一個講解:

一、Flutter中shape的使用:

BoxDecoration:描述如何繪製容器,Container與BoxDecoration配合來裝飾 background, border, or shadow。

new Center(
          child: new Container(
            width: 50.0,
            height: 50.0,
            decoration: new BoxDecoration(
              //背景色
              color: const Color(0xff7c94b6),
              //沒有圖片的小夥,註釋掉image這個,用color背景也是可以看效果的
              image: new DecorationImage(
                image: new ExactAssetImage('images/mozi.jpeg'),
                fit: BoxFit.cover,
              ),
              //shape型別:rectangle|circle
              shape: BoxShape.rectangle,
              //邊框顏色
              border: new Border.all(
                color: Colors.red,//邊框顏色
                width: 2.0,//邊框寬度
              ),
            ),
          ),
        )
複製程式碼

主要是在容器中使用BoxDecoration進行繪製,如果不指定borderRadius 那麼容器就是一個矩形。

如果設定shape引數,BoxShape.rectangle:矩形,BoxShape.circle:圓形

我們通過改變borderRadius值來變化shape的弧度。

我們可以BoxDecoration的屬性borderRadius中配置一個邊界半徑:

borderRadius: new BorderRadius.all(new Radius.circular(15.0))
複製程式碼

Radius.circular構造一個圓的半徑。

下面我們實現下“預定按鈕區域”:

 ///預訂按鈕區域
      new Container(
        //設定容器邊距
        padding: const EdgeInsets.only(top: 21.0, left: 20.0),
        child: new Container(
          //容器中小部件居中
          alignment: Alignment.center,
          //設定小部件距離容器的邊距
          padding: const EdgeInsets.fromLTRB(14.0, 7.0, 14.0, 7.0),
          //設定裝飾器
          decoration: new BoxDecoration(
            //設定邊界
            border: new Border.all(
              color: Colors.black38,
              width: 1.0,
            ),
            //設定邊界半徑
            borderRadius: new BorderRadius.all(new Radius.circular(50.0)),
          ),
          child: new Row(
            //按鈕和預訂文字水平排列顯示
            children: <Widget>[
              new Icon(Icons.timer, color: Colors.black38, size: 12.0),
              new Text(
                "預訂",
                style: new TextStyle(fontSize: 13.0),
              )
            ],
          ),
        ),
      )
複製程式碼

同樣的,推薦列表中使用者頭像也是同樣的道理:

         new Container(
            //外邊距,如果用padding的話頭像會變形
            margin: new EdgeInsets.symmetric(horizontal: 10.0),
            //需要定容器寬高,否則CircleAvatar裁剪出來的圖片很小
            width: 40.6,
            height: 40.6,
            //新增一個邊框
            decoration: new BoxDecoration(
                shape: BoxShape.circle,
                //設定邊框顏色
                border: new Border.all(
                  width: 1.0,
                  color: Colors.yellow,
                )),
            child: new CircleAvatar(//圓角頭像小部件
              radius: 5.0,
              //AssetBundleImageProvider
              backgroundImage: new AssetImage(
                assetName,//動態傳入進來,如'images/chenhe.jpg'
              ),
            ),
          )
複製程式碼

二、Flutter中Stack使用,類似Android中FrameLayout控制元件:

new Center(
          child: new Stack(
            children: <Widget>[
              widget1,
              widget2,
              widget3,
              ......
            ],
          ),
        )
複製程式碼

上面這段程式碼顯示出來的小部件都疊加在一起,預設對齊方式是左上角,Stack控制元件本身包含所有不定位的子控制元件,

我們可以通過Stack的alignment讓內部所有的子控制元件對齊方式改變。

在做上面企鵝電競效果的時候,Expanded啊,Container啊,等等控制元件,內部各種屬性用了遍,無法讓其他小部件在Stack內部進行定位到某個位置。就一個滑鼠點選的操作,Stack原始碼中有告訴我們哪個控制元件可以對Stack內部的小部件進行定位。

Positioned程式碼的註釋:

A widget that controls where a child of a [Stack] is positioned.

通過子控制元件的top、right、bottom和left屬性將它們定位在Stack控制元件不同位置處。

Flutter 佈局之企鵝電競

程式碼樣例如下:

new Container(
          width: double.infinity,
          height: double.infinity,
          child: new Stack(
            children: <Widget>[
              new Container(
                width: 100.0,
                height: 80.0,
                color: Colors.green,
              ),
              new Container(
                width: 50.0,
                height: 50.0,
                color: Colors.orangeAccent,
              ),
              new Positioned(
                  right: 150.0,
                  bottom: 280.0,
                  child: new Container(
                    width: 100.0,
                    height: 100.0,
                    color: Colors.lime,
              )),
              new Positioned(
                  right: 50.0,
                  bottom: 100.0,
                  child: new Container(
                    width: 60.0,
                    height: 60.0,
                    color: Colors.deepPurpleAccent,
                  )),
            ],
          ),
        )
複製程式碼

三、旋轉控制元件RotatedBox:

截圖效果中有一個抽獎中的tag背景,我拿到企鵝電競app內的小圖示,它是反過來的,我懶得轉方向,考慮程式怎麼旋轉,順便讓大家瞭解下怎麼旋轉這個圖片。

Flutter 佈局之企鵝電競

RotatedBox:旋轉內部小部件,上面的圖片我們需要順時針旋轉2次即可。

const RotatedBox({
    Key key,
    @required this.quarterTurns,
    Widget child,
  }) : assert(quarterTurns != null),
       super(key: key, child: child);
複製程式碼

quarterTurns這個屬性值代表的是:旋轉的次數;每旋轉一次走順時針方向的四分之一;

我們通過如下程式碼實現了,抽獎中的tag效果:

new Stack(
                alignment: Alignment.center,
                children: <Widget>[
                  //RotatedBox:旋轉內部小部件;
                  //quarterTurns:旋轉的次數;每旋轉一次走順時針方向的四分之一;
                  new RotatedBox(quarterTurns: 2,child: new Image.asset('images/battle_status_bg_yellow.9.png',width: 45.0,height: 20.0,fit: BoxFit.fill,),),
                  new Text('抽獎中',style: new TextStyle(fontSize: 9.0,color: Colors.black),),
                ],
              )
複製程式碼

下面來實現企鵝電競直播欄下面的佈局效果:

1.入口無狀態小部件來一波,瞭解下:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('企鵝電競佈局實戰篇一'),
        ),
        body: new ListView.builder(
          itemBuilder: (BuildContext context, int index) {
            return index == 0 ? buildHeader() : buildContent(index);
          },
          itemCount: 3,
        ),
      ),
    );
  }
}
複製程式碼

2.來構建下列表頭部內容:

Widget buildHeader() {
  return new Container(
    alignment: Alignment.topLeft,
    child: new Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        //佈局輪播圖區域,本篇只講簡單的佈局,不做輪播圖介紹
        //設定寬度最大,高度150畫素,裁剪方式:居中裁剪
        new Image.asset(
          'images/lake.jpg',
          width: double.infinity,
          height: 126.6,
          fit: BoxFit.cover,
        ),
        _buildReminder(),
        _buildRecommendList(),
      ],
    ),
  );
}
複製程式碼

_buildReminder()和_buildRecommendList()這兩個有了上面的講解自己就可以實現了,最下面我會附上github程式碼地址。

3.構建網格列表body體,瞭解下:

Widget buildContent(int index) {
  return new Column(
    children: <Widget>[
      new Container(
        margin: const EdgeInsets.all(15.0),
        child: new Row(
          children: <Widget>[
            //強制子類填充可用空間==match_parent
            new Expanded(
                child: new Text(
              '直播推薦',
              textAlign: TextAlign.left,
              style: new TextStyle(
                fontSize: 15.0,
                fontWeight: FontWeight.w500,
                fontFamily: 'Roboto',
              ),
            )),
            new Expanded(
                child: new Text(
              '重新整理',
              textAlign: TextAlign.right,
              style: new TextStyle(
                  fontSize: 12.0,
                  color: const Color.fromARGB(255, 136, 136, 153)),
            ))
          ],
        ),
      ),
      new Row(
        children: <Widget>[
          //強制填充剩餘空間
          new Expanded(
              child: new Container(
            margin: const EdgeInsets.only(right: 1.5),
            child: _buildContentImageText(
                'images/zhubo01.jpg', '新進主播,多多關注', 'Dae-安格', 16.6),
          )),
          new Expanded(
            child: new Container(
              margin: const EdgeInsets.only(left: 1.5),
              child: _buildContentImageText(
                  'images/zhubo02.jpeg', '國服李白,瞭解一下', 'EL-溜神', 52.1),
            ),
          ),
        ],
      ),
      new Row(
        children: <Widget>[
          new Expanded(
              child: new Container(
            margin: const EdgeInsets.only(right: 1.5),
            child: _buildContentImageText(
                'images/zhubo03.jpeg', '貂蟬帶你五殺', '呂布別走\(^o^)/~', 5.9),
          )),
          new Expanded(
              child: new Container(
            margin: const EdgeInsets.only(left: 1.5),
            child: _buildContentImageText(
                'images/zhubo04.jpeg', '國服最騷香香', '國服最騷香香', 11.1),
          ))
        ],
      )
    ],
  );
}
複製程式碼

上述程式碼中Expanded作用是:填充剩餘可用空間==Match_parent

網格列表body體,佈局結構:

Column{
  Container子widget1,<外邊距,child{
   Row{
     Expanded包裹一個Text文字(直播),
     Expanded包裹一個Text文字(重新整理)
   }
  }>
  Row子widget2,------>_buildContentImageText()
  Row子widget3 ------->_buildContentImageText()
}
複製程式碼

_buildContentImageText()方法主要針對Stack的使用

Widget _buildContentImageText(
    String asserPath, String desc, String username, double onlinePopulation) {
  return new Container(
    alignment: Alignment.center,
    child: new Column(
      children: <Widget>[
        new Stack(
          children: <Widget>[
            //封面圖
            new Image.asset(
              asserPath,
              fit: BoxFit.cover,
            ),
            //抽獎標識
            new Container(
              alignment: Alignment.topRight,
              padding: const EdgeInsets.only(top: 5.0),
              child: new Stack(
                alignment: Alignment.center,
                children: <Widget>[
                  //RotatedBox:旋轉內部小部件;
                  //quarterTurns:旋轉的次數;每旋轉一次走順時針方向的四分之一;
                  new RotatedBox(
                    quarterTurns: 2,
                    child: new Image.asset(
                      'images/battle_status_bg_yellow.9.png',//Tag背景圖片
                      width: 45.0,
                      height: 20.0,
                      fit: BoxFit.fill,
                    ),
                  ),
                  new Text(
                    '抽獎中',
                    style: new TextStyle(fontSize: 9.0, color: Colors.black),
                  ),
                ],
              ),
            ),
            //使用者名稱和人氣值
            new Positioned(
              //控制[Stack]子部件位置的小部件
              left: 15.0,
              right: 11.0,
              bottom: 7.0,
              child: new Row(
                children: <Widget>[
                  //填充剩餘空間
                  new Expanded(
                      child: new Text(
                    username,
                    textAlign: TextAlign.left,
                    style: new TextStyle(
                      fontSize: 8.0,
                      color: Colors.white,
                    ),
                  )),
                  new Expanded(
                      child: new Text(
                    '$onlinePopulation 萬人氣',
                    textAlign: TextAlign.right,
                    style: new TextStyle(fontSize: 8.0, color: Colors.white),
                  ))
                ],
              ),
            ),
          ],
        ),
        //網格圖片下面的文字介紹
        new Container(
          margin: const EdgeInsets.only(top: 7.0, bottom: 16.0),
          child: new Text(
            desc,
            style: new TextStyle(
              color: Colors.black,
              fontSize: 12.0,
              fontWeight: FontWeight.w500,
            ),
          ),
        )
      ],
    ),
  );
}
複製程式碼


完整程式碼附上github地址連結:TheMelody/Flutter_PenguinSports01


相關文章