AI智能
改变未来

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。

源码如下:

@protectedScrollActivity 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的判断。
@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、当我们手动调用position.moveTo方法滚动到最底部的时候,获取到的maxScrollExtent并不是实际innerPositionmaxScrollExtent,而应该是maxScrollExtent - outerPosition.maxScrollExtent + outerPosition.pixels

接下来我们分析源码看看哪里出了问题。首先,我们看看与之有直接关联的maxScrollExtent方法。

@overridedouble get maxScrollExtent => _maxScrollExtent;

我们看到只是单纯的返_maxScrollExtent,那我们看看_maxScrollExtent是在哪里赋值的,经过查看源码得知,_maxScrollExtent赋值的地方主要在下面这个方法里:

@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;}

所以我们重写这个方法,修改如下:

@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);}

这样我们成功解决了上面提到的两个问题。

CustomScrollView

实现浮动的Index。

SliverPersistentHeader

实现Index固定在头部。

CupertinoSliverRefreshIndicator

实现下拉刷新。

群组

新建联系人页面

编辑头像

联系人详情

至此,基本完成。

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Flutter仿写一个iOS风格的通讯录