简介:这是一份面向Delphi开发者的Unigui图片轮播示例工程,演示在Unigui框架下实现带左推移平滑动画的轮播组件,覆盖自动播放、手动切换、循环索引、触摸交互与跨平台UI适配,适合深入理解Unigui动画和事件驱动编程的中高级Delphi开发者学习和参考。压缩包共26个文件、256KB,包含源程序、窗体定义、工程文件,以及图片素材、日志和图标资源,虽然体积小巧但目录结构完整,解压后可直接打开工程查看整体设计与效果。已有338人学习下载;通过它可梳理Unigui中TImage、TTimer与布局组件的配合方式,理解图片索引在循环播放中的管理思路,掌握左推移动画的具体写法。同时示例保留了手动切换与触摸事件处理,开发人员可通过配置调整轮播间隔、动画速度等参数,并直接复用到自己的跨平台项目中,作为可裁剪、可定制的轮播组件模板。
1. 拿到这个 rar 包以后,我建议你先弄清楚它到底是什么
前阵子一个做 Delphi 的老同事丢给我一个压缩包,文件名写着"unigui_图片轮播_左推移.rar",说是从某个外包项目里拷出来的,想让我帮忙看看能不能直接用。我解压完扫了一圈,发现这其实就是一套基于 UniGUI 框架写的图片轮播 Demo,核心效果是把传统桌面端的轮播交互搬到了 Web 页面上,动画方向是“左推移”——也就是图片列表整体向左滑动,循环播放。
很多刚接触 UniGUI 的朋友看到这类包会以为下载下来就能跑,实际上你要先搞清楚三件事:你是哪个版本的 Delphi、装了哪个版本的 UniGUI、以及这个包里的组件是否依赖了第三方库。我这边环境是 Delphi 10.4 + UniGUI 1.90.0.1563,打开后编译一次通过,但你如果用的是 Delphi 11 或更新版本,可能要留意 UniGUI 的版本兼容性,尤其是uniGUI自带的uniTimer、uniPanel这些底层组件的行为有细微差异。
这篇博文我会从“项目整体结构”开始,逐步拆解左推移轮播的实现思路、几个核心属性的配置逻辑、我在调试过程中踩过的坑,以及最后怎么把这个组件改造成可以放进真实项目里的形态。代码上不会追求花哨,重点是让你理解为什么这么做,以及下次你自己写的时候怎么避开我走过的弯路。
2. 方案怎么选:为什么是 UniGUI 组件自绘而不是直接塞 HTML
2.1 常见的三种轮播实现路径
UniGUI 做图片轮播,我在网上见过不少讨论,大致可以分为三条路:
第一条路:直接用 HTML + JavaScript 嵌入 UniHTMLFrame。比如在某宝、某东的验证码、广告位这类场景里,用原生的swiper.js或者自己写 CSS3 动画,通过UniHTMLFrame把前端代码塞进 UniGUI 页面。这条路的好处是前端生态丰富,动效上限高;坏处是 UniGUI 的 Ajax 回调和前端事件需要手动桥接,比如点击图片后要把id传回 Delphi 端,你得用js: function或者window.ajaxRequest这类方式,写起来不够“Delphi”。
第二条路:使用 UniGUI 自带的UniCarousel控件。如果你用的是较新版本的 UniGUI,工具箱里确实有一个轮播控件。它的优点是开箱即用,不需要写太多代码;缺点是样式定制能力有限,尤其想做成“左推移连续滚动 + 首尾无缝衔接”这类效果时,往往要通过 CSS Hack 才能实现,而且它的内部实现是依赖 ExtJS 的carousel,在部分浏览器下跑起来会有偶发的白屏。
第三条路:用 UniPanel + UniTimer + 自定义 CSS 实现“伪轮播”。也就是这个 rar 包里采用的方式。核心逻辑是:一个容器面板放多个TUniImage,通过定时器控制整块图片层的left值或transform: translateX(),让图片层向左平移,移出视野的图片再把位置挪回最右边,形成一个循环队列。它的好处是逻辑透明、可控性强、跨浏览器兼容性容易做,而且代码量并不大。
2.2 为什么这个包选择了自绘方案
我看完这个 Demo 的源码,基本可以确定作者是有意选择第三条路的。因为它的核心代码只涉及四个单元:主窗体、轮播框架类、一个定时器、以及一份 CSS 样式文件。没有引入任何第三方 JS 库,也没有依赖 ExtJS 的高级组件。这种极简结构对于做交付型项目非常友好——你不用担心宿主环境被公司安全策略限制了外部 CDN,也不需要每次升级 UniGUI 版本后担心轮播控件的内部 API 变化。
从维护角度看,自绘方案还有一个隐性优势:所有行为都暴露在 Delphi 代码里,客户要求“把轮播速度从 3 秒改为 5 秒”“点击跳到第二张”“首尾循环加个淡入淡出”,你可以直接在 IDE 里改属性或者写事件,不用跑到前端源码里翻。
不过也要提醒一句:自绘方案不是万能的。如果你需要大量的复杂动效(比如 3D 翻转、手势缩放、视差滚动),我建议你还是老老实实走 HTMLFrame + 前端库的路子。轮播这个场景,自绘足够,但不要为了“用 Delphi 解决一切”而硬扛。
3. 左推移动画的核心实现拆解
3.1 图片队列的底层结构
整个轮播组件的核心是“一个容器 + 一个移动层 + N 张图片”。容器通常是TUniPanel,设置好宽度、高度和Layout := absolute,目的是让子控件可以自由定位。移动层是一个承载所有图片的容器,代码里经常就是另一个TUniPanel或者直接把多张TUniImage放进父容器,然后统一改父容器的Left属性。
我倾向于把移动层作为一个整体来管理,而不是单独移动每一张图片。因为如果单独移动图片,你需要保证同一时刻所有图片的位移量完全一致,否则会出现“拖影”或“断层”。用统一容器改Left值,相当于把多张图片用隐形胶带粘成一个长卷,你推动的是整卷,而不是单张纸,逻辑简单,性能也好。
“左推移”的基本运动过程是这样的:初始状态下,第一张图片位于可视区;定时器每个周期把移动层的Left值减少一个步长(方向向左),当Left的绝对值等于单张图片宽度时,说明第一张已经移出了可视区,此时把它从容器头部移到尾部,同时把移动层的Left归零。这样视觉上看起来是无限循环,但代码里你只操作了一次 DOM 节点的顺序变更和一次坐标复位,开销极小。
3.2 定时器驱动的核心参数
如果你看过这个 Demo 的DFM文件,会发现里面有一个UniTimer。它的Interval默认是 3000 毫秒,但需要注意,这个Interval是“多久触发一次移动”,而不是“动画持续多久”。动画时长是通过 CSS 的transition控制的,我在改版后把两者区分开:
FInterval:轮播切换间隔,也就是每张图停留的时间,单位毫秒,建议 3000 到 6000。FAnimationDuration:一次滑动动画的时长,单位毫秒,通常 500 到 800。太短显得生硬,太长会让用户觉得卡顿。FStep:每次动画移动的距离,一般是图片宽度(或宽度的一部分)。
这三个参数配合时,有个常见的坑:如果你把UniTimer.Interval设成和动画时长一样短,动画还没做完就触发了下一次移动,视觉上就会出现“跳帧”或者“卡一半就切图”。所以在代码里,我习惯在每次触发时先禁用 Timer,等动画结束后再启用,防止重入。
demo 里的实现是用了 ExtJS 的动画函数animate,在 UniGUI 的TUniImage上调用,这种方式在组件渲染到浏览器端后是通过修改样式或Ext.Fx来实现的。我不建议在服务端用 Delphi 代码sleep或者用循环做延时,因为 UniGUI 是基于 AJAX 的,服务端的阻塞会直接影响所有在线用户的回话响应。你需要把动画执行完全交给浏览器端。
3.3 “首尾无缝”的实现细节
很多人做到这一步,发现循环播放时老是在最后一张回到第一张的那一刻“闪一下”或者明显空白。原因很简单:你把最后一张挪回第一张时,如果没有一个占位图或者保持连续的图片序列,就会出现一个短暂的空窗期。
正确的做法是,在队列末尾“补”一张和第一张相同的图。当滚到“补的这张”时,用户肉眼看到的是刚刚滑出第一张,马上又有一张和它一样的图接着滑入,此时你静默地把真实的队列复位到第一张的位置,用户根本无感知。这个原理和走马灯、跑马灯是同一个逻辑,理解了这个,你的循环就不会再闪。
这个 Demo 里用的不是“补位法”,而是“移动复位法”——也就是说图片节点本身没有复制,只是每移出一张,就把它的Left改到最右侧之后,再整体归零。这个方法也能无缝,但需要注意的是,如果图片尺寸不一致,复位时会出现跳动。建议使用移动复位法时,强制所有图片的显示尺寸保持一致(比如统一Width := 600; Height := 300),否则只能使用复制补位法。
4. 实操:怎样把这个组件改造成自己的
4.1 从 rar 包解压到集成到主窗体
如果你也拿到了这套代码,文件结构大概是这样的:
Unit.Main.pas:主窗体,里面放了 UniPanel、UniImage、UniTimer。Unit.Carousel.pas:轮播逻辑的框架类,负责创建图片列表、管理位移。style.css:控制动画过渡和图片大小。*.dfm:窗体设计文件。
我建议你不要直接在主窗体里堆代码,而是快速把它抽取成一个可复用的 Frame(TUniFrame),这样你可以在不同页面里调用同一个轮播。我的做法是:
- 新建一个
TFrame或TUniFrame,把 UniPanel、UniTimer 放上去。 - 在 Frame 的公开接口中暴露三个属性:
ImageList: TStringList(存放图片 URL 或本地路径)、Interval: Integer、AnimationDuration: Integer。 - 提供一个
StartPlay方法和StopPlay方法,分别在OnShow和OnHide里调用。
抽取之后的好处是,轮播组件的行为可以和业务页面解耦,你只需要在任意页面里拖入这个 Frame,设置图片列表,然后调用一次StartPlay就行。如果是更复杂的业务,你还可以把“点击图片后触发某个事件”封装成OnImageClick,事件参数里携带Index和图片 URL。
4.2 参数配置的常见取值与调整方法
我把自己调过的一组稳定参数做成表格,你可以直接参考:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| UniTimer.Interval | 4000 | 每张图片停留 4 秒,适合大部分展示场景 |
| AnimationDuration | 600 | 滑动速度适中,不拖沓也不急促 |
| Image Width / Height | 容器宽 600,高 300 | 按实际容器等比缩放 |
| 图片数量 | 3~6 张 | 少于 3 张时循环效果不明显,多于 6 张应分批加载 |
| LoadMask | False | 加载图片时不要用阻塞型遮罩,否则轮播会卡顿 |
这里特别说下图片加载。如果你的图片来自远程 HTTP 地址,第一次加载时会出现空白或延迟。UniGUI 的TUniImage.URL属性指向的是相对路径,加载由浏览器异步处理,所以你在 Delphi 端设置完URL后,图片并不会立刻显示。为了解决这个问题,我通常会在图片容器里先放一个 1x1 的透明占位图(SPacer.gif 或者 base64 字符串),等真实图片加载完成后再替换。如果你有本地图片资源,也可以将其放到可访问的目录下,使用Format('<img src="%s" onload="...">', [Url])的方式显示,但这样就回到了 HTML 和 Delphi 的混用,看你团队的技术栈取舍。
4.3 移动端触控适配
轮播在桌面端只要鼠标点按钮或者靠定时器就能跑,但手机上用户习惯用手指滑。UniGUI 的组件默认没有手势滑动支持,所以你要么手动引入 touch 事件,要么调整按钮大小让触控点击更友好。
我当时的做法是:在 UniHTMLFrame 里嵌入一小段 JavaScript,监听touchstart/touchmove/touchend事件,把滑动距离换算成滚动的页码,再通过Ajax请求回传给 Delphi 端,触发切换逻辑。这个方法实现起来并不难,核心是在 JS 端记录触摸起点clientX,在touchend时判断位移量,超过阈值就调用ajaxRequest。但要注意:如果页面本身有横向滚动需求,这个手势处理会冲突,你需要判断deltaX的绝对值是否大于deltaY,只有横向滑动才交给轮播处理。
如果不想写 JS,还有一个土办法:把左右切换按钮做得足够大(比如 48px 以上),并放在图片两侧的中部区域。移动端用户虽然不能直接滑动图片,但点击左右按钮也可以完成切换,在非核心卖点的项目里完全够用。
5. 常见问题:我收拾过的几类烂摊子
5.1 多页面轮播导致 Timer 重复触发
这是最容易被忽视的问题。如果一个窗口里有多个 Frame,每个 Frame 都放了一个 UniTimer,用户切换页面时,如果你在OnDestroy里没把 Timer 停掉,后台页面仍然会继续触发 AJAX 轮播。等到用户切回时,会出现一下子跳好几张图的现象。
解决办法:在所有涉及轮播的窗体的OnHide事件里调用Timer.Enabled := False,在OnShow里重新打开。如果你用的是 TabSheet,则要监听 Tab 切换事件,在切换时主动启用/禁用。这个坑非常典型,几乎每次接项目都会遇到,写出来给你提个醒。
5.2 左推移动画卡顿,图片经常半截卡住
如果你发现滑动过程中图片像“幻灯片卡带”,十有八九是 CSStransition没有生效,或者你用了Left + UniTimer的方式,但每次位移量太大。
我在调试时发现,UniGUI 的TUniImage.Left改变后,服务端会发送 AJAX 消息给浏览器,浏览器再执行 DOM 操作。如果每次定时器触发一次就发一次 AJAX,高频操作会导致网络往返排队,表现就是卡。优化思路是:把多次位移放在一次请求里完成,或者干脆在前端用requestAnimationFrame做动画,Delphi 端只负责“当前显示第几张”的状态。简单来说,不要让服务端管每一帧动画,服务端只做“宏观指令”,动画过程让浏览器干。
5.3 解压 rar 时碰到问题
这个话题不算核心,但既然热词里提到了,我就补充一下。rar 包如果是从别人那里拷来的,偶尔会遇到加密或者损坏。加密的 rar 大概率是作者加了保护,我不建议去破解,如果你没有密码,直接联系原作者要比较稳妥。如果只是损坏,可以试试用 WinRAR 的“修复”功能,或者把文件扩展名改为.zip,有些情况下它能被通用解压工具识别(不过成功率不高)。对于纯 Delphi 源码交付,我一般会建议对方用 7z 压缩成标准 zip 再发,避免团队里有人没有装解压 rar 的工具。你拿到的这个包,解压后没有密码,直接用 7-Zip 就能处理。
5.4 点击图片后如何拿到下标并把业务数据带过去
轮播图的最终归宿是点击后进入详情页或者触发业务动作。在老代码里,很多人直接给每张图写一个OnClick事件,然后硬编码图片名字。这种做法改起来很痛苦,我建议你在组件里维护一个TList<T图片对象>,每个对象包含ImageURL和Data(可以是字符串或对象),点击事件统一收集,用Sender对应的组件找到它在列表里的下标,再触发对外事件。这样业务层拿到Index和Data后,想怎么跳转都行。
6. 扩展方向:轮播还能怎么玩
如果你的项目已经实现了基础的左推移轮播,再往下走,有几个方向可以进阶:
- 多图同时可见的“焦点图”效果:中间大图,两侧预览相邻图片。这个效果通过调整每张图片的缩放比例和透明度实现,Delphi 端同样只需要维护一个队列和几个状态值,其余交给 CSS 类选择器完成。
- 视频轮播混合模式:轮播中某些页放视频,某些页放图片。做法是在切换时检查当前项的类型,如果是视频就显示
UniHTMLFrame并嵌入播放器,离开时暂停。 - 自动轮播 + 用户悬停暂停:鼠标悬停在轮播区域时停止自动播放,鼠标移开后恢复。这在产品展示场景里很有用,实现只需要监听鼠标事件,简单。
我个人的建议是,如果项目周期紧,不要一上来就追求复杂度,先把“图片能左移、能循环、点击有反应”这三个基础动作做扎实。数据量超过 20 张时,再考虑分页加载和懒加载。轮播本质是 UI 交互中的一个模块,别让它占了项目上太多时间。
7. 我自己的几点体会
这个 rar 包虽然名字不起眼,解压出来以后也不像商业组件那样有花哨的文档,但它把 UniGUI 里一个非常典型的交互需求完整地演示了一遍。我从里面最直接的收获是:UniGUI 的组件模型虽然偏传统,但只要理解了 Ajax 的异步特性,把浏览器端和 Delphi 端的职责划分清楚,写出来的东西一点不比纯前端项目差。
如果你正准备在自己的 UniGUI 项目里加一个图片轮播模块,我的建议是不要直接网上复制一大段代码,先花半天时间把这个包里的 Demo 跑通,然后自己动手改一改:把图片换成你的、把速度调成你喜欢的、把点击事件绑定到你的业务上。这么走一遍以后,你对 UniGUI 事件机制、组件生命周期和 CSS 的配合会有质的理解。这套方法适用于所有类似的组件封装工作,尤其是那些带“定时器”“动画”“列表”关键词的需求,都能用上同一套思路。
本文还有配套的精品资源,点击获取