Flutter第5天--佈局例項+操作互動
今天調料十足,保證新鮮美味----2018-12-20
1:寫在前面:
每個佈局的實現方案都有很多,我只是選擇自己認為較好的佈局方案
對於非常複雜的佈局,建議先打草稿
,再進行顏色塊模擬
,最後再寫控制元件
有留白的地方Expanded+flex(以下我所說的flex就是Row+Column的總成
)會有很好的適應性
2.選幾張圖鎮樓:
-- | - |
---|---|
一、入門級佈局1:
1.出題
2.思路
很容易看出,三個塊水平排列,兩端靠邊,Row逃不掉了,中間很容易想到Expanded
這樣中間的部分自動尺寸,而且留白很多,基本上不會造成溢位,對不同螢幕適應性更好
三個部件寫完後,用個Container套一下給內邊距就行了(邊距的多少,就不糾結了,演示而已)
3.解題
var 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.出題
2.思路
有了上面的指引,相信下面的應該難不倒你: 三個Row,中間用Column,模式基本同上,達到這步應該很簡單
這裡暫停一下,為了說明flex佈局的軸,對於Column而言,主軸是縱向
交錯軸橫向,預設交錯軸是center
,所以呈現了上面的效果,我們只需要輕輕地:
crossAxisAlignment: CrossAxisAlignment.start,
就完成雛形了,剩下的小修小補一下
3.解題
寫文字的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.出題:(來玩掘金吧~)
這是網頁掘金的主頁欄,是我喜歡的風格,現在flutter上走一波
2.分析
有了前兩個的經驗,這種樣式應該難不倒你,區塊劃分如下:
也許有新手不知道從哪入手,那就畫個Container,填個色,這是從0到1質變,然後就是+1的量變了
我比較喜歡卡片。所以這個用Card包一下吧,三塊一目瞭然
3.解題
也許你不知道一個佈局有多大,你可以用上面的bg函式包裹一下,如下:
背景有助於你的排布,最後當然要把背景去掉
//較大文字
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.分析:還是先打塊:
分塊的方式有很多,你喜歡怎麼打就這麼打,你可以看出行,也可以看成列
外部是個Column,頭,身,尾。身是一個Row,文字兩行是Column,頭,尾都是Row
3.解題
////////////////////////-----------------測試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
這個多一個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({
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繪板
drawGrid繪製網格見第二篇(其實沒有也無所謂,我比較喜歡)
//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
測試
實現起來還是很簡單的,
onPanUpdate
的時候加點就行了
onPanUpdate: (d) {
var pos = d.globalPosition;
balls.add(new Draw(pos.dx,
pos.dy - appBar.preferredSize.height - barTopHeight, randomARGB()));
複製程式碼
3.畫線
好吧,這個比較搓,不過測試了
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(() {});
},
);
}
}
複製程式碼
八、關於跳轉
跳轉方式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----看到這裡,我在此感謝你的喜歡與支援