In the practical development of Flutter, we may encounter that the widgets provided by the Flutter framework do not achieve the desired effect. In this case, we need to customize the widgets. As we learned from the trilogy of Flutter construction, layout and drawing, The actual measurement, layout, and drawing operations are all in the RenderObject, and we can implement customization by inheriting the relevant RenderObject. However, the Flutter framework has been designed to allow us to customize the entry points.
CustomPaint CustomPaint
Example: circular progress bar
Use CustomPaint to draw the desired effect
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),// Write real drawing logic in Painter); } } class CircleProgressPainter extends CustomPainter { ... Override void paint(Canvas Canvas, Size Size) {... Size is the size of canvas}}Copy the code
CustomSingleChildLayout Layout for a single child
Example: Implement a constraint to square the child
Layout the child with CustomSingleChildLayout and constrain it to a square
class RectLayout extends StatelessWidget { final Widget child; RectLayout({@required this.child}); @override Widget build(BuildContext context) { return CustomSingleChildLayout( delegate: RectLayoutDelegate(),// Delegate for layout child: child,); }} class RectLayoutDelegate extends SingleChildLayoutDelegate {/ / determine the size of the layout, @override Size getSize(BoxConstraints constraints) => super.getSize(constraints); Whether you need / / / relayout @ override bool shouldRelayout (SingleChildLayoutDelegate oldDelegate) = > false; Size is the size of layout. This is determined by getSize. ChildSize is the Constraints derived from getConstraintsForChild. 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); } // define the constraint for child, Override BoxConstraints getConstraintsForChild(BoxConstraints constraints) {// Double maxEdge = min(constraints.maxWidth, constraints.maxHeight); return BoxConstraints(maxWidth: maxEdge, maxHeight: maxEdge); }}Copy the code
CustomSingleChildLayout Layouts multiple children
Example: realize grid layout
Use CustomSingleChildLayout to position the child as a grid layout
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(// The real layout implements the 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) {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++; } // index = 0 for each child; 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; } // Determine the layout size, @override Size getSize(BoxConstraints constraints) => super.getSize(constraints); }Copy the code
Combination customization
In general, composite customization is the most common way to combine widgets to achieve the desired effect by inheriting from statelessWidgets or StatefulWidgets.
- Example: pull-down refresh, pull-up load
Implementation 1: By the combination of RefreshIndictor and ScrollController
Thread: Trigger more loads by listening for scrolling
_scrollController.addListener(() { var maxScroll = _scrollController.position.maxScrollExtent; if (_scrollController.offset >= maxScroll) { if (widget.loadMoreStatus ! = LoadMoreStatus.noData) { widget.onLoadMore(); }}});Copy the code
Implementation 2: Use NotificationListener to monitor the overall scroll state, and then combine translation and animation to realize it
Note: Monitor the user’s overscroll distance to move the content area, so as to achieve the effect of drop-down refresh and drop-down loading
@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,
),
),
),
)
],
);
}
Copy the code
- Example: Slide layout up, down, left, and right
Implementation: The GestureDetector listens for gesture swiping and then panning to achieve the effect
Ideas: mainly dealing with sliding boundary, and switch zero boundary point
@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,
),
],
),
);
}
Copy the code
The complete code above is organized here flutter
Summary of Flutter learning
I have been studying Flutter for some time. From the use of the widgets at the beginning to the research on the framework in the later stage, all my observations and summaries will be recorded, mainly to sort out my knowledge points, and also to study and discuss with the majority of Flutter learners together.
Project address: flutter