Flutter第5天--布局实例+操作交互
今天调料十足,保证新鲜美味----2018-12-20
1:写在前面:
每个布局的实现方案都有很多,我只是选择自己认为较好的布局方案
对于非常复杂的布局,建议先打草稿
,再进行颜色块模拟
,最后再写控件
有留白的地方Expanded+flex(以下我所说的flex就是Row+Column的总成
)会有很好的适应性
2.选几张图镇楼:
新手级2-ok.png-- | - |
---|---|
一、入门级布局1:
1.出题
测试1.png2.思路
测试1.png很容易看出,三个块水平排列,两端靠边,Row逃不掉了,中间很容易想到Expanded
这样中间的部分自动尺寸,而且留白很多,基本上不会造成溢出,对不同屏幕适应性更好
三个部件写完后,用个Container套一下给内边距就行了(边距的多少,就不纠结了,演示而已)
3.解题
测试1-ok.pngvar rowLine = Row(
children: <Widget>[
Icon(
Icons.extension,
color: Colors.blue,
),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 20),
child: Text(
"好友微视",
style: TextStyle(fontSize: 18),
),
)),
Icon(Icons.arrow_forward)
],
);
var test1 = Container(color: Colors.white, padding: EdgeInsets.all(15), child: rowLine);
二、入门级布局2:
[番外]:小封装1---添加测试背景色
实在要吐槽:想加个
背景色
想加一下麻烦死了...我是在受不了,封装一下方法
bg(Widget w, [Color color]) {
return Container(color: color ?? randomARGB(), child: w);
}
Color randomARGB(){
Random random = new Random();
int r = 30 + random.nextInt(200);
int g = 30 + random.nextInt(200);
int b = 30 + random.nextInt(200);
int a = 50 + random.nextInt(200);
return Color.fromARGB(a, r, g, b);
}
1.出题
微信条.png2.思路
分析1.png有了上面的指引,相信下面的应该难不倒你:
三个Row,中间用Column,模式基本同上,达到这步应该很简单
分析1.png这里暂停一下,为了说明flex布局的轴,对于Column而言,主轴是纵向
交错轴横向,默认交错轴是center
,所以呈现了上面的效果,我们只需要轻轻地:
crossAxisAlignment: CrossAxisAlignment.start,
就完成雏形了,剩下的小修小补一下
3.解题
测试2-ok.png写文字的style真心烦,抽取一下吧
//正常文字
var commonStyle = TextStyle(color: Colors.black, fontSize: 18);
//灰色较小文字
var infoStyle = TextStyle(color: Color(0xff999999), fontSize: 13);
//左边头像
var headImg = Image.asset(
"images/icon_gql.jpg", width: 45, height: 45,
);
//中间的信息
var center2 = Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text( "心如止水", style: commonStyle,),
Text( "《应龙》--张风捷特烈 一游小池两岁月,洗却凡世几闲尘。时逢雷霆风会雨,应乘扶摇化入云。",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: infoStyle,
textAlign: TextAlign.start,
)
],
);
//尾部的时间+图标
var end2 = Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("06:45",style: infoStyle),
Icon(Icons.visibility_off,size: 20,color: Color(0xff999999),
)
],
);
//整行的内容
var rowLine2 = Row(
children: <Widget>[
Padding(child: headImg, padding: EdgeInsets.all(5)),
Expanded(child: Padding(padding: EdgeInsets.all(5), child: center2)),
end2
],
);
//包裹一下,收工
var test2 = Container(
height: 70,
color: Colors.white,
padding: EdgeInsets.all(5),
child: rowLine2);
三、新级级别布局1
[番外]:小封装2
好吧,我又要
封
了:感觉加个padding也是一堆废话,封装一下吧
加padding只要函数包一下就好:--看起来要比以前那一坨好多了
pd(Text("创世神"), l: 5)//只加左边距
pda(Text("创世神"),5)//全加边距
//以前全加加Pading:-----------------
Padding(
child: headImg3,
padding: EdgeInsets.all(5),
),
pd(Widget w, {double l, double t, double r, double b}) {
return Padding(
child: w,
padding: EdgeInsets.fromLTRB(l ?? 0, t ?? 0, r ?? 0, b ?? 0),
);
}
//全部padding
pda(Widget w, double a) {
return Padding(
child: w,
padding: EdgeInsets.all(a),
);
}
//水平、竖直的两个padding
pdhv(Widget w, {double h, double v}) {
return Padding(
child: w,
padding: EdgeInsets.fromLTRB(h ?? 0, v ?? 0, h ?? 0, v ?? 0),
);
}
1.出题:(来玩掘金吧~)
新手级1.png这是网页掘金的主页栏,是我喜欢的风格,现在flutter上走一波
2.分析
新手级1.png有了前两个的经验,这种样式应该难不倒你,区块划分如下:
也许有新手不知道从哪入手,那就画个Container,填个色,这是从0到1质变,然后就是+1的量变了
我比较喜欢卡片。所以这个用Card包一下吧,三块一目了然
3.解题
新手级1-阶段.png也许你不知道一个布局有多大,你可以用上面的bg函数包裹一下,如下:
新手级1-ok.png背景有助于你的排布,最后当然要把背景去掉
//较大文字
var bigStyle = TextStyle(color: Colors.black, fontSize: 20, fontWeight: FontWeight.bold);
//btn文字
var btnStyle = TextStyle(color: Color(0xffffffff), fontSize: 13);
////////////////////////-----------------测试3--------------------------------
//左边头像
var headImg3 = Image.asset("images/icon_90.png", width: 50, height: 50,);
//中间的信息
var center3 = Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("张风捷特烈",style: bigStyle),
Row(children: <Widget>[
Icon(Icons.next_week, size: 15),
pd(Text("创世神 | 无"), l: 5)
],
),
Row(children: <Widget>[
Icon(Icons.keyboard, size: 15),
pd(Text("海的彼岸有我未曾见证的风采"), l: 5)
],
),
],
);
//尾部的
var end3 = Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Row(children: <Widget>[
Icon(Icons.language,size: 15,),
Icon(Icons.local_pharmacy, size: 15),
Icon(Icons.person_pin_circle, size: 15)
],
),
bg(pdhv(
Text("编辑",style: btnStyle,), h: 10, v: 3), Colors.blueAccent),
],
);
var rowLine3 = Row(
children: <Widget>[
pda(headImg3, 5),
Expanded(child: pda(center3,5)),
pda(end3, 10),
],
);
var test3 = Card(
child: Container(
height: 95,
color: Colors.white,
padding: EdgeInsets.all(5),
child: rowLine3));
四、新手级别布局(2)
1.出题:还拿掘金来玩吧
新手级2.png这个稍微复杂了一丢丢
2.分析:还是先打块:
新手级2.png分块的方式有很多,你喜欢怎么打就这么打,你可以看出行,也可以看成列
外部是个Column,头,身,尾。身是一个Row,文字两行是Column,头,尾都是Row
3.解题
新手级2-ok.png////////////////////////-----------------测试4--------------------------------
var line1_4 = Row(
children: <Widget>[
Image.asset("images/icon_90.png", width: 20, height: 20),
Expanded( child: pd(Text("张风捷特烈"), l: 5),),
Text("Flutter/Dart", style: infoStyle,)
],
);
var center_right = Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Flutter第4天--基础控件(下)+Flex布局详解", style: littelStyle, maxLines: 2,),
pd(Text(
"1.2:优雅地查看:图片的适应模式--BoxFit1.3:优雅地查看:颜色混合模式--colorBlendMode",
style: infoStyle, maxLines: 2,overflow: TextOverflow.ellipsis),t:5),
],
);
//中间的信息
var center4 = Row(
children: <Widget>[Expanded(child: pda(center_right, 5)),
Image.asset("images/wy_300x200.jpg", width: 80,height: 80,fit: BoxFit.fitHeight)
],
);
var end4 = Row(
children: <Widget>[
Icon(Icons.grade,color: Colors.green,size: 20,),
Text("1000W",style: infoStyle,),
pd(Icon(Icons.tag_faces,color:Colors.lightBlueAccent, size: 20),l:15,r:5),
Text("2000W",style: infoStyle),
],
);
var item4 = Column(children: <Widget>[line1_4, Expanded(child: center4), end4]);
var test4 = Card(
child: Container(
height: 160,
color: Colors.white,
padding: EdgeInsets.all(10),
child: item4));
经过这四个,可以看出,大块是小块组合的,一点点拼总能拼出来,
所以遇到复杂界面不要怕,一点一点分块,最后一点一点拼合,就能搞定
几个小例子就这样吧,好好消化一下
五:ListView的测试
条目有了,此时不测试ListView更待何时?
当然现在还只是静态的,你可以将需要的字段抽取出来封装成函数
然后再动态获取数据填充视图(打算放在最后一天说,这里用静态页面测试)
1.ListView.builder
条目2 | 条目4 |
---|---|
//条目2
var test5 = ListView.builder(
itemCount: 30,
itemBuilder: (BuildContext context, int index) {
return
Column(children: <Widget>[test2,Divider(height:1)],);
},
);
2.ListView.separated
separated.png这个多一个separatorBuilder,类型和itemBuilder一毛一样
也就是在某些位置,插入东西分割(常用的是分割线),看下图:
我在index=1的条目下面插入了test2条目(左图),变相的多条目...,
当然你可以随意控制怎么玩,比如每隔两个插入一个(右图),注意:插入的条目不算总数里
//在index=1下插入
var test6 = ListView.separated(
itemBuilder: (ctx, i) {
return Column(
children: <Widget>[test4],
);
},
separatorBuilder: (ctx, i) {
return Column(children: <Widget>[i==1?test2:Container()],
);
},
itemCount: 40);
//每隔两个插入
var test6 = ListView.separated(
itemBuilder: (ctx, i) {
return Column(
children: <Widget>[test4],
);
},
separatorBuilder: (ctx, i) {
return Column(
children: <Widget>[(i+1 ) % 2== 0 ? test2 : Container()],
);
},
itemCount: 40);
六、操作交互:
Bit世界的三大要素:数据(m),界面(v),交互(c或p),
一个项目讲白了,就是围绕这三个转,说谁更重要的都是废话
没有数据的是空壳标本,没有交互的是植物人,没有界面的那时白日做梦...
Flutter的交互感觉好奇葩...也许是一切节Widget的思想驱使吧,还是包一下
1.先天交互天赋的控件
Switch Slider Checkbox TextField SnackBar BottomNavigationBar
OutlineButton FlatButton RaisedButton IconButton FloatingActionButton 等...
2.没有先天天赋怎么办?---GestureDetector
给你光环加持
GestureDetector.png看一下源码:好吧,挺多的
GestureDetector({
Key key,
this.child,
this.onTap,----点击----Function()---
this.onTapDown,----按下:Function(TapDownDetails details)---
this.onTapUp,---- 抬起:Function(TapUpDetails details)----
this.onTapCancel,----取消(onTap无法触发时):Function()----
this.onDoubleTap,----双击----void Function()----
this.onLongPress,----长按----void Function()----
this.onLongPressUp,----长按松开----void Function()----
this.onVerticalDragDown,----竖直拖动按下----Function(DragDownDetails details)----
this.onVerticalDragStart,----竖直拖动开始----Function(DragStartDetails details)----
this.onVerticalDragUpdate,----竖直拖动更新----Function(DragUpdateDetails details)----
this.onVerticalDragEnd,----竖直拖动结束----Function(DragEndDetails details)----
this.onVerticalDragCancel,----竖直拖动取消----Function()----
this.onHorizontalDragDown,
this.onHorizontalDragStart,
this.onHorizontalDragUpdate,
this.onHorizontalDragEnd,
this.onHorizontalDragCancel,
this.onPanDown,
this.onPanStart,
this.onPanUpdate,
this.onPanEnd,
this.onPanCancel,
this.onScaleStart,
this.onScaleUpdate,
this.onScaleEnd,
this.behavior,
this.excludeFromSemantics = false
3.测试1:四大战将
3.1.源码追踪:
this.onTap,----点击----Function()---
this.onTapDown,----按下:Function(TapDownDetails details)---
this.onTapUp,---- 抬起:Function(TapUpDetails details)
this.onTapCancel,----取消(onTap无法触发时):Function()----
---->[源码追踪:onTapDown]
final GestureTapDownCallback onTapDown;
---->[源码追踪:GestureTapDownCallback]
typedef GestureTapDownCallback = void Function(TapDownDetails details);
---->[源码追踪:TapDownDetails]
class TapDownDetails {
/// Creates details for a [GestureTapDownCallback].
///
/// The [globalPosition] argument must not be null.
TapDownDetails({ this.globalPosition = Offset.zero })
: assert(globalPosition != null);
---->[源码追踪:Offset]
class Offset extends OffsetBase {
/// Creates an offset. The first argument sets [dx], the horizontal component,
/// and the second sets [dy], the vertical component.
const Offset(double dx, double dy) : super(dx, dy);
//好吧,搞了半天就是落点嘛...
3.2测试代码
var box = Container(
width: 100,
height: 100,
color: Colors.lightBlueAccent,
);
var ctrl_test = GestureDetector(
child: box,
onTap: () {
print("onTap");
},
onTapDown: (d) {
print("onPanDown" + d.globalPosition.toString());
},
onTapUp: (d) {
print("onTapUp" + d.globalPosition.toString());
},
onTapCancel: () {
print("onTapUp");
},
);
点了一下,控制台输出:
I/flutter (27114): onPanDownOffset(205.5, 384.5)
I/flutter (27114): onTapUpOffset(205.5, 384.5)
I/flutter (27114): onTap
可见坐标是相对于屏幕顶点的
onTapCancel
4.测试2:三大小白
顾名思义...不多说
this.onDoubleTap,----双击----void Function()----
this.onLongPress,----长按----void Function()----
this.onLongPressUp,----长按松开----void Function()----
var ctrl_test2 = GestureDetector(
child: box,
onDoubleTap: () {
print("onDoubleTap");
},
onLongPress: () {
print("onLongPress");
},
onLongPressUp: () {
print("onLongPressUp");
});
5.测试3:战场双龙(只给一条,另一条类比)
this.onVerticalDragDown,----竖直拖动按下----Function(DragDownDetails details)----
this.onVerticalDragStart,----竖直拖动开始----Function(DragStartDetails details)----
this.onVerticalDragUpdate,----竖直拖动更新----Function(DragUpdateDetails details)----
this.onVerticalDragEnd,----竖直拖动结束----Function(DragEndDetails details)----
this.onVerticalDragCancel,----竖直拖动取消----Function()----
var ctrl_test3 = GestureDetector(
child: box,
onVerticalDragDown: (d) {
print("onVerticalDragDown---" + d.globalPosition.toString());
},
onVerticalDragStart: (d) {
print("onVerticalDragStart---" + d.globalPosition.toString());
},
onVerticalDragUpdate: (d) {
print("onVerticalDragUpdate---" + d.globalPosition.toString());
},
onVerticalDragCancel: () {
print("onVerticalDragCancel---");
});
I/flutter ( 4994): onVerticalDragDown---Offset(182.5, 384.8)
I/flutter ( 4994): onVerticalDragStart---Offset(182.5, 384.8)
I/flutter ( 4994): onVerticalDragUpdate---Offset(182.5, 390.2)
I/flutter ( 4994): onVerticalDragUpdate---Offset(181.8, 402.2)
I/flutter ( 4994): onVerticalDragUpdate---Offset(180.8, 420.5)
I/flutter ( 4994): onVerticalDragUpdate---Offset(181.2, 443.5)
七、交互操作小案例
1:点击生成小球
canvas画出的CustomPaint大小神奇般的是0,导致GestureDetector不起作用
没办法,只能曲线救国,GestureDetector包住全部,在减去偏移量
小球的绘制就不分析了,就是收集球,再画出来,如果第二天的文章会了,这都是小菜
1.1小球数据承载类:
class Draw {
double x;
double y;
Color color;
Draw(this.x, this.y, this.color);
}
1.2:准备Canvas绘板
生成球.gifdrawGrid绘制网格见第二篇(其实没有也无所谓,我比较喜欢)
//Canvas绘版
class CanvasView extends CustomPainter {
BuildContext context;
Paint mPaint;
CanvasView(this.context) {
mPaint = new Paint();
}
@override
void paint(Canvas canvas, Size size) {
balls.forEach((ball) {
drawBall(canvas, ball);
});
var winSize = MediaQuery.of(context).size;
drawGrid(canvas, winSize);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
//绘制小球
void drawBall(Canvas canvas, Draw ball) {
mPaint.color = ball.color;
canvas.drawCircle(Offset(ball.x, ball.y), 10, mPaint);
}
}
1.3.数据的变动与渲染(交互)
var balls = []; //小球合集
class CanvasPage extends StatefulWidget {
CanvasPage({Key key, this.title}) : super(key: key);
final String title;
@override
_CanvasPageState createState() => _CanvasPageState();
}
class _CanvasPageState extends State<CanvasPage> {
@override
Widget build(BuildContext context) {
var appBar = AppBar(
title: Text("张风捷特烈"),
);
var barTopHeight = MediaQueryData.fromWindow(window).padding.top;
print(barTopHeight);
var scf = Scaffold(
appBar: appBar,
body: CustomPaint(
painter: CanvasView(context),
));
return GestureDetector(
child: scf,
onTapDown: (d) {
var pos = d.globalPosition;
balls.add(new Draw(pos.dx,
pos.dy - appBar.preferredSize.height - barTopHeight, randomRGB()));
print(balls.length);
setState(() {});
},
);
}
}
2.onPanUpdate
测试
绘图.gif实现起来还是很简单的,
onPanUpdate
的时候加点就行了
onPanUpdate: (d) {
var pos = d.globalPosition;
balls.add(new Draw(pos.dx,
pos.dy - appBar.preferredSize.height - barTopHeight, randomARGB()));
3.画线
画线.gif好吧,这个比较搓,不过测试了
onPanDown
、onPanUpdate
、onPanEnd
Flutter的canvas用的怪怪的,无法记录前次的绘制,要实现自由绘制,看来只能拼点了
//Canvas绘版
class CanvasView extends CustomPainter {
BuildContext context;
Paint mPaint;
double _downX;
double _downY;
double _upX;
double _upY;
CanvasView(this.context, this._downX, this._downY, this._upX, this._upY) {
mPaint = new Paint()
..strokeWidth = 10
..strokeCap = StrokeCap.round;
}
@override
void paint(Canvas canvas, Size size) {
var winSize = MediaQuery.of(context).size;
drawGrid(canvas, winSize);
print("_downX:$_downX,_downY:$_downY");
canvas.drawLine(Offset(_downX, _downY), Offset(_upX, _upY), mPaint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
}
class CanvasPage extends StatefulWidget {
CanvasPage({Key key, this.title}) : super(key: key);
final String title;
@override
_CanvasPageState createState() => _CanvasPageState();
}
class _CanvasPageState extends State<CanvasPage> {
var _downX;
var _downY;
var _upX;
var _upY;
@override
Widget build(BuildContext context) {
var appBar = AppBar(
title: Text("张风捷特烈"),
);
var barTopHeight = MediaQueryData.fromWindow(window).padding.top;
var scf = Scaffold(
appBar: appBar,
body: CustomPaint(
painter: CanvasView(context, _downX, _downY, _upX, _upY),
));
return GestureDetector(
child: scf,
onPanDown: (d) {
_downX = d.globalPosition.dx;
_downY =
d.globalPosition.dy - appBar.preferredSize.height - barTopHeight;
},
onPanUpdate: (d) {
_upX = d.globalPosition.dx;
_upY = d.globalPosition.dy - appBar.preferredSize.height - barTopHeight;
setState(() {});
},
onPanEnd: (d) {
_downX = -10.0;
_downY = -10.0;
_upX = -10.0;
_upY = -10.0;
setState(() {});
},
);
}
}
八、关于跳转
页面跳转与关闭.gif跳转方式1:加routes
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.lightBlue,
),
home: new CanvasPage(),
routes: <String, WidgetBuilder> {
'/clock': (BuildContext context) => ClockPage(),
},);
//跳转方法:
Navigator.of(context).pushNamed('/clock');
跳转方式2:直接开控件
Navigator.push(context,MaterialPageRoute(builder: (bu) => ClockPage()));
关闭方式:
Navigator.pop(context);
要说flutter的方便之处,那就是布局是对象,这有多爽:
1.Android时候写xml,如果一个布局文件你想要其中的一部分,这就尴尬了:
cv一下,删删改改,有时id有联系就更尴尬了。
2.虽然安卓的xml相比于Java代码布局的简洁性,复用性高很多,但仍有局限性。
3.而flutter布局是对象,你可以用变量来记录它,随用随取。
4.Flutter的flex布局让布局的适应性变得很强,虽然Android的约束布局也可以,但略显繁杂
好了,今天就到这里
后记:捷文规范
1.本文成长记录及勘误表
项目源码 | 日期 | 备注 |
---|---|---|
V0.1-github | 2018-12-20 | Flutter第5天--布局实例+操作交互 |
2.更多关于我
笔名 | 微信 | 爱好 | |
---|---|---|---|
张风捷特烈 | 1981462002 | zdl1994328 | 语言 |
我的github | 我的简书 | 我的掘金 | 个人网站 |
3.声明
1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持
icon_wx_200.png