Flutter開(kāi)發(fā)之Widget自定義總結(jié)
前言
在Flutter實(shí)際開(kāi)發(fā)中,大家可能會(huì)遇到flutter框架中提供的widget達(dá)不到我們想要的效果,這時(shí)就需要我們?nèi)プ远xwidget,從Flutter構(gòu)建、布局、繪制三部曲中我們了解到,實(shí)際的測(cè)量、布局、繪制操作都在RenderObject中,我們是可以進(jìn)行繼承相關(guān)的RenderObject來(lái)實(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中寫(xiě)真正的繪畫(huà)邏輯 ); } } class CircleProgressPainter extends CustomPainter { ...省略 @override void paint(Canvas canvas, Size size) { ...繪制的具體邏輯,size是畫(huà)布的大小 } }
CustomSingleChildLayout對(duì)單一child進(jìn)行布局
例:實(shí)現(xiàn)對(duì)child約束成正方形
思路:使用CustomSingleChildLayout對(duì)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傳過(guò)來(lái)的約束 @override Size getSize(BoxConstraints constraints) => super.getSize(constraints); ///是否需要relayout @override bool shouldRelayout(SingleChildLayoutDelegate oldDelegate) => false; ///確定child的位置,返回一個(gè)相對(duì)于parent的偏移值,size是layout的大小,由getsize確定,childSize由getConstraintsForChild得出的Constraints對(duì)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對(duì)多個(gè)child進(jìn)行布局
例:實(shí)現(xiàn)網(wǎng)格布局
思路:使用CustomSingleChildLayout對(duì)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) { //對(duì)每個(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++; } //對(duì)每一個(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傳過(guò)來(lái)的約束 @override Size getSize(BoxConstraints constraints) => super.getSize(constraints); }
組合自定義
一般情況,組合自定義應(yīng)該是我們最經(jīng)常用的方式,通過(guò)繼承自StatelessWidget或StatefulWidget,把多個(gè)Widget組合起來(lái),從而達(dá)到我們需要的效果。
例:下拉刷新,上拉加載
實(shí)現(xiàn)一:通過(guò)自帶的RefreshIndictor和ScrollController組合實(shí)現(xiàn)
思路:通過(guò)對(duì)滾動(dòng)進(jìn)行監(jiān)聽(tīng)來(lái)觸發(fā)加載更多
_scrollController.addListener(() { var maxScroll = _scrollController.position.maxScrollExtent; if (_scrollController.offset >= maxScroll) { if (widget.loadMoreStatus != LoadMoreStatus.noData) { widget.onLoadMore(); } } });
實(shí)現(xiàn)二:通過(guò)NotificationListener監(jiān)聽(tīng)scroll的整體狀態(tài),讓后結(jié)合平移、動(dòng)畫(huà)來(lái)實(shí)現(xiàn)
思路:通過(guò)監(jiān)聽(tīng)用戶(hù)overscroll的距離來(lái)平移內(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):通過(guò)GestureDetector監(jiān)聽(tīng)手勢(shì)滑動(dòng),然后通過(guò)平移來(lái)達(dá)到效果
思路:主要處理滑動(dòng)邊界,以及開(kāi)關(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知識(shí)點(diǎn)整理
Flutter學(xué)習(xí)總結(jié)
對(duì)Flutter的學(xué)習(xí)也有一段時(shí)間了,從最開(kāi)始的Widget的使用,到后面的框架的一些研究,所有的心得與總結(jié)都會(huì)記錄下來(lái),主要是對(duì)自己知識(shí)點(diǎn)的整理,同樣也為了能夠與廣大Flutter的學(xué)習(xí)者共同學(xué)習(xí),相互探討。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Android 音樂(lè)播放器的開(kāi)發(fā)實(shí)例詳解
本文主要講解Android 音樂(lè)播放器的開(kāi)發(fā),這里給大家提供一個(gè)簡(jiǎn)單的示例代碼,和實(shí)現(xiàn)效果圖,有需要開(kāi)發(fā)音樂(lè)播放器的朋友可以參考下2016-08-08簡(jiǎn)單學(xué)習(xí)Android TextView
這篇文章主要和大家一起簡(jiǎn)單學(xué)習(xí)Android TextView,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09Android開(kāi)發(fā)實(shí)現(xiàn)自動(dòng)切換文字TextSwitcher功能示例
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)自動(dòng)切換文字TextSwitcher功能,結(jié)合實(shí)例形式詳細(xì)分析了Android使用TextSwitcher實(shí)現(xiàn)文字自動(dòng)切換的原理、實(shí)現(xiàn)方法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2019-03-03Flutter Set存儲(chǔ)自定義對(duì)象時(shí)保證唯一的方法詳解
在Flutter中,Set和List是兩種不同的集合類(lèi)型,List中存儲(chǔ)的元素可以重復(fù),Set中存儲(chǔ)的元素不可重復(fù),如果想在Set中存儲(chǔ)自定義對(duì)象,需要確保對(duì)象的唯一性,那么如何保證唯一,接下來(lái)小編就給大家詳細(xì)的介紹一下2023-11-11Android來(lái)電監(jiān)聽(tīng)和去電監(jiān)聽(tīng)實(shí)現(xiàn)代碼
本文是關(guān)于來(lái)點(diǎn)監(jiān)聽(tīng)和去電監(jiān)聽(tīng)展開(kāi)問(wèn)題,通過(guò)實(shí)例代碼講解,對(duì)android來(lái)電監(jiān)聽(tīng)和去電監(jiān)聽(tīng)的相關(guān)知識(shí)感興趣的朋友一起看看吧2017-06-06Android實(shí)現(xiàn)伸縮彈力分布菜單效果的示例
本文介紹下在Android中實(shí)現(xiàn)伸縮彈力分布菜單效果。這種效果比較炫酷,有需要的朋友可以參考一下。2016-10-10Android自定義水波紋底部導(dǎo)航的實(shí)現(xiàn)
TabLayout作為導(dǎo)航組件來(lái)說(shuō),使用場(chǎng)景非常的多,也意味著要滿(mǎn)足各種各樣的需求,這篇文章主要介紹了Android自定義水波紋底部導(dǎo)航的實(shí)現(xiàn)2022-08-08