Flutter 布局之企鹅电竞
Posted H5前端开发社区
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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(),
],
),
);
}复制代码
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
以上是关于Flutter 布局之企鹅电竞的主要内容,如果未能解决你的问题,请参考以下文章