在 FireMonkey 中,TImageList 是一个集中管理图像资源的组件。其他控件(如 TImage、TButton 等)可以通过设置 ImageIndex 绑定到 ImageList 中的某张图。
但当 ImageList 中的图像被动态修改(例如通过 Images[Index].Assign(...) 替换图片),绑定的控件并不会自动更新——因为 FireMonkey 默认不会监听 ImageList 内容的变化。
为了解决这个问题,TCustomImageList 引入了一个 延迟批量刷新机制:
//变量声明FPlatformTimer: IFMXTimerService;FTimerHandle: TFmxHandle;
IFMXTimerServiceif not TPlatformServices.Current.SupportsPlatformService(IFMXTimerService, FPlatformTimer) thenraise EUnsupportedPlatformService.Create('IFMXTimerService');
SetTimer、iOS 的 NSTimer、Android 的 Handler 等)。TPlatformServices.Current.SupportsPlatformService(...) 获取实例。FPlatformTimer: IFMXTimerServiceFTimerHandle: TFmxHandleUINT_PTR),用于标识和销毁定时器。-1 表示“未创建”。FChangedList: TDictionary<Integer, TCustomDestinationItem>ImageIndex。Links: array of TImageLinkImageList 的控件链接(如 TImage 的内部引用)。FChangedList.AddOrSetValue(ImageIndex, SomeMarker);StartTimer; // 启动刷新定时器
StartTimer —— 创建一次性定时器procedure TCustomImageList.StartTimer;beginif FTimerHandle = TFmxHandle(-1) thenbeginFTimerHandle := FPlatformTimer.CreateTimer(100, TimerProc);end;end;
TimerProc。TimerProc —— 定时器触发后的处理逻辑procedure TCustomImageList.TimerProc;typeTChangedLink = recordLink: TImageLink;Index: Integer;end;varI: Integer;Tmp: TCustomDestinationItem;LChangedLinks: TList<TChangedLink>;LLink: TChangedLink;beginStopTimer;DoChanged;if LinkCount > 0 thenbeginLChangedLinks := TList<TChangedLink>.Create;tryfor I := 0 to LinkCount - 1 doif (Links[I] <> nil) and (Links[I].IgnoreIndex or ((FChangedList <> nil) andFChangedList.TryGetValue(Links[I].ImageIndex, Tmp))) thenbeginLLink.Link := Links[I];LLink.Index := I;LChangedLinks.Add(LLink);end;for I := 0 to LChangedLinks.Count - 1 dobeginLLink := LChangedLinks[I];if LinkContains(LLink.Link, LLink.Index) thenLLink.Link.Change;end;finallyLChangedLinks.Free;end;if FChangedList <> nil thenFChangedList.Clear;end;end;
StopTimer;→ 确保定时器只执行一次(非循环),符合“延迟刷新”语义。
DoChangedDoChanged;→ 通知 ImageList 自身有变更。
for I := 0 to LinkCount - 1 doif (Links[I] <> nil) and(Links[I].IgnoreIndex or((FChangedList <> nil) and FChangedList.TryGetValue(Links[I].ImageIndex, Tmp))) thenbegin// 加入待刷新列表end;
IgnoreIndex = True:表示该链接不依赖具体索引(如自定义绘制),总是刷新。ImageIndex 是否在 FChangedList 中(即是否被修改过)。Link.ChangeLLink.Link.Change;→ 实际触发绑定控件(如 TImage)重绘。
if FChangedList <> nil thenFChangedList.Clear;
| 防抖(Debounce) | |
| 按需刷新 | ImageIndex 匹配) |
| 跨平台兼容 | IFMXTimerService,无需关心底层 OS |
| 资源安全 | |
| 线程安全? |
这段代码充分体现了 FireMonkey 在 UI 响应优化 与 跨平台抽象 方面的成熟设计:
“当数据发生变化时,并不立即触发界面刷新,而是延迟一小段时间,将多次变更合并后统一更新 UI。”
这种“防抖+批量更新”的机制有效避免了频繁重绘带来的性能损耗和视觉闪烁。
不过,在实际使用中仍需特别注意以下两点:
定时器精度受平台影响不同操作系统对定时器的调度策略存在差异。例如,在 Android 的节电模式下,即使设置为 100ms 的定时器,实际触发延迟可能达到 200ms 甚至 500ms。因此,对实时性要求极高的场景需谨慎依赖此类延迟刷新机制。
所有 UI 操作必须在主线程执行FireMonkey 的 UI 控件(包括 TImageList 及其关联组件)并非线程安全。切勿从非主线程直接修改 ImageList 的内容或属性——这不仅可能导致界面未更新,还可能引发崩溃或数据不一致。如需从后台线程触发变更,应通过 TThread.Synchronize 或 TThread.Queue 将操作调度回主线程。