This article mainly introduces how to use the Cupertino style control of Flutter to write an iOS style address book, as well as the problems encountered in the process and solutions.
When you write apps with Flutter, you will generally use material style controls. Because material style controls are rich, but they will appear Android flavor on iOS, which is not suitable. So this article will systematically introduce Cupertino controls, some of the underlying controls of the system and how to define a beautiful and suitable self by imitating iOS address book Your control.
Due to the limitation of the three-party contact package, some functions are not implemented. I will continue to pay attention to the update of this contact plug-in and add new functions in time.
home page
Main controls and problems
CupertinoPageScaffold
An iOS style Scaffold that can add NavigationBar.
NestedScrollView
Implement floating NavigationBar and SearchBar.
NestedScrollView I have rewritten with myself, mainly because there are two problems in the source code.
1. When the list slides to the bottom, then continues to slide, and then stops, let go. At this time, the list will scroll to the bottom again, but the source code does not deal with the situation when the speed is equal to 0, so when the list is released, it will rebound back, and the rebound distance is less than maxscrollextend.
The source code is as follows:
@protectedScrollActivity createInnerBallisticScrollActivity(_NestedScrollPosition position, double velocity) { return position.createBallisticScrollActivity( position.physics.createBallisticSimulation( velocity == 0 ? position as ScrollMetrics : _getMetrics(position, velocity), velocity, ), mode: _NestedBallisticScrollActivityMode.inner, ); }
In this case, when velocity == 0, the innerPosition is directly assigned to the position parameter of the createballisticisimulation method. Let's go on.
ScrollActivity createBallisticScrollActivity( Simulation simulation, { @required _NestedBallisticScrollActivityMode mode, _NestedScrollMetrics metrics, }) { if (simulation == null) return IdleScrollActivity(this); assert(mode != null); switch (mode) { case _NestedBallisticScrollActivityMode.outer: assert(metrics != null); if (metrics.minRange == metrics.maxRange) return IdleScrollActivity(this); return _NestedOuterBallisticScrollActivity( coordinator, this, metrics, simulation, context.vsync, ); case _NestedBallisticScrollActivityMode.inner: return _NestedInnerBallisticScrollActivity( coordinator, this, simulation, context.vsync, ); case _NestedBallisticScrollActivityMode.independent: return BallisticScrollActivity(this, simulation, context.vsync); } return null; }
When velocity == 0, the execution is
case _NestedBallisticScrollActivityMode.inner: return _NestedInnerBallisticScrollActivity( coordinator, this, simulation, context.vsync, );
At this time, the simulation is obtained through innerPosition above, and then passed to "NestedInnerBallisticScrollActivity". We will continue to look down,
class _NestedInnerBallisticScrollActivity extends BallisticScrollActivity { _NestedInnerBallisticScrollActivity( this.coordinator, _NestedScrollPosition position, Simulation simulation, TickerProvider vsync, ) : super(position, simulation, vsync); final _NestedScrollCoordinator coordinator; @override _NestedScrollPosition get delegate => super.delegate as _NestedScrollPosition; @override void resetActivity() { delegate.beginActivity(coordinator.createInnerBallisticScrollActivity( delegate, velocity, )); } @override void applyNewDimensions() { delegate.beginActivity(coordinator.createInnerBallisticScrollActivity( delegate, velocity, )); } @override bool applyMoveTo(double value) { return super.applyMoveTo(coordinator.nestOffset(value, delegate)); } }
We found that the operation performed here is not what we want. When the velocity == 0 and the sliding distance is greater than maxscrollextend, we just want to scroll to the bottom of the list, so we need to change the implementation here. There are two ways to achieve this:
The first way: change the "getMetrics" method
// This handles going forward (fling up) and inner list is// underscrolled, OR, going backward (fling down) and inner list is// scrolled past zero. We want to skip the pixels we don't need to grow// or shrink over.if (velocity > 0.0) { // shrinking extra = _outerPosition.minScrollExtent - _outerPosition.pixels; } else if (velocity < 0.0) { // growing extra = _outerPosition.pixels - (_outerPosition.maxScrollExtent - _outerPosition.minScrollExtent); } else { extra = 0.0; }assert(extra <= 0.0); minRange = _outerPosition.minScrollExtent; maxRange = _outerPosition.maxScrollExtent + extra;assert(minRange <= maxRange); correctionOffset = 0.0;
Add the judgment of velocity == 0.
The second method: modify the createInnerBallisticScrollActivity method, and add the velocity == 0 judgment.
@protectedScrollActivity createInnerBallisticScrollActivity(_NestedScrollPosition position, double velocity) { return position.createBallisticScrollActivity( position.physics.createBallisticSimulation( velocity == 0 ? position as ScrollMetrics : _getMetrics(position, velocity), velocity, ), mode: velocity == 0 ? _NestedBallisticScrollActivityMode.independent : _NestedBallisticScrollActivityMode.inner, ); }
2. When we manually call the position.moveTo method to scroll to the bottom, the maxscrollextend obtained is not the actual innerPosition maxscrollextend, but should be maxscrollextend - outerposition.maxscrollextend + outerposition.pixels.
Next let's analyze the source code to see what's wrong. First, let's look at the maxscrollextend method that is directly related to it.
@overridedouble get maxScrollExtent => _maxScrollExtent;
We can see that it's just a simple way to return to maxscrollextend. Let's see where maxscrollextend is assigned. After checking the source code, we can see that the assignment of maxscrollextend is mainly in the following method:
@overridebool applyContentDimensions(double minScrollExtent, double maxScrollExtent) { assert(minScrollExtent != null); assert(maxScrollExtent != null); if (!nearEqual(_minScrollExtent, minScrollExtent, Tolerance.defaultTolerance.distance) || !nearEqual(_maxScrollExtent, maxScrollExtent, Tolerance.defaultTolerance.distance) || _didChangeViewportDimensionOrReceiveCorrection) { assert(minScrollExtent != null); assert(maxScrollExtent != null); assert(minScrollExtent <= maxScrollExtent); _minScrollExtent = minScrollExtent; _maxScrollExtent = maxScrollExtent; _haveDimensions = true; applyNewDimensions(); _didChangeViewportDimensionOrReceiveCorrection = false; } return true; }
So we rewrite this method and modify it as follows:
@overridebool applyContentDimensions(double minScrollExtent, double maxScrollExtent) { assert(minScrollExtent != null); assert(maxScrollExtent != null); var outerPosition = coordinator._outerPosition; var outerMaxScrollExtent = outerPosition.maxScrollExtent; var outerPixels = outerPosition.pixels; if (outerMaxScrollExtent != null && outerPixels != null) { maxScrollExtent -= outerMaxScrollExtent - outerPixels; maxScrollExtent = math.max(minScrollExtent, maxScrollExtent); } return super.applyContentDimensions(minScrollExtent, maxScrollExtent); }
In this way, we have successfully solved the two problems mentioned above.
CustomScrollView
Implement floating Index.
SliverPersistentHeader
The Index is fixed on the head.
CupertinoSliverRefreshIndicator
Implement pull-down refresh.
group
New contact page
Edit Avatar
Contact details
Zhengzhou polycystic ovary hospital: http://jbk.39.net/yiyunfengcai/tsyl_zztjyy/1225/
Select label
Zhengzhou infertility hospital: http://www.03913882333.com/
So far, it is basically completed.