Flutter 布局之企鹅电竞

本人主要在知乎上发布相关Flutter文章,知乎了解下:

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

我们来实战剖析下“企鹅电竞”直播栏下怎么实现:

代码我尽量写的大家浅显易懂,不装逼,不多哔哔,点关注不迷路。

企鹅电竞app截图:

下面看下最终实现的效果图:

布局解析成下面一个简单的图例:

整体布局结构如下:

我们把内容部分拆分成了几个方法体:

_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控件不同位置处。

代码样例如下:

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内的小图标,它是反过来的,我懒得转方向,考虑程序怎么旋转,顺便让大家了解下怎么旋转这个图片。

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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值