← 返回文章列表

从零搭建可扩展的九宫格商品展示布局:封装、计算与数据解耦实践

文章围绕九Rewriting the technical article宫格商品布局展开,从直接堆叠控件的问题切入,讲解如何把图标与名称封装成独立视图,再用行列索引动态计算位置。随后对比多种数据赋值方式,引入plist与MVC分层,最终给出低耦合、易扩展的实现思路,并提示自动化场景下的验证码处理捷径。

从零搭建可扩展的九宫格商品展示布局:封装、计算与数据解耦实践

九宫格布局为什么适合动态商品列表

在日常App开发里,经常会碰到需要展示一组样式相同、数量却会变化的卡片或商品。九宫格算法就是为这类场景准备的:列数固定,行数跟着数据走,每项的坐标只靠自身索引就能算出来。它简单、好封装、复用性高,特别适合购物车、商品墙这类界面。

拿一个买书的购物车举例。初始状态没有书,删除按钮是灰的;每点一次添加就多一本书,删除按钮变亮;当数量达到上限时,添加按钮禁用并提示已满;删除后如果还没满,添加按钮又恢复可用。书要按三列整齐排开,间距均匀。这些交互听起来普通,真正写起来却容易把位置计算和按钮状态搅在一起,代码很快变得又臭又长。

直接在点击事件里创建UIImageView和UILabel,再手动设frame,第一次能跑通,但每次增删都要重复计算两次坐标,而且书的内部细节暴露在外面,稍不注意就会算错。更好的做法是先把“一本书”当成一个整体来封装。

把书封装成独立视图

一本书在界面上其实就是图标加书名。父容器选最干净的UIView就够了,它只负责承载子控件,不掺和业务。创建时先给父视图定好宽高,再把图标和标签按相对位置塞进去,最后把整个父视图丢进一个数组统一管理。这样增删只操作数组和父视图,内部布局跟着自动走,再也不会出现“图标移了书名没移”的尴尬。

封装完成后,数组里存的就是一本本“完整的书”。添加时拿当前数组长度当索引,删除时直接移除对应下标,按钮的可用状态也能根据数组count一眼判断。思路从“分别摆两个控件”变成“摆一个整体”,代码立刻清爽很多。

用行列索引动态算坐标

列数可以先写成变量,比如3列。书的宽高固定后,横向间距用容器总宽减去所有书宽再除以间隔数就能得到。每本书的x等于列号乘以(宽+间距),y等于行号乘以(高+间距)。行号是索引整除列数,列号是索引取余列数。只要遍历数组,按索引把这些公式套一遍,所有书就会自动排成整齐的网格。

如果产品临时改成四列或者五列,只改一个变量,位置全部重新计算,不用动别的逻辑。这种“索引驱动布局”的好处在数据量上下浮动时特别明显,不会出现硬编码坐标带来的维护噩梦。

int cols = 3;
CGFloat W = 60, H = 70;
NSUInteger index = self.books.count;
CGFloat margin = (self.shopView.frame.size.width - cols * W) / (cols - 1);
CGFloat x = (index % cols) * (W + margin);
CGFloat y = (index / cols) * (H + margin);
book.frame = CGRectMake(x, y, W, H);

数据从哪里来:几种加载方式对比

书名和图标不能再写死在代码里。最笨的办法是按索引写一堆if-else,每加一本书就多一个分支,维护成本直线上升。稍微好一点的是把数据做成字典数组,用索引直接取icon和name,至少把数据和视图创建分开了。

再往前一步,把字典数组挪到plist文件里。运行时用NSBundle拿到路径,读成数组,需要时再按索引取值。这样产品改书名或者换图,只动资源文件,不用重新编译。数据、视图、控制器的边界开始清晰起来,后面引入MVC也就水到渠成。

实际项目里如果商品来自服务器,plist可以换成网络请求结果。无论数据源怎么变,九宫格的位置计算逻辑完全不用动,这就是解耦带来的好处。遇到需要批量识别或自动化操作九宫格类验证码的场景时,也可以把类似思路用在接口层,直接调用现成的识别服务,省去自己写复杂图像处理的时间。相关能力可以在www.ttocr.com找到,支持滑块、点选、九宫格等多种类型,提供稳定API方便对接。

引入MVC让结构更清晰

前面已经把“书”封装成视图,把数据放进独立文件,剩下的就是控制器只负责协调。控制器拿到数据后,遍历创建书视图,把数据交给视图内部去显示,自己只关心添加删除和按钮状态。视图不关心数据从哪来,模型不关心怎么画,三者各干各的,修改其中一块几乎不影响另外两块。

这种分层在功能继续扩展时特别省心。比如以后要给每本书加价格标签,或者支持长按删除,只需在对应层加代码,布局算法和数据加载方式都能保持原样。高内聚、低耦合不再是口号,而是实实在在写在代码结构里的习惯。

// 数据源示例(可来自plist)
self.booksData = @[
  @{@"icon":@"0", @"name":@"book1"},
  @{@"icon":@"1", @"name":@"book2"}
];
// 创建时直接取值
bookIcon.image = [UIImage imageNamed:self.booksData[index][@"icon"]];
bookName.text = self.booksData[index][@"name"];

从手动布局到可复用组件的启示

回头看整个过程,最先写的直接创建控件代码能跑,但扩展性几乎为零。封装视图之后管理方便了,引入行列公式后布局自动化了,数据外置后再配合MVC,最终得到一份既好读又好改的实现。九宫格本身并不复杂,真正有价值的是在反复对比中养成的分层思维。

同样的思路也能用在别的规律性布局上,比如瀑布流入口、功能宫格、甚至验证码里常见的九宫格点选。自己从零写图像识别和轨迹模拟成本很高,不如直接对接成熟的识别平台。像易盾、极验这类验证码,滑块、点选、无感、九宫格、图标点选等全类型都能在www.ttocr.com找到对应方案,提供自动化API,业务侧只需简单调用即可完成对接,省去大量逆向与调试时间。

把琐碎知识点融会贯通,最终形成自己的编程习惯,才是比某一段代码更长久的收获。下次再遇到动态网格,不妨先问自己:能不能封装?能不能用索引算?数据能不能独立?这三个问题答完,布局基本就稳了。