本人主要在知乎上发布相关Flutter文章,知乎了解下:android
https://www.zhihu.com/people/qiang-fu-5-67/activities
git
咱们来实战剖析下“企鹅电竞”直播栏下怎么实现:github
代码我尽可能写的你们浅显易懂,不装逼,很少哔哔,点关注不迷路。bash
企鹅电竞app截图:app
下面看下最终实现的效果图:less
布局解析成下面一个简单的图例:ide
总体布局结构以下:布局
咱们把内容部分拆分红了几个方法体:ui
_buildReminder()实现提醒内容区域==>预约按钮那一行
_buildRecommedList()实现横向的推荐列表==>吸金榜,周礼榜,真爱榜
_buildContentImageText()实现直播推荐下方的网格列表中的一个单元格内容
复制代码
在实践过程当中用了好几个实现方式,this
其中一种是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实现就好了。
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'
),
),
)
复制代码
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,
)),
],
),
)
复制代码
截图效果中有一个抽奖中的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,
),
),
)
],
),
);
}
复制代码