Article
工作中的数学:不经意间的学有所用,成就感爆棚
大部分的编程工作都是面向对象的、MVC的,调调方法调调接口,或者找找有没有现成的库实现了我们要做的功能,像拼积木。
真正需要拿出笔来进行计算,需要用到数学进行演算的其实很少。就一些比较冷门的、特殊一点的需求,然后还有那么点难度的,才需要自己去实现去笔画笔画思考思考。
记录下实际工作中两个用到数学知识的功能:滚动条上当前页提示 和地图旋转。
# 相似:滚动条上当前页提示
滚动条,我们天天接触的,刷微博看文章都会看到它。
但真正要在滚动条上加点东西,其实也不是一件的容易事情,也没有现成的可以照搬。下面是Google文档的,当鼠标放到滚动条上 时,显示当前视口是第几页 的效果。

滚动条是内容区域的一个缩影,用 滚动条的轨道和滑块 来类比 整个内容和视口区域,实现小小滚动条控制大大的内容的显示。
如果要在滚动条中间显示提示信息,就要找出滑块滚动了多少(thumbTop),以及滑块的长度(thumbHeight)。
转为一个数学问题,用纸笔画一下:

下面在代码中实现,初始化获取 viewportHeight, totalHeight,然后根据推算的公式计算得出滑块当前的偏移量 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;
}
注释调试信息,最终效果:

知识、学识,这些在刚开始学习的时刻总觉得既没用又枯燥,但这是一颗种子,种下去了就好好的照看它,长大了会为你遮阳挡雨的。
在我们的程序之路上,除了不懈的努力,有了数学的加持(尽管大部分时间都不怎么用的到),能给我们增添额外的开心和一份持续的坚定。
Related
Related posts
-
Cursor Agent 编程小小心得
2026-06-11
-
有一种自由叫做程序员的自由:把公众号文章镜像回自己的博客
2026-06-02
-
树莓派 OpenClaw Browser 看不见摸不着?给它配个 VNC 图形环境,踏实安心的Debug
2026-03-09
-
从使用者到创造者:用 AI 构建你的专属 VS Code 工具链
2026-02-27