Delphi 11.0 FMX框架下TCanvas.DrawLine在Android与Windows平台的绘制差异问题问询
Delphi 11 FMX TCanvas.DrawLine 跨平台线条端点位置差异问题
我来帮你拆解这个Delphi FMX跨平台绘制的兼容性问题,结合你已经做的排查,整理出问题根因和可行的解决方向:
问题复现与表现
你遇到的是Delphi 11跨平台应用中TCanvas.DrawLine的绘制行为差异:当线条厚度大于1且使用TStrokeCap.Round线帽时:
- Windows平台(默认使用
TCanvasD2D):线条端点位于线帽的中心位置,符合预期 - Android 8.0.0(华为P10)(使用
TCanvasGpu):线条端点位于线帽的最外侧,与预期不符 - 该问题同样出现在
TImage组件的绘制场景中
复现代码如下:
procedure TMainForm.FormPaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF); begin Canvas.Stroke.Thickness := 20; Canvas.Stroke.Cap := TStrokeCap.Round; Canvas.Stroke.Color := TAlphaColorRec.Red; Canvas.Stroke.Kind := TBrushKind.Solid; Canvas.DrawLine(PointF(20,20), PointF(70,70), 1); Canvas.DrawLine(PointF(70,70), PointF(130,70), 1); end;
根因定位
你已经做了非常精准的排查,这里再梳理一下核心原因:
- Canvas实现差异:Windows平台默认使用
TCanvasD2D,Android平台使用TCanvasGpu,两者的线条绘制逻辑不一致 - 源码bug疑点:
FMX.StrokeBuilder.pas中的TStrokeBuilder.InsertDash方法存在问题——即使在实线(TStrokeDash.Solid)模式下,这段代码也会被FMX.Canvas.GPU.pas调用:
这段代码对线条的起始和结束位置做了不必要的偏移,导致procedure TStrokeBuilder.InsertDash(SrcPos, DestPos: TPointF; const DashDirVec, ThickPerp: TPointF); var InitIndex, DivIndex, Divisions: Integer; SinValue, CosValue: Single; RoundShift: TPointF; begin if FBrush.Cap = TStrokeCap.Round then begin RoundShift := DashDirVec * FHalfThickness; SrcPos := SrcPos + RoundShift; DestPos := DestPos - RoundShift; end;TCanvasGpu绘制的线条端点位置偏移到了线帽外侧,而Windows的TCanvasD2D没有这个偏移逻辑。 - 验证结论:通过设置
FMX.Types.GlobalUseGPUCanvas := True(在Application.Initialize前)让Windows也使用TCanvasGpu,出现了和Android完全一致的问题,确认TCanvasGpu是差异的源头。
另外你提到的Android官方文档说明也很关键:Android原生Paint.Cap的行为应该和Windows一致,说明这是FMX框架封装层的bug,而非Android平台本身的问题。
解决方案建议
1. 优先尝试官方修复路径
- 升级框架版本:检查RAD Studio 11的最新补丁,或直接升级到Delphi 12,这个bug可能已经在后续版本中被修复
- 提交官方bug报告:通过Embarcadero的官方bug追踪系统提交问题,附带复现代码、测试环境信息和你的排查结果,推动官方修复
2. 临时规避方案(尽量减少平台专属代码)
如果暂时无法升级或等待官方修复,这里提供两个可行思路:
方案A:条件编译调整坐标
虽然你希望避免,但这是最直接的临时解决办法,针对Android平台对线条端点做反向偏移:
procedure TMainForm.FormPaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF); var HalfThickness: Single; P1, P2, P3: TPointF; DirVec: TPointF; Len: Single; begin Canvas.Stroke.Thickness := 20; Canvas.Stroke.Cap := TStrokeCap.Round; Canvas.Stroke.Color := TAlphaColorRec.Red; Canvas.Stroke.Kind := TBrushKind.Solid; HalfThickness := Canvas.Stroke.Thickness / 2; P1 := PointF(20,20); P2 := PointF(70,70); P3 := PointF(130,70); {$IFDEF ANDROID} // 调整第一条线的端点:反向偏移半个厚度 DirVec := P2 - P1; Len := Sqrt(DirVec.X*DirVec.X + DirVec.Y*DirVec.Y); if Len > 0 then begin DirVec := DirVec / Len; P1 := P1 + DirVec * HalfThickness; P2 := P2 - DirVec * HalfThickness; end; // 调整第二条线的端点:水平方向反向偏移 P2 := P2 + PointF(HalfThickness, 0); P3 := P3 - PointF(HalfThickness, 0); {$ENDIF} Canvas.DrawLine(P1, P2, 1); Canvas.DrawLine(P2, P3, 1); end;
方案B:自定义StrokeBuilder
如果熟悉FMX源码,可以尝试重写TStrokeBuilder类,修改InsertDash方法的逻辑——当绘制实线时跳过那段偏移代码。不过这个方案需要对FMX框架有一定了解,且需要全面测试避免影响其他绘制逻辑。
3. 第三方测试呼吁
针对你无法测试更高版本Android的需求,这里呼吁其他开发者帮忙验证:如果有使用Delphi 11/12 + Android 9及以上版本的朋友,麻烦运行上述复现代码,确认该问题是否仍然存在,欢迎在评论区反馈结果。
内容的提问来源于stack exchange,提问作者XylemFlow
相关产品推荐
相关产品推荐

