Flutter開發(fā)之Widget自定義總結(jié)
前言
在Flutter實(shí)際開發(fā)中,大家可能會(huì)遇到flutter框架中提供的widget達(dá)不到我們想要的效果,這時(shí)就需要我們?nèi)プ远xwidget,從Flutter構(gòu)建、布局、繪制三部曲中我們了解到,實(shí)際的測量、布局、繪制操作都在RenderObject中,我們是可以進(jìn)行繼承相關(guān)的RenderObject來實(shí)現(xiàn)自定義的。但是其實(shí)flutter框架在設(shè)計(jì)之初就給我們預(yù)留出了自定義的入口,方便我們進(jìn)行自定義。
CustomPaint自定義繪制
例:圓形進(jìn)度條

思路:使用CustomPaint繪制需要的效果
class CircleProgress extends StatelessWidget {
final Size size;
final double progress;
CircleProgress({@required this.size, @required this.progress});
@override
Widget build(BuildContext context) {
return CustomPaint(
size: size,
painter: CircleProgressPainter(endDegree: progress * 360),//在Painter中寫真正的繪畫邏輯
);
}
}
class CircleProgressPainter extends CustomPainter {
...省略
@override
void paint(Canvas canvas, Size size) {
...繪制的具體邏輯,size是畫布的大小
}
}
CustomSingleChildLayout對單一child進(jìn)行布局
例:實(shí)現(xiàn)對child約束成正方形

思路:使用CustomSingleChildLayout對child進(jìn)行布局,并約束為正方形
class RectLayout extends StatelessWidget {
final Widget child;
RectLayout({@required this.child});
@override
Widget build(BuildContext context) {
return CustomSingleChildLayout(
delegate: RectLayoutDelegate(),//進(jìn)行布局的代理
child: child,
);
}
}
class RectLayoutDelegate extends SingleChildLayoutDelegate {
//確定layout的size,constraints是parent傳過來的約束
@override
Size getSize(BoxConstraints constraints) => super.getSize(constraints);
///是否需要relayout
@override
bool shouldRelayout(SingleChildLayoutDelegate oldDelegate) => false;
///確定child的位置,返回一個(gè)相對于parent的偏移值,size是layout的大小,由getsize確定,childSize由getConstraintsForChild得出的Constraints對child進(jìn)行約束,得到child自身的size
@override
Offset getPositionForChild(Size size, Size childSize) {
double dx = (size.width - childSize.width) / 2;
double dy = (size.height - childSize.height) / 2;
return Offset(dx, dy);
}
///確定child的約束,用于確定child的大小
@override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {//
double maxEdge = min(constraints.maxWidth, constraints.maxHeight);
return BoxConstraints(maxWidth: maxEdge, maxHeight: maxEdge);
}
}
CustomSingleChildLayout對多個(gè)child進(jìn)行布局
例:實(shí)現(xiàn)網(wǎng)格布局

思路:使用CustomSingleChildLayout對child進(jìn)行布局、定位,使其成為網(wǎng)格的布局
class GridLayout extends StatelessWidget {
final List<Widget> children;
final double horizontalSpace;
final double verticalSpace;
GridLayout(
{@required this.children,
@required this.horizontalSpace,
@required this.verticalSpace});
@override
Widget build(BuildContext context) {
List<Widget> layoutChildren = new List();
for (int index = 0; index < children.length; index++) {
layoutChildren.add(LayoutId(id: index, child: children[index]));
}
return CustomMultiChildLayout(
delegate: GridLayoutDelegate(//真正的布局實(shí)現(xiàn)
horizontalSpace: horizontalSpace,
verticalSpace: verticalSpace,
),
children: layoutChildren,
);
}
}
class GridLayoutDelegate extends MultiChildLayoutDelegate {
final double horizontalSpace;
final double verticalSpace;
List<Size> _itemSizes = List();
GridLayoutDelegate(
{@required this.horizontalSpace, @required this.verticalSpace});
@override
void performLayout(Size size) {
//對每個(gè)child進(jìn)行逐一布局
int index = 0;
double width = (size.width - horizontalSpace) / 2;
var itemConstraints = BoxConstraints(
minWidth: width, maxWidth: width, maxHeight: size.height);
while (hasChild(index)) {
_itemSizes.add(layoutChild(index, itemConstraints));
index++;
}
//對每一個(gè)child逐一進(jìn)行定位
index = 0;
double dx = 0;
double dy = 0;
while (hasChild(index)) {
positionChild(index, Offset(dx, dy));
dx = index % 2 == 0 ? width + horizontalSpace : 0;
if (index % 2 == 1) {
double maxHeight =
max(_itemSizes[index].height, _itemSizes[index - 1].height);
dy += maxHeight + verticalSpace;
}
index++;
}
}
@override
bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) {
return oldDelegate != this;
}
//確定layout的size,constraints是parent傳過來的約束
@override
Size getSize(BoxConstraints constraints) => super.getSize(constraints);
}
組合自定義
一般情況,組合自定義應(yīng)該是我們最經(jīng)常用的方式,通過繼承自StatelessWidget或StatefulWidget,把多個(gè)Widget組合起來,從而達(dá)到我們需要的效果。
例:下拉刷新,上拉加載
實(shí)現(xiàn)一:通過自帶的RefreshIndictor和ScrollController組合實(shí)現(xiàn)

