跳到正文
W Winse Blog
dev 3 min read

工作中的数学:不经意间的学有所用,成就感爆棚

大部分的编程工作都是面向对象的、MVC的,调调方法调调接口,或者找找有没有现成的库实现了我们要做的功能,像拼积木。

真正需要拿出笔来进行计算,需要用到数学进行演算的其实很少。就一些比较冷门的、特殊一点的需求,然后还有那么点难度的,才需要自己去实现去笔画笔画思考思考。

记录下实际工作中两个用到数学知识的功能:滚动条上当前页提示地图旋转

# 相似:滚动条上当前页提示

滚动条,我们天天接触的,刷微博看文章都会看到它。

但真正要在滚动条上加点东西,其实也不是一件的容易事情,也没有现成的可以照搬。下面是Google文档的,当鼠标放到滚动条上 时,显示当前视口是第几页 的效果。

滚动条是内容区域的一个缩影,用 滚动条的轨道和滑块 来类比 整个内容和视口区域,实现小小滚动条控制大大的内容的显示。

如果要在滚动条中间显示提示信息,就要找出滑块滚动了多少(thumbTop),以及滑块的长度(thumbHeight)。

转为一个数学问题,用纸笔画一下:

下面在代码中实现,初始化获取 viewportHeighttotalHeight,然后根据推算的公式计算得出滑块当前的偏移量 thumbTop 和高度 thumbHeight :

  void updateViewport(double width, double height) {     debugPrint("viewportHeight: $height");     if (_viewportHeight == height) return;     _viewportHeight = height;     SchedulerBinding.instance.addPostFrameCallback((_) {       _updateViewportInternal();     });   } void _updateViewportInternal() {     if (!_scrollController.position.hasContentDimensions) return;     _totalHeight = _scrollController.position.maxScrollExtent + _scrollController.position.viewportDimension;     debugPrint('contentHeight: $_totalHeight');     _isShowScrollbar = _totalHeight > _viewportHeight;     _thumbHeight = (_viewportHeight / _totalHeight) * _viewportHeight;     _thumbTop = (_scrollTop / _totalHeight) * _viewportHeight;     setState(() {});   }

当页面滚动时,重新计算滑块的偏移量thumbTop,并更新提示信息。

    _scrollController.addListener(() {       setState(() {         _scrollTop = _scrollController.position.pixels;         _thumbTop = (_scrollTop / _totalHeight) * _viewportHeight;         final page = ((_scrollTop + EditorPage.A4HeightInDp / 4) / (EditorPage.gap + EditorPage.A4HeightInDp)).toInt() + 1;         if (currentPage != page) {           _isAutoShown = true;         }         currentPage = page;         _updateTooltip();         _debouncer.value++;       });     });

实现的效果如下,增加了页面跨页时和鼠标放到滚动条上时显示当前页提示信息

# 旋转和投影:地图旋转

日常我们都使用地图进行导航,手机地图会根据我们走的路线旋转,屏幕保持我们向前走的方向。大厂提供的SDK都带了这个功能,但如果是自绘的地图引擎,那仅仅转一下角度 是不够的,旋转后四个角会出现空隙

为了让视口填满地图瓦片,那么就需要扩大加载瓦片的边界。沿着视口的四个角,画平行于旋转后的矩形。理论上,根据扩大后的边界来加载瓦片就能完全的填满视口了。

旋转后(参见下图),需要多加载一圈的瓦片新偏移量 也得往左上角移。新的有效范围 就变成了:新偏移量 到 中心点 的距离 加上 扩大后的新矩形一半的宽高(也可以通过 新偏移量 到 新矩形顶点 加上 新矩形的宽高)。

增加旋转逻辑,计算新的偏移量瓦片的索引坐标 和需要加载瓦片的数量

  List<Widget> _buildTiles() {     List<Widget> tiles = [];     int zoomInt = _zoom.floor();     double rootOffsetX = this._rootOffsetX;     double rootOffsetY = this._rootOffsetY;     int rootTileX = this._rootTileX;     int rootTileY = this._rootTileY;     double w = mapWidth;     double h = mapHeight;     int tilesXNum = ((w - rootOffsetX) / 256).ceil();     int tilesYNum = ((h - rootOffsetY) / 256).ceil();     print("original tiles: $tilesXNum * $tilesYNum, rootTile: ($rootTileX, $rootTileY), rootOffset: ($rootOffsetX, $rootOffsetY)");     if (rotationRad != 0) {       final cosAngle = math.cos(rotationRad).abs();       final sinAngle = math.sin(rotationRad).abs();       final rotatedWidth = (w * cosAngle) + (h * sinAngle);       final rotatedHeight = (h * cosAngle) + (w * sinAngle);       // 扩展的尺寸(旋转后的覆盖视口矩形变大)       finaldouble extraWidth = (rotatedWidth - w) / 2;       finaldouble extraHeight = (rotatedHeight - h) / 2;       // 扩展的瓦片数(向上取整)       finalint extraTilesX = ((extraWidth + rootOffsetX) / _tileSize).ceil();       finalint extraTilesY = ((extraHeight + rootOffsetY) / _tileSize).ceil();       rootTileX -= extraTilesX;       rootTileY -= extraTilesY;       rootOffsetX -= extraTilesX * _tileSize;       rootOffsetY -= extraTilesY * _tileSize;       // 有效宽度的瓦片数       tilesXNum = ((w / 2 - rootOffsetX + rotatedWidth / 2) / 256).ceil();       tilesYNum = ((h / 2 - rootOffsetY + rotatedHeight / 2) / 256).ceil();       print(         "rotated ${((rotationRad * 180 / math.pi) % 360).toStringAsFixed(1)} tiles: $tilesXNum * $tilesYNum, rootTile: ($rootTileX, $rootTileY), rootOffset: ($rootOffsetX, $rootOffsetY)",       );     }     for (int y = 0; y < tilesYNum; y++) {       for (int x = 0; x < tilesXNum; x++) {         double offsetX = rootOffsetX + x * _tileSize;         double offsetY = rootOffsetY + y * _tileSize;         int tileX = rootTileX + x;         int tileY = rootTileY + y;         tiles.add(_buildTile(tileX, tileY, zoomInt, offsetX, offsetY));       }     }     return tiles;   }

注释调试信息,最终效果:

知识、学识,这些在刚开始学习的时刻总觉得既没用又枯燥,但这是一颗种子,种下去了就好好的照看它,长大了会为你遮阳挡雨的。

在我们的程序之路上,除了不懈的努力,有了数学的加持(尽管大部分时间都不怎么用的到),能给我们增添额外的开心和一份持续的坚定。

在 GitHub 上讨论

欢迎通过 GitHub Issue 留言或反馈。每条讨论都会关联到对应文章的源文件路径。

2025-07-31-工作中的数学:不经意间的学有所用,成就感爆棚.md

Related posts