Flutter仿写一个iOS风格的通讯录
此文章主要介紹怎么使用Flutter的Cupertino風格控件,寫一個iOS風格的通訊錄,還有在此過程中遇到的問題及解決辦法。
大家在用Flutter寫App的時候,一般都會使用material風格的控件,因為material風格的控件比較豐富,但是,他在iOS上就會顯得Android氣息比較重,不太適合,所以本文章將通過用仿寫iOS通訊錄,系統地介紹Cupertino控件,及系統的一些底層控件和怎么自己定義優美的適合自己的控件。
由于使用的聯系人三方包的限制,有些功能未能實現,我會持續關注這個聯系人插件的更新,及時加上新功能。
Github地址
首頁
主要用到的控件及問題
CupertinoPageScaffold
一個iOS風格Scaffold,可以添加NavigationBar。
NestedScrollView
實現浮動的NavigationBar和SearchBar。
NestedScrollView我用的自己重寫過的,主要是因為源碼中的有兩個問題。
1、當列表滑動到底部,然后繼續滑動,然后停止,松手,這時候可列表會重新滾動到底部,但是源碼沒有處理當速度等于0的時候的情況,所以當松手的時候,列表會回彈回去,回彈距離小于maxScrollExtent。
源碼如下:
@protected ScrollActivity createInnerBallisticScrollActivity(_NestedScrollPosition position, double velocity) {return position.createBallisticScrollActivity(position.physics.createBallisticSimulation(velocity == 0 ? position as ScrollMetrics : _getMetrics(position, velocity),velocity,),mode: _NestedBallisticScrollActivityMode.inner,); }這里當velocity == 0的時候,直接把innerPosition賦值給了createBallisticSimulation方法的position參數,我們繼續往下看。
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; }這里velocity == 0的時候,執行的是
case _NestedBallisticScrollActivityMode.inner:return _NestedInnerBallisticScrollActivity(coordinator,this,simulation,context.vsync,);這時候的simulation就是上面通過innerPosition得到的,然后傳給了_NestedInnerBallisticScrollActivity,我們在繼續往下看,
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;@overridevoid resetActivity() {delegate.beginActivity(coordinator.createInnerBallisticScrollActivity(delegate,velocity,));}@overridevoid applyNewDimensions() {delegate.beginActivity(coordinator.createInnerBallisticScrollActivity(delegate,velocity,));}@overridebool applyMoveTo(double value) {return super.applyMoveTo(coordinator.nestOffset(value, delegate));} }我們發現這里執行的操作并不是我們想要的,當velocity == 0,滑動距離大于maxScrollExtent的時候,我們只想滾動到列表的最底部,所以我們改一下這里的實現。此處有兩種實現方式:
第一種方式:改_getMetrics方法
// 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) {// shrinkingextra = _outerPosition.minScrollExtent - _outerPosition.pixels; } else if (velocity < 0.0) {// growingextra = _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;這里加上velocity == 0的判斷。
第二種方式:修改createInnerBallisticScrollActivity方法,加上velocity == 0的判斷。
@protected ScrollActivity 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、當我們手動調用position.moveTo方法滾動到最底部的時候,獲取到的maxScrollExtent并不是實際innerPosition的maxScrollExtent,而應該是maxScrollExtent - outerPosition.maxScrollExtent + outerPosition.pixels。
接下來我們分析源碼看看哪里出了問題。
首先,我們看看與之有直接關聯的maxScrollExtent方法。
我們看到只是單純的返_maxScrollExtent,那我們看看_maxScrollExtent是在哪里賦值的,經過查看源碼得知,_maxScrollExtent賦值的地方主要在下面這個方法里:
@override bool 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; }所以我們重寫這個方法,修改如下:
@override bool 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); }這樣我們成功解決了上面提到的兩個問題。
CustomScrollView
實現浮動的Index。
SliverPersistentHeader
實現Index固定在頭部。
CupertinoSliverRefreshIndicator
實現下拉刷新。
群組
新建聯系人頁面
編輯頭像
聯系人詳情
選擇標簽
至此,基本完成。
總結
以上是生活随笔為你收集整理的Flutter仿写一个iOS风格的通讯录的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第1章-确定superboot210如何
- 下一篇: c语言流水灯小程序,流水灯小程序.doc