简介:这是基于Unity引擎实现的交互式照片墙展示资源,核心效果为鼠标悬停图片时放大、相邻图片向两侧平滑让位,营造被“挤走”的动态观感,适合学习UGUI交互、动画控制与事件监听的中级开发者,也可作为毕业设计或课程作业的原型参考。包内共824个文件,压缩后约3.33MB,包含unity场景、cs脚本、asset资源、png贴图及dll库等类型,其中asset与unity文件用于恢复场景结构,cs脚本承载交互逻辑,png贴图作为照片素材,可快速还原完整项目环境。已有3328人学习下载。完整覆盖UI系统搭建、RectTransform锚点与偏移布局、Animation动画触发、OnPointerEnter/Exit事件响应等关键知识点,并附带性能优化与精灵图集处理方法,适合作为照片墙交互效果的参考模板,便于在此基础上扩展自定义布局与特效。
1. 先聊清楚:Unity照片墙到底解决什么问题
接到“Unity照片墙”这类需求时,很多人第一反应是在 Canvas 底下堆 ScrollRect 和 Image,结果照片一多就卡成幻灯片,最后不得不回头重做。照片墙本质上不是一个“显示图片”的 UI 控件,而是一个把大量图片组织成可浏览场景的 Unity 功能模块。它要同时解决图片的加载、排列、交互、内存和渲染性能,而不是单纯地把 Texture 贴到屏幕上。
这篇文章要讲的是落地路径:从选型、加载、摆放、交互到性能调优,按我做过一遍的顺序讲清楚。适合正在做数字展厅、相册回顾、产品图墙或虚拟展览的 Unity 开发者,尤其是手里已经有一批照片但不知道怎么高效铺进场景、又担心卡顿和内存的人。你也可以把照片换成商品图、技能图标、作品集缩略图,思路完全通用。
2. 照片墙的架构选型:UI 方案与 3D 相框墙,为什么我最终选了后者
2.1 UI 方案:ScrollRect + Image 的思路与上限
最常见的照片墙做法是在 UGUI 下用 ScrollRect 配合 GridLayoutGroup,把每一张照片做成一个 RawImage 挂在 Content 下面。这个方案的优点是开发速度快,点击查看大图可以直接用一个 Overlay 面板,不需要管相机和射线。对于 10 到 20 张照片的规模,这个方案完全够用,拖进去就能跑,这也是多数教程里演示的形态。
但规模一旦增加到 100 张以上,问题就来了。每一张 RawImage 都会生成一个 CanvasRenderer 参与合批,虽然 UGUI 会把相邻的图片合批,但有遮罩、有层叠、有不同材质时合批会断裂。更关键的是内存:一张 2048×2048 的 RGBA32 纹理在内存里要占 16MB,100 张就是 1.6GB,再加上 mipmap 可能到 2.1GB,移动端直接被杀进程。你以为问题是卡,实际上问题是内存先爆了。
所以这个方案的适用边界很明确:图片数少、不需要 3D 运镜、目标平台是 PC 或内存足够的设备。有人会给它加虚拟滚动、动态加载来硬撑更大的规模,这等于自己实现一套复用机制,绕了一圈复杂度不比 3D 方案低。
2.2 3D 相框墙:相机、相框与挂载点的最小架构
我最终选择的是 3D 场景方案:照片贴到 Quad 或 Plane 上,摆成一面相框墙,相机在场景里移动浏览。这个方案有三个结构上的好处:一是图片数量靠场景节点承载,配合合批可以铺几百张不崩;二是相机控制和点击查看天然支持,不需要额外做 UGUI 的事件转发;三是照片墙本身可以融入更大的数字孪生或展厅场景,而不是孤立的 UI 面板。
最小架构只需要三个部分。第一是挂载点,一个空物体作为整面墙的根节点,后续摆放相框时都作为它的子物体,方便整体平移和旋转。第二是相框,每个相框是一个 Quad,法线朝向相机初始方向,正反面通过材质控制。第三是主相机,通过脚本控制位置和视角,这里就会用到你搜过的 Unity 摄像机跟随相关技巧,但用在照片墙上需要的是“自由漫游 + 目标查看”而不是死死跟着角色。
层级结构我一般这么组织:
PhotoWallRoot ├── Frame_001 │ ├── Quad (MeshRenderer + MeshCollider) │ └── Label (TextMesh,可选) ├── Frame_002 └── ...Quad 是最经济的显示载体,只有 2 个三角形、4 个顶点,一张图对应一个 draw call。把同一面墙的 Quad 都放到同一层,使用同一个材质球(只换纹理),Unity 会自动做 SRP Batcher 或动态合批,draw call 数量不会随照片数线性增长。这个特性是照片墙能铺大规模的基础,后面优化章节我还会再展开。
2.3 照片墙选型对比:从 draw call、内存、开发量三个维度看
| 维度 | UI 方案(ScrollRect + RawImage) | 3D 相框墙方案 |
|---|---|---|
| 最大图片量 | 30 张以内手感可用 | 200~500 张可优化 |
| 内存控制 | 依赖 Resources 卸载,难精确控制 | 可逐张加载释放,按可视区管理 |
| Draw Call | 受遮罩和层级影响,容易合批断裂 | Quad + 单材质,容易合批 |
| 点击交互 | 用 UGUI 事件,需额外做层级穿透 | Physics Raycast,天然带 3D 坐标 |
| 运镜效果 | 只能做 UI 层缩放平移 | 相机自由移动、平滑跟随、动画查看 |
| 开发量 | 前期少,后期补性能费劲 | 前期结构清晰,后期优化有抓手 |
如果你只是做一个小工具给内部用,选 UI 方案没问题;但凡是面向用户的项目,或者你预感到照片会越来越多,我建议直接上 3D 方案。这属于把投入前置,后面不会一边改需求一边补性能债。另外说一句,unity游戏优化的话题总是绕不开 draw call 和内存这两座山,照片墙项目恰好把这两座山都踩了一遍,做一次就知道 Unity 资源管线是怎么回事了。
3. 照片数据链路:从磁盘到显存,把几百张照片安全喂进场景
3.1 导入参数:为什么必须关 mipmap 并把压缩格式切到 ASTC
照片墙里最常见的玄学现象是:照片在 PC 上看着清晰,打包到手机上变糊,或者隔着两米看着正常,走近一看全是马赛克。这其实是纹理导入参数的问题,不是加载代码写得不对。
Unity 对纹理的默认导入设置里有两个坑。第一个是 mipmap 默认开启,这对 3D 模型是合理的,因为远距离采样需要降档;但照片墙的 Quad 始终以接近原始尺寸的屏幕比例展示,mipmap 带来的远距离清晰度收益很低,反而额外占用约三分之一的内存。第二个是压缩格式默认用平台默认的 DXT 或 BC 系列,在移动端(尤其 Android)会退化为不支持的格式,Unity 运行时转格式导致 CPU 峰值飙升。
我一般会给照片墙资源单独建一个目录,再写一个 Editor 脚本批量调整导入参数,而不是手动一张张改。脚本里必改的三项是:mipmap 关闭、压缩格式按平台覆盖、Max Texture Size 按业务限到 1024 或 2048。压缩格式的经验值如下表:
| 平台 | 推荐压缩格式 | 说明 |
|---|---|---|
| Android(中低端) | ASTC 6x6 | 内存占用低,画质可接受 |
| Android(高端)/ iOS | ASTC 4x4 | 画质优先,内存稍微上浮 |
| Windows / Mac Editor | BC7 | 桌面端显示质量最好 |
| WebGL | ASTC 或 ETC2 | 视浏览器支持而定 |
压缩格式是一票决定内存大小的设置,选错后面怎么优化都白搭。ASTC 4x4 比 RGBA32 省将近 8 倍内存,照片墙项目里 200 张 1024×1024 的图用 ASTC 6x6 大约能压到 170MB 左右,这个量级在移动端勉强可以接受。如果你确实要展示高清细节,就不要用 ASTC 8x8 这种极致压缩,否则照片里的文字边缘全是红绿噪点。
3.2 用代码加载并生成 Texture2D:Resources 与 Addressables 两条路径
导入参数只解决编辑器里预制好的资源。如果照片是运行时从服务器拉取的,或者放在 StreamingAssets 里按日期动态更新的,你需要自己写加载逻辑。先看一段最基础的 Resources 加载代码:
using System.Collections; using UnityEngine; using UnityEngine.UI; public class PhotoLoader : MonoBehaviour { public Transform photoWallRoot; // 以帧为单位加载,避免一次性卡死主线程 IEnumerator LoadPhotoSequence(string[] photoNames) { foreach (string name in photoNames) { // Resources.LoadAsync 不会阻断渲染线程 ResourceRequest request = Resources.LoadAsync<Texture2D>("Photos/" + name); yield return request; Texture2D tex = request.asset as Texture2D; if (tex == null) { Debug.LogWarning("照片加载失败: " + name); continue; } CreateFrame(tex); // 每帧只加载一张,把压力摊开 yield return null; } } void CreateFrame(Texture2D tex) { GameObject quad = GameObject.CreatePrimitive(PrimitiveType.Quad); quad.transform.SetParent(photoWallRoot, false); Renderer renderer = quad.GetComponent<Renderer>(); // 只替换纹理,不新建材质,保持合批 renderer.material.mainTexture = tex; } }这里有两个关键参数值得解释。Resources.LoadAsync 的泛型参数决定了返回的资源类型,必须与资源的实际类型一致,否则 request.asset 为空。代码里 CreatePrimitive 会带出 MeshCollider 和默认材质,你需要自行删除 Collider 或在后续步骤单独挂载,否则默认的碰撞体可能覆盖整个 Quad,影响点击判断。
Addressables 的写法区别在于异步句柄的释放方式。Resources 卸载时用 Resources.UnloadUnusedAssets,这是一个全局操作,容易卡顿;Addressables 则把每个资源当成可引用计数的对象,加载时做 Addressables.LoadAssetAsync (key),不再需要时调用 handle.Release()。照片墙这种大量加载、大量释放的场景,用 Addressables 能精确控制每个纹理的生命周期。如果你还没上 Addressables,也不要急着重构,先把 Resources 路径跑通,之后再迁移。
3.3 摆放相框:行列布局、间距与朝向的计算
照片加载上来后,下一步是按网格摆到墙上。这里不需要 GridLayoutGroup,因为相框是 3D 物体,用简单的循环算位置就行。我常用的参数是:每行 6 张,列间距 1.6 米,行间距 1.2 米,Quad 宽 1.5 米、高 1.0 米。这个比例下照片墙整体宽度约 10 米,适合大多数室内展厅场景。
using UnityEngine; public class PhotoWallLayout : MonoBehaviour { public int columns = 6; public float spacingX = 1.6f; public float spacingY = 1.2f; public Vector2 photoSize = new Vector2(1.5f, 1.0f); public void PlaceFrames(Transform wallRoot, int totalCount) { for (int i = 0; i < totalCount; i++) { Transform frame = wallRoot.GetChild(i); int row = i / columns; int col = i % columns; float x = (col - (columns - 1) * 0.5f) * spacingX; float y = row * spacingY; frame.localPosition = new Vector3(x, y, 0); // 朝向墙面法线方向,即 Z 轴正方向 frame.forward = wallRoot.forward; frame.localScale = new Vector3(photoSize.x, photoSize.y, 1f); } } }代码里的核心计算是 x 轴的居中偏移,把每一行的第一个相框和最后一个相对墙中心对称。frame.forward 设置为 wallRoot.forward 的含义是让相框平面与墙根节点法线一致,这能保证所有相框整齐朝向相机,不会出现部分相框斜着看的问题。如果你想让相框单独看向相机,可以用 transform.LookAt,但那是“相册单张特写”的需求,不是照片墙整体效果。
照片墙的布局还有一个容易被忽略的点:行数多到撑出屏幕时,相机视野里只能看到一部分,这时更合理的做法是分面墙摆放,比如三面墙围成 U 型,而不是仅在一面墙上无限往下排。Unreal 和 Unity 的虚拟展厅项目里这个做法很常见,好处是相机移动距离短,浏览体验更接近实体展览。
3.4 运行时更换照片而不重启:纹理句柄与释放循环
业务上经常有“这批照片换掉,下一批重新展示”的需求。许多第一次做的人直接遍历对象、重新设置材质贴图,然后发现内存持续上涨,最后不得不重启应用。问题出在旧纹理没有释放,新纹理又不断加载。
正确做法是维护一个旧纹理列表,在切换批次时先释放旧引用,再加载新图。用 Resources 方式时,释放后调用 Resources.UnloadUnusedAssets 清理真正无人引用的对象;用 Addressables 时,逐个调用旧 handle.Release()。但要注意一点,释放只是把引用计数减一,Unity 不会立刻回收显存,真正的回收发生在 UnloadUnusedAssets 或下一帧的显存整理中。如果切换频繁,建议在切换完成后延迟几帧再做卸载,避免同一帧大量销毁重建导致卡顿。
private Texture2D[] currentTextures; void ClearWall() { foreach (Texture2D tex in currentTextures) { // 使用 Addressables 时这里是 Addressables.Release(handle) Destroy(tex); } currentTextures = null; // 关键:等一帧再执行真正卸载,降低 UWA/Profiler 里的瞬时峰值 StartCoroutine(UnloadLater()); } IEnumerator UnloadLater() { yield return null; Resources.UnloadUnusedAssets(); }这里有一个需要自己评估的参数:UnloadLater 的延迟帧数。延迟过长会让旧纹理多占几帧内存,延迟过短又会在同一帧里和新纹理加载叠加成尖峰。我一般取 1 帧,如果目标平台是低端 Android,改为 3 帧更稳。
4. 交互与相机控制:点击查看、滑动翻页与缩放的手感调校
4.1 用射线拾取相框:从 ScreenPointToRay 到事件回调
照片墙的点击交互不需要 UGUI 的事件系统,直接用物理射线就行。相框上的 Quad 挂一个 BoxCollider,然后写一段射线检测脚本,点击时把命中的相框信息传给照片查看面板。这里有个前置条件:相框所在 Layer 必须单独设一层,不要和地面、墙壁混在一起,否则射线会先命中你不想要的物体。
using UnityEngine; using UnityEngine.EventSystems; public class PhotoWallRaycaster : MonoBehaviour { public Camera mainCamera; public LayerMask frameLayer; void Update() { if (Input.GetMouseButtonUp(0)) { // 如果鼠标点在 UI 上,不响应 3D 点击 if (EventSystem.current.IsPointerOverGameObject()) return; Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit, 100f, frameLayer)) { var frame = hit.collider.GetComponentInParent<PhotoFrame>(); if (frame != null) frame.ShowDetail(); } } } }参数 frameLayer 是核心。射线距离 100 米是指相机到相框的看距离,如果你的展厅场景比这个大,必须调整,否则边缘相框点不中;更准确的写法是直接用 Mathf.Infinity,我在实拍中发现 100 米限制会埋下隐患,所以建议直接写 Infinity。IsPointerOverGameObject 这行是避坑关键,它会拦住所有 UGUI 元素的点击,比如你右上角的“返回”按钮,没有这一行,点 UI 时射线还会穿到后面的相框上。
移动端要把 Input.GetMouseButtonUp(0) 换成 Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Ended,因为 UGUI 的鼠标模拟在触屏上偶尔会有延迟。这个替换在 WebGL 和手机浏览器上尤其重要,Safari 对鼠标事件的模拟与 Android 不一致。
4.2 相机视角控制:平滑跟随与目标查看动画
照片墙的相机控制分成两个模式:全局漫游模式,用 WASD 或摇杆移动,观察整面墙;目标查看模式,点击某张照片后,相机平滑移动到照片正面,并做一个轻微的拉近动画。这里就会用到你搜过的 unity 摄像机跟随,但不是跟在角色背后,而是跟在一条“预计算好的观察路径”上。
using UnityEngine; public class PhotoCameraController : MonoBehaviour { public Transform cameraRig; public float moveSpeed = 4f; public float rotateSpeed = 2f; void Update() { if (Input.GetKey(KeyCode.W)) MoveCamera(Vector3.forward); if (Input.GetKey(KeyCode.S)) MoveCamera(Vector3.back); if (Input.GetKey(KeyCode.A)) MoveCamera(Vector3.left); if (Input.GetKey(KeyCode.D)) MoveCamera(Vector3.right); } void MoveCamera(Vector3 direction) { // 按相机朝向移动,而不是按世界坐标移动 Vector3 forward = cameraRig.forward; forward.y = 0; forward.Normalize(); cameraRig.position += Quaternion.LookRotation(forward) * direction * moveSpeed * Time.deltaTime; } }这段代码里有一个隐蔽的坑:直接替换 cameraRig.position 会失去平滑感。正确做法是让相机的位置用 Vector3.SmoothDamp 在插值缓冲中过渡,而上面的代码是“目标值驱动”的简化版。实际项目中你应该维护一个 targetPosition,每帧更新 targetPosition,再用 SmoothDamp 让当前位置向目标靠近。这个区别在高速移动时尤其明显,直接赋值会导致画面抖动,看起来像拍短视频时手持不稳。
目标查看动画的插值比例我推荐 0.1 到 0.15,对应大概 0.5 秒内从漫游位置过渡到照片正面。过快会产生“瞬移”的不真实感,过慢则让用户觉得相机不听使唤。还有一个细节:查看模式下要禁用漫游输入,否则用户按下方向键会打断动画,然后在半路留下一个歪着脖子的视角。
4.3 分辨率自适配与触屏缩放:从 Screen.width 到运行时的动态调整
照片墙项目最容易被低估的是分辨率适配。PC 上在宽屏看 10 米宽的墙刚刚好,换到手机竖屏只能看到墙上三分之一的内容,再换到 WebGL 嵌入页可能又过宽。我的习惯是启动时读取 Screen.width 和 Screen.height,算出墙的显示范围,然后动态设置相机的透视视野或移动相机到合适的距离。
void AdjustCameraForScreen() { float aspect = (float)Screen.width / Screen.height; // 保证墙的宽高都落在视野内 float requiredDistance = (wallWidth * 0.5f) / Mathf.Tan(mainCamera.fieldOfView * 0.5f * Mathf.Deg2Rad); mainCamera.transform.position = new Vector3(0, 1.6f, requiredDistance * aspect); }这段逻辑里的 requiredDistance 计算要再次强调:fieldOfView 指的是垂直视野角,水平视野由宽高比决定,所以竖屏时要除以 aspect 才能让水平方向的墙完全进入画面。这是分辨率设置里最常见的误区,只调 fieldOfView 不改位置,竖屏下照片还是会溢出屏幕。
触屏缩放同样分两根手指的捏合操作,它与相机距离直接相关。我维护一个 minDistance 和 maxDistance,把捏合幅度映射到相机 Z 轴距离,并限制在区间内,防止用户把相机怼进墙里或者拉得太远看不到墙。移动端触控还有一个绕不开的点:Input.mousePosition 的坐标在 WebGL 和原生端一致,但触控要额外处理多点触控的坐标平均值,否则缩放中心会偏,这在滑动翻页时尤其明显。
5. 常见问题与避坑实录:照片墙翻车的 5 个典型现场
5.1 照片发虚:压缩格式与分辨率在远近观看时的矛盾
现象是同一张照片,站在 3 米外看还可以,走近到 1 米看文字边缘发毛;或者在手机上看整面墙清晰,单点放大后糊成一片。原因是纹理的 Max Size 设置太小,加上 mipmap 开启时远距离采样会自动降档,近距离又没有更高分辨率的纹理可切换。解决方法是:把 Max Texture Size 设置到 2048,压缩格式保持 ASTC 4x4(移动端)或 BC7(桌面端),关闭 mipmap;如果你想保留近距离清晰度,可以按每张照片的实际长宽比生成不同尺寸的变体,运行时根据相机距离选择显示对应的 LOD。如果不做 LOD,那就接受这个折中:照片墙是整体浏览场景,不是单张高清查阅工具。
5.2 内存爆炸:同步加载大量照片时进程直接被杀
现象很直接:Profiler 里 Texture 内存瞬间冲到 1GB 以上,紧接着应用闪退。原因有两个:一是 Resources.Load 是同步操作,加载 200 张图会在同一帧完成所有纹理创建和上传;二是每张纹理开了 mipmap,RGBA32 真彩格式的内存占用比压缩格式高出数倍。解决方法是:全部改用异步加载(Resources.LoadAsync 或 Addressables.LoadAssetAsync),并且每帧只处理一张;把纹理格式统一改成 ASTC 或 BC7;加载前先算出照片总像素数,超过 300MB 就分批加载,显示前几行,后续随相机距离动态补充。这也是 Unity 游戏优化里最基础的一条:永远不要在同一帧做大量 IO 操作。
5.3 相框边缘闪烁裂纹:Z-fighting 与阴影设置的锅
现象是相邻两个相框的交界处出现类似霓虹灯闪烁的细线,镜头拉远后变成一整片噪点。原因是相邻 Quad 的平面几乎共面,加上默认阴影的偏移量不足以区分两个几何面,深度缓冲区无法稳定决定谁在前。解决办法不影响任何人:第一,物理上拉开相框间距,比如 1.6 米间距配合 1.5 米宽的 Quad,留出 0.1 米间隙;第二,调整 Unity 阴影设置里的 Shadow Bounds 或 Light 参数的 Shadow Bias,增大法线偏移;第三,如果你用自定义 Shader 渲染相框,建议在片元阶段直接把透明边缘的深度值加一点偏移,但要做到这一点需要二 pass Shader,太复杂,一般不建议在照片墙项目里做。
5.4 点击穿透:UI 按钮触发不了或触发到背后照片
现象是当你在照片墙之上放一个“上一页”“关闭”按钮时,点击按钮有时成功,有时反而触发了照片的查看缩略图。原因是 EventSystem 同时处理 uGUI 和物理射线事件,3D 射线检测没有过滤掉 UI 所在的层。解决方法是:给 UI Canvas 的 GraphicRaycaster 和一个专门用于 3D 点击的 LayerMask 各管各的区域。具体在代码里加 EventSystem.current.IsPointerOverGameObject() 判断,或者把照片墙的交互层单独命名为 FrameLayer,并把这个 Layer 从相机的 cullingMask 里暂时剔除,只让射线检测这个层。我用的是前者,因为后者改 cullingMask 会影响渲染。
5.5 WebGL 打包后照片缺失:异步加载与服务器路径的时序问题
现象是同样一套代码,在 Editor 里跑一切正常,打包成 WebGL 部署到服务器后,一部分照片加载出来是黑块,刷新几次又恢复了。原因是 WebGL 下的资源加载走 AssetBundle 流式加载,需要访问服务器路径,而你的异步函数可能在加载完成前就尝试填入纹理,或者打包时 AssetBundle 的路径没有配置为远端地址。解决方法是:在 WebGL 平台直接把加载改用 Addressables 的 Remote Load Path 模式,并给加载协程加上超时和失败重试逻辑;同时确认服务器返回了正确的 Content-Type,因为部分静态服务器对 .ab 文件的 MIME 类型不识别,会返回 404 或二进制错误,Unity 里表现为资源加载静默失败。这个坑我踩过两回,最后一次是通过浏览器开发者工具看 Network 面板才发现是 404 而不是代码问题。
6. 进阶:用 Profiler 验证照片墙性能,再把加载收进 Addressables 的按需管线
照片墙做完功能后,不能只看画面效果,要用 Profiler 做一轮系统验证。我习惯抓三个数据:一是 Profiler 里的 Memory > Texture,看每个纹理的实时内存和总数,确认没有异常堆叠;二是 Rendering 面板的 Draw Call,正常状态下照片墙的 Quad 应该被合批成个位数或十几个批次,如果你看到与照片数同量级的批次,说明材质没有被 Unity 合批成功,大概率是某张照片的纹理尺寸不一致导致顶点数据无法合并;三是 CPU Main 的耗时曲线,在加载切换批次时看有没有超过 50ms 的尖峰,如果有,说明异步加载没有彻底生效,某个环节还有同步操作。
加载管线的最终形态是:预加载首批可见的 3 行照片,相机移动到接近墙的末尾时,再触发下一批的 Addressables 加载。这个“可视区驱动加载”的手法比一次性全加载省内存,也比“到哪加载哪”的手感好。我用一个小的环形缓冲队列,把离相机最近和最远的纹理句柄逐一出队入队,同时限制同一时刻在显存的纹理数量不超过 60 张,超出就释放最远的那张。
这个方案唯一的代价是:照片墙刚打开时墙的远端是空的,需要等加载完成才补齐。如果产品要求必须一眼看到全貌,那你还是得全量加载,这时内存优化就变成压缩格式的独角戏——ASTC 是唯一的后悔药。我自己的习惯是:在项目计划阶段就先和需求方确定照片总量和单张分辨率上限,再决定走全量还是按需加载,这个决定晚做一天,后面重构就多费一天。这条路走下来,照片墙最让人头疼的从来不是“显示照片”,而是“显示多少、什么时候显示、显示完怎么释放”这三个问题。希望这些记录能帮你在自己的照片墙项目里少踩一轮坑。
本文还有配套的精品资源,点击获取