此文章主要介绍怎么使用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并不是实际innerPositionmaxScrollExtent,而应该是maxScrollExtent - outerPosition.maxScrollExtent + outerPosition.pixels

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

@override
double get maxScrollExtent => _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. android sdk build-tools_从零开始仿写一个抖音App——视频编辑SDK开发(一)

    本文首发于微信公众号--世界上有意思的事,搬运转载请注明出处,否则将追究版权责任.交流qq群:859640274. 大家好久不见,又有一个多月没有发文章了.不知道还有哪些读者记得我的 从零开始仿写抖音 ...

  2. 从零开始仿写一个BiliBili客户端之编译ijkplayer

    章节 从零开始仿写一个B站客户端之-编译ijkplayer 从零开始仿写一个B站客户端之-抓包B站接口 从零开始仿写一个B站客户端之-使用ijkplayer打造一个通用的播放器 从零开始仿写一个B站客 ...

  3. 从零开始仿写一个抖音App

    点击上方"何俊林",马上关注,每天早上8:50准时推送 真爱,请置顶或星标 本文转载自公号开发者技术前线,原文:https://juejin.im/post/5b9e9bf1e51 ...

  4. 吕文翰 php,自己动手写一个 iOS 网络请求库(三)——降低耦合

    自己动手写一个 iOS 网络请求库(三)--降低耦合 2015-5-22 / 阅读数:16112 / 分类: iOS & Swift 本文中,我们将一起降低之前代码的耦合度,并使用适配器模式实 ...

  5. 从零开始仿写一个抖音App——基于FFmpeg的极简视频播放器

    本文首发于微信公众号--世界上有意思的事,搬运转载请注明出处,否则将追究版权责任.微信号:a1018998632,交流qq群:859640274 1.从零开始仿写一个抖音app--开始 4.从零开始仿 ...

  6. HTML: 仿写一个财经类静态的网页

    要求:仿写一个静态的网页,主要采用HTML+CSS+DIV的布局方式, 新建两个文件:demo.html.demo.css 图片素材:image.zip demo.html代码如下: <!doc ...

  7. 礼物说仿写项目iOS源码

    礼物说仿写(updating...) 源码下载: http://code.662p.com/view/14507.html api: 礼物说 首页精选 banner2: http://api.liwu ...

  8. [Electron]仿写一个课堂随机点名小项目

    自从前几个月下了抖音,无聊闲暇时就打会打开抖音,因为打开它有种莫名其妙打开了全世界的感觉... 无意中看到这个小视频:随机点名 于是仿写了一个课堂点名小项目,算是对Electron的一个简单的认识,有 ...

  9. OCRunner 第零篇:从零教你写一个 iOS 热修复框架

    为什么要热修复 在软件开发过程中,很难避免 BUG 的存在,尤其是对于一些达到一定规模的 App 因为协作模式错综复杂,就很容易带着问题上线. 一旦问题上线之后,问题就麻烦了,不仅需要重新打包.测试, ...

  10. QT开发--仿写一个智能家居APP

    一.给工程添加图片 二.引用添加的图片 在界面设置界面点击按钮或者文本控件,右键改变样式表,然后再添加资源选择border-image,然后就可以找到图片进行编译控制. 三.qt布局 水平布局,垂直布 ...

最新文章

  1. MySQL的root用户修改普通用户密码
  2. 从涂鸦到发布 —— 理解API的设计过程
  3. 2020-WEB开发路线图,和即将到来的 2021-WEB技术清单
  4. Python将JSON格式数据转换为SQL语句以便导入MySQL数据库
  5. Android RecyclerView快速上手
  6. 灰度调节_MIUI12稳定版第二批部分全量推送,小米8灰度测试,新增三大亮点
  7. 前端框架 Bootstrap 4.4.0 发布
  8. 关于“稳定”的相爱相杀:负载测试和压力测试
  9. 开机出现 Invalid Partition Table 怎么办
  10. Android广播机制(1)
  11. postgresql用户和角色
  12. 1.1 认识Word 2010操作界面
  13. udal导mysql_teledb-udal实践分享
  14. Unity游戏开发客户端面经——设计模式(初级)
  15. 解决Echarts使用tab切换时只显示第一个tab中图表,其他tab中图表不显示或显示不全问题
  16. ASM算法原理及实现过程
  17. 关于报错ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging
  18. 业务系统日志记录规范总结
  19. 人民币贬值波及海外代购:海淘族成本上涨
  20. 20210217 电感上的寄生电阻

热门文章

  1. db2 linux 64位下载,Redhat6.2 64位 安装DB2V10.5
  2. 天正cad计算机快捷键,天正CAD快捷键大全
  3. Latex安装教程(附美赛论文latex模板)
  4. aurelia_Aurelia历险记:创建自定义PDF查看器
  5. 主流服务器虚拟化技术厂商,各大服务器厂商虚拟化技术剖析
  6. 新浪纯微博html5版,官方出品的精简版!新浪微博轻版App体验
  7. H5页面在自己APP中图标显示正常,接入钉钉请求.woff图标字体报错
  8. Chrome 插件开发
  9. Chromeedge好用扩展插件分享
  10. 《数字图像处理第二版》第三章部分习题