今天將用Flutter的組件來實際佈局演練一下,在此以前你須要熟悉Flex佈局web
這個平時很是常見,並且相對簡單,因此是個練手的不錯人選編程
簡單分析一下:一共三塊,用Row佈局,左右分別處於頭尾,中間自延伸
頭像使用Image,小紅點用ClipOval對Container裁剪,堆疊在一塊兒,用Stack佈局
中間的文字是兩行的Column,右邊的也是兩行的Column,比較簡單,剩下的就是邊距了。
複製代碼
用一個ClipRRect來進行圖片的圓角操做,Container來限制大小,
經過Stack佈局將小紅點放到圖片左上角,小紅點經過ClipOval對Container裁剪json
var left = Container(
child: ClipRRect(
borderRadius: BorderRadius.circular(5),
child: Image.asset(
//頭像
"images/娜美.jpg",
fit: BoxFit.cover,
),
),
width: 50,
height: 50,
);
var leftWrap = Stack(
alignment: Alignment(1.2, -1.2),
children: <Widget>[
left,
ClipOval(child: Container(width: 10, height: 10, color: Colors.red,),)
],);
複製代碼
想讓兩頭的固定,中間填滿,在Flex佈局中能夠用Expanded將其包裹bash
var center = Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("心如止水", style: nameTextStyle),
Text(
"在嗎?小哥哥",
style: infoTextStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
)
],
);
var right = Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("06:45", style: infoTextStyle),
Icon(
Icons.visibility_off,
size: 18,
color: Color(0xff999999),
)
],
);
var body = Row(
children: <Widget>[leftWrap, Expanded(child: center,), right],//使用Expanded
);
複製代碼
邊距根據需求本身加一下,能夠用Padding,也能夠用Container微信
封裝一個組件,首先要看它是否有狀態,判斷的標準很簡單:
看它的界面是否有須要因響應而改變的部分,有則將該字段當作狀態值。
這個條目組件有個小紅點,是會隨着狀態的不一樣而顯隱的,因此寫成有狀態的組件
封裝成組件的好處在於複用起來很是方便,以下就不用再從新寫一遍了。less
import 'package:flutter/material.dart';
class ItemChart extends StatefulWidget {
ItemChart({Key key,this.chartBean,}) : super(key: key);
final ChartBean chartBean;
@override
_ItemChartState createState() => _ItemChartState();
}
class _ItemChartState extends State<ItemChart> {
bool _checked;//是否已查看
ChartBean _chartBean;//條目描述類
@override
Widget build(BuildContext context) {
return Container();
}
}
class ChartBean{
Image image;//頭像
String name;//名字
String sentence;//句子
String time;//時間
bool shield;//是否屏蔽
ChartBean({this.image, this.name, this.sentence, this.time,
this.shield=false});
}
複製代碼
class _ItemChartState extends State<ItemChart> {
var nameTextStyle = TextStyle(color: Colors.black, fontSize: 16);
var infoTextStyle = TextStyle(color: Color(0xff999999), fontSize: 12);
bool _checked=false;//是否已查看
@override
Widget build(BuildContext context) {
var left = Container(
child: ClipRRect(
borderRadius: BorderRadius.circular(5),
child: widget.chartBean.image,
),
width: 50,
height: 50,
);
var leftWrap = Stack(
alignment: Alignment(1.2, -1.2),
children: <Widget>[
left,
ClipOval(child: Container(width: 10, height: 10, color: Colors.red,),)
],);
var center = Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(widget.chartBean.name, style: nameTextStyle),
Padding(padding: EdgeInsets.only(top:4),child: Text(
widget.chartBean.sentence,
style: infoTextStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),)
],
);
var right = Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(widget.chartBean.time, style: infoTextStyle),
widget.chartBean.shield?Icon(
Icons.visibility_off,
size: 18,
color: Color(0xff999999),
):Container(height: 18,)
],
);
var body = Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[Padding(padding: EdgeInsets.only(right: 10),child: _checked?left:leftWrap,), Expanded(child: center,), right],
);
return Container(child: body, padding: EdgeInsets.all(10),);
}
}
複製代碼
定義一個回調函數類型,在最外層用一個InkWell漣漪事件響應組件來觸發監聽
在其中將_checked字段進行改變,再用setState從新渲染。ide
class ItemChart extends StatefulWidget {
ItemChart({Key key,this.chartBean,this.onTap}) : super(key: key);
final ChartBean chartBean;
final TapCallback onTap;
@override
_ItemChartState createState() => _ItemChartState();
}
typedef TapCallback = void Function(ChartBean bean);
---->[build方法中添加監聽以及回調及狀態改變]----
return InkWell(
child: Container(child: body, padding: EdgeInsets.all(10)),
onTap: () {
_checked = true;
if (widget.onTap != null) {
widget.onTap(widget.chartBean);
}
setState(() {});
},
);
---->[使用]----
show = ItemChart(
onTap: (bean){
print(bean.name);
},
chartBean: ChartBean(
name: "張風捷特烈",
sentence: "我是要成爲編程之王的男人。你要不要成爲編程之王的女人?",
time: "08:30",
shield: false,
image: Image.asset(
"images/icon_head.png",
)),
);
複製代碼
這樣,當拿到json數據,解析,填充到ListView中就很是方便了。函數
我的以爲掘金的簡介仍是挺好看的,就來看看這個如何佈局:佈局
最外層作個Card,其中主要三部分,能夠用Row來包,
左邊頭像,能夠用Image ,加圓形裁剪。
中間是一個三行的Column ,水平方向靠左,而且自延伸
右邊是兩行的Column,上下左右居中
複製代碼
使用ClipOval將一個Image裁成圓形post
var left = ClipOval(
child: Image.asset(
//頭像
"images/icon_head.png",
width: 50,
height: 50,
));
複製代碼
這裏是關於文字的操做,有一點要注意的Flex中的textBaseline屬性對文字中的做用
使用Expanded可讓Row儘量延展,文字到頭也會自動換行,當橫屏是也會適應。
var titleTextStyle=TextStyle(color: Colors.black, fontSize: 20);
var infoTextStyle=TextStyle(color: Color(0xff72777B), fontSize: 14);
var user = Row(
children: <Widget>[
Text(
"張風捷特烈",
style: titleTextStyle,
),
ClipRRect(
borderRadius: BorderRadius.circular(5),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 4),
color:Color(0xff34D19B) ,
child: Text("Lv4",
style: TextStyle(
fontWeight: FontWeight.w900,
color: Colors.white,fontSize: 10,
),
)),)
],
);
var info = Row(
children: <Widget>[
Container(
margin: EdgeInsets.only(right: 5),
child: Icon(Icons.next_week, size: 15),
),
Text(
"創世神 | 無",
style: infoTextStyle,
)
],
);
var say = Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.ideographic,
children: <Widget>[
Container(
margin: EdgeInsets.only(right: 5),
child: Icon(Icons.keyboard, size: 15),
),
Expanded(
child: Text("海的彼岸有我不曾見證的風采",
style:infoTextStyle))
],
);
複製代碼
也比較簡單,經過一個Column將兩塊拼出來。
var right = Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
children: <Widget>[
Icon(
Icons.language,
size: 18,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Icon(Icons.local_pharmacy, size: 18)),
Icon(Icons.person_pin_circle, size: 18)
],
),
OutlineButton(
onPressed: () {},
child: Text(
"編輯資料",
style: TextStyle(fontSize: 11),
),
textTheme: ButtonTextTheme.primary,
borderSide: BorderSide(color: Color(0xff6F80F7), width: 1),
),
],
);
複製代碼
var result = Card(
child: Row(
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 10, right: 10),
child: left,
),
Expanded(child: center),
Container(
margin: EdgeInsets.only(left: 10, right: 10),
child: right,
),
],
mainAxisAlignment: MainAxisAlignment.spaceAround,
));
複製代碼
上面寫的只是一個靜態的佈局,也只是個玩偶而已,複用和可維護性是很低的。
如何讓它成爲一個能隨意更改內容的有靈魂的組件呢?以下,能夠很容易複用
將能夠抽離的寫死字段抽離出來,自定義一個描述類做爲入參,這是基本的思路
將頁面上的字段進行抽取,造成一個類
class User {
String name;//名字
int lever;//等級
Image image;//頭像
String position;//職務
String company;//公司
String proverbs;//箴言
User({this.name, this.lever, this.image, this.position, this.company,
this.proverbs});
}
複製代碼
繼承自StatelessWidget的無狀態組件,傳入一個User對象
class UserPanel extends StatelessWidget {
final User user;
UserPanel({this.user});
@override
Widget build(BuildContext context) {
return null;
}
}
複製代碼
將上面的代碼中寫死的部分用User對象的屬性替換便可,而後再build方法裏返回出去
好比在名字的等級,其餘的就不貼了,換一下就好了。在使用時傳入User對象便可
var user = Row(
children: <Widget>[
Text(
this.user.name,
style: titleTextStyle,
),
ClipRRect(
borderRadius: BorderRadius.circular(5),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 4),
color: Color(0xff34D19B),
child: Text(
"Lv${this.user.lever}",
style: TextStyle(
fontWeight: FontWeight.w900,
color: Colors.white,
fontSize: 10,
),
)),
)
],
);
---->[使用方法]----
UserPanel(
user: User(
name: "Toly",
lever: 4,
company: "捷特王國",
position: "編程之王",
proverbs: "心之既在,無問東西。",
image: Image.asset(
//頭像
"images/head_me.jpg",
)),
),
複製代碼
既然封裝了,就完善一些,將圖像和編輯資料的點擊回調傳遞出去
若是有其餘的須要點擊,也能夠相似的回調出去
//定義兩個回調函數類型
typedef HeadTapCallback = void Function();
typedef EditTapCallback = void Function(User user);
//聲明成員變量
final HeadTapCallback onHeadTap;
final EditTapCallback onEditTap;
UserPanel({
this.user,this.onHeadTap,this.onEditTap});
//頭像點擊回調
var left = InkWell(
child: ClipOval(
child: Container(
width: 50,
height: 50,
child: this.user.image,
)),
onTap:onHeadTap,
);
//編輯按鈕點擊回調
OutlineButton(
onPressed: (){
if (onEditTap!=null) {
onEditTap(this.user);
}
},
child: Text(
"編輯資料",
style: TextStyle(fontSize: 11),
),
textTheme: ButtonTextTheme.primary,
borderSide: BorderSide(color: Color(0xff6F80F7), width: 1),
),
---->[使用]----
UserPanel(
onHeadTap: (){
print("----------------");
},
onEditTap: (user){
print("----------------user:${user.name}");
},
user: User(
name: "Toly",
lever: 4,
company: "捷特王國",
position: "編程之王",
proverbs: "心之既在,無問東西。",
image: Image.asset(
//頭像
"images/head_me.jpg",
)),
),
複製代碼
這樣該組件就能夠獨立出來,從一個寫死的靜態界面變成了可複用的組件
它會根據你傳入的User對象進行不一樣的表現,也就是它是"活的",
User即是他的靈魂,回調監聽即是他的行爲。而不像靜態界面,只是人偶而已。
今天從有狀態和無狀態兩種組件看了一下如何對組件進行簡單的封裝,但願你有所收穫。
就不寫靜態界面了,直接上。佈局和上面大同小異,只要可以劃分好結構,都好辦
這裏要提一點的是下面的價格經過TextSpan處理了一下,你能夠好好看看。
var show = Goods(
onTap:(goods){
print(goods.title);
},
width: 200,
goods: GoodBean(
price: 21.89,
saleCount: 99,
title: "得力筆記本文具商務復古25K/A5記事本PU軟皮面日記本子定製可印logo簡約工做筆記本會議記錄本小清新大學生用",
caverUrl: "https://img.alicdn.com/imgextra/i3/108452043/O1CN01IMPSxR1QxjhmdZLXA_!!0-saturn_solar.jpg_220x220.jpg_.webp"),));
}
複製代碼
import 'package:flutter/material.dart';
class Goods extends StatelessWidget {
Goods({Key key, this.goods, this.width,this.onTap}) : super(key: key);
final GoodBean goods;
final double width;
final TapCallback onTap;
@override
Widget build(BuildContext context) {
var top = Image.network(goods.caverUrl);
var center = Container(
child: Text(
goods.title,
maxLines: 2,
),
);
var bottom = Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 5),
child: _getPriceWidget(goods.price),
),
Expanded(
child: Text("${goods.saleCount}人付款",style: TextStyle(fontSize:13,color: Colors.black38),),
),
Icon(Icons.more_horiz,color: Colors.black38)
],
);
var result = Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
top,
Padding(
padding: EdgeInsets.all(10),
child: center,
),
Padding(
padding: EdgeInsets.fromLTRB(10,0,10,10),
child: bottom,
)
]);
return Card(
elevation: 5,
child: InkWell(child: Container(
width: width,
child: result,
),onTap: (){
if(onTap!=null){
onTap(goods);
}
},),
);
}
Widget _getPriceWidget(double price) {
var prices = price.toString().split(".");
var span = TextSpan(
text: '¥',
style: TextStyle(fontSize: 12, color: Colors.deepOrangeAccent),
children: <TextSpan>[
TextSpan(
text: "${prices[0]}.",
style: TextStyle(fontSize: 16, color: Colors.deepOrangeAccent)),
TextSpan(
text: "${prices[1]}",
style: TextStyle(fontSize: 12, color: Colors.deepOrangeAccent)),
],
);
return RichText(text: span);
}
}
typedef TapCallback = void Function(GoodBean bean);
class GoodBean {
String caverUrl; //封面圖連接
String title; //連接
double price; //價格
int saleCount;
GoodBean({this.caverUrl, this.title, this.price, this.saleCount}); //銷售數
}
複製代碼
本文到此接近尾聲了,若是想快速嚐鮮Flutter,《Flutter七日》會是你的必備佳品;若是想細細探究它,那就跟隨個人腳步,完成一次Flutter之旅。
另外本人有一個Flutter微信交流羣,歡迎小夥伴加入,共同探討Flutter的問題,本人微信號:zdl1994328
,期待與你的交流與切磋。