思路:通過對滾動(dòng)進(jìn)行監(jiān)聽來觸發(fā)加載更多
_scrollController.addListener(() {
var maxScroll = _scrollController.position.maxScrollExtent;
if (_scrollController.offset >= maxScroll) {
if (widget.loadMoreStatus != LoadMoreStatus.noData) {
widget.onLoadMore();
}
}
});
實(shí)現(xiàn)二:通過NotificationListener監(jiān)聽scroll的整體狀態(tài),讓后結(jié)合平移、動(dòng)畫來實(shí)現(xiàn)

思路:通過監(jiān)聽用戶overscroll的距離來平移內(nèi)容區(qū)域,從而達(dá)到下拉刷新,上拉加載的效果
@override
Widget build(BuildContext context) {
double topHeight =
_pullDirection == PullDirection.DOWN ? _overScrollOffset.dy.abs() : 0;
double bottomHeight =
_pullDirection == PullDirection.UP ? _overScrollOffset.dy.abs() : 0;
return Stack(
children: <Widget>[
widget.headerBuilder.buildTip(_state, topHeight),
Align(
alignment: Alignment.bottomCenter,
child: widget.footerBuilder.buildTip(_state, bottomHeight),
),
Transform.translate(
offset: _overScrollOffset,
child: NotificationListener<ScrollNotification>(
onNotification: handleScrollNotification,
child: DecoratedBox(
decoration: BoxDecoration(color: Colors.grey[100]),
child: ListView.builder(
itemBuilder: buildItem,
itemCount: 30,
),
),
),
)
],
);
}
例:上下左右滑動(dòng)的layout
實(shí)現(xiàn):通過GestureDetector監(jiān)聽手勢滑動(dòng),然后通過平移來達(dá)到效果

思路:主要處理滑動(dòng)邊界,以及開關(guān)的零界點(diǎn)
@override
Widget build(BuildContext context) {
//debugPrint('_slideOffset:${_slideOffset.toString()}');
return GestureDetector(
onPanUpdate: handlePanUpdate,
onPanEnd: handlePanEnd,
child: Stack(
children: <Widget>[
widget.background,
Transform.translate(
child: widget.foreground,
offset: _slideOffset,
),
],
),
);
}
以上的完整代碼在這flutter知識點(diǎn)整理
Flutter學(xué)習(xí)總結(jié)
對Flutter的學(xué)習(xí)也有一段時(shí)間了,從最開始的Widget的使用,到后面的框架的一些研究,所有的心得與總結(jié)都會(huì)記錄下來,主要是對自己知識點(diǎn)的整理,同樣也為了能夠與廣大Flutter的學(xué)習(xí)者共同學(xué)習(xí),相互探討。
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對腳本之家的支持。
相關(guān)文章
Android仿QQ分組實(shí)現(xiàn)二級菜單展示
這篇文章主要為大家詳細(xì)介紹了Android仿QQ分組實(shí)現(xiàn)二級菜單展示,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09
Android模擬開關(guān)按鈕點(diǎn)擊打開動(dòng)畫(屬性動(dòng)畫之平移動(dòng)畫)
這篇文章主要介紹了Android模擬開關(guān)按鈕點(diǎn)擊打開動(dòng)畫(屬性動(dòng)畫之平移動(dòng)畫)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09
Android實(shí)時(shí)文件夾創(chuàng)建方法
這篇文章主要介紹了Android實(shí)時(shí)文件夾創(chuàng)建方法,涉及基于Activity實(shí)現(xiàn)文件實(shí)時(shí)查詢的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-09-09
Android如何從實(shí)現(xiàn)到封裝一個(gè)MVP詳解
原生的 MVC 框架遇到大規(guī)模的應(yīng)用,就會(huì)變得代碼難讀,不好維護(hù),無法測試的囧境。因此,Android 開發(fā)方面也有很多對應(yīng)的框架來解決這些問題。所以這篇文章主要給大家介紹了關(guān)于Android如何從實(shí)現(xiàn)到封裝一個(gè)MVP的相關(guān)資料,需要的朋友可以參考下。2017-09-09
Android組件之BroadcastReceiver廣播接收者
這篇文章主要為大家介紹了Android組件之BroadcastReceiver廣播接收者實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
android ListView的右邊滾動(dòng)滑塊啟用方法 分享
android ListView的右邊滾動(dòng)滑塊啟用方法 分享,需要的朋友可以參考一下2013-05-05

