這篇文章主要給大家介紹了關於Flutter開發中Widget自定義的相關資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用Flutter具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
前言
在Flutter實際開發中,大家可能會遇到flutter框架中提供的widget達不到我們想要的效果,這時就需要我們去自定義widget,從Flutter構建、佈局、繪製三部曲中我們瞭解到,實際的測量、佈局、繪製操作都在RenderObject中,我們是可以進行繼承相關的RenderObject來實現自定義的。但是其實flutter框架在設計之初就給我們預留出了自定義的入口,方便我們進行自定義。
CustomPaint自定義繪製
例:圓形進度條
思路:使用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進行佈局
例:實現對child約束成正方形
思路:使用CustomSingleChildLayout對child進行佈局,並約束爲正方形
class RectLayout extends StatelessWidget { final Widget child; RectLayout({@required this.child}); @override Widget build(BuildContext context) { return CustomSingleChildLayout( delegate: RectLayoutDelegate(),//進行佈局的代理 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的位置,返回一個相對於parent的偏移值,size是layout的大小,由getsize確定,childSize由getConstraintsForChild得出的Constraints對child進行約束,得到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對多個child進行佈局
例:實現網格佈局
思路:使用CustomSingleChildLayout對child進行佈局、定位,使其成爲網格的佈局
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(//真正的佈局實現 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) { //對每個child進行逐一佈局 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++; } //對每一個child逐一進行定位 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); }
組合自定義
一般情況,組合自定義應該是我們最經常用的方式,通過繼承自StatelessWidget或StatefulWidget,把多個Widget組合起來,從而達到我們需要的效果。
例:下拉刷新,上拉加載
實現一:通過自帶的RefreshIndictor和ScrollController組合實現
思路:通過對滾動進行監聽來觸發加載更多
_scrollController.addListener(() { var maxScroll = _scrollController.position.maxScrollExtent; if (_scrollController.offset >= maxScroll) { if (widget.loadMoreStatus != LoadMoreStatus.noData) { widget.onLoadMore(); } } });
實現二:通過NotificationListener監聽scroll的整體狀態,讓後結合平移、動畫來實現
思路:通過監聽用戶overscroll的距離來平移內容區域,從而達到下拉刷新,上拉加載的效果
@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, ), ), ), ) ], ); }
例:上下左右滑動的layout
實現:通過GestureDetector監聽手勢滑動,然後通過平移來達到效果
思路:主要處理滑動邊界,以及開關的零界點
@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知識點整理
Flutter學習總結
對Flutter的學習也有一段時間了,從最開始的Widget的使用,到後面的框架的一些研究,所有的心得與總結都會記錄下來,主要是對自己知識點的整理,同樣也爲了能夠與廣大Flutter的學習者共同學習,相互探討。
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對神馬文庫的支持。