Firemonkey使用TPathData类来存储一系列相连的曲线和直线。查看TPathData的曲线方法源码可知,SmoothCurveTo和QuadCurveTo方法最终还是转化为调用CurveTo方法。而CurveTo是绘制三阶贝塞尔曲线,也就是说TPataData不支持直接绘制二阶贝塞尔曲线。
Firemonkey自带有一个PaintBox控件,但是这个控件仅仅是公布了一个OnPaint事件,真正绘制内容代码还需自己实现。TMS里有个继承自TShape的SignatureCapture控件,查看源码发现其是在Mouse Down/Move/Up 三个事件中记录点并使用的是DrawLine方法进行连线,所以其绘制签名笔迹不够圆滑。OrangeUI里也有一个DrawPanel控件可以手绘笔迹,虽没有源码,但其测试效果也不圆滑,而且在iOS上测试还有一个能绘制到控件区域外的Bug。
这里给出一个使用原生手势识别和BezierPath绘制签名的控件实现源码:
unit FMX.TU2Signature;
interface
uses
System.Classes, System.Types, System.UITypes,
FMX.Types, FMX.Graphics, FMX.Controls, FMX.Objects, FMX.TU2;
type
IBezierPath = interface
procedure MoveTo(const P: TPointF);
procedure LineTo(const P: TPointF); //一阶
procedure QuadTo(const ControlPoint, EndPoint: TPointF); //二阶
procedure CurveTo(const Control1, Control2, EndPoint: TPointF); //三阶
procedure DrawToBitmap(const ACanvas: TCanvas);
procedure Clear;
function IsEmpty: Boolean;
{Update}
procedure Resize(const AWidth, AHeight: Single);
procedure SetPenColor(const Value: TAlphaColor);
procedure SetPenThickness(const Value: Single);
end;
[ComponentPlatformsAttribute(TU2FMXPlatforms)]
TSignature = class(TRectangle)
private
FPrevPoint: TPointF;
FPath: IBezierPath;
FIndex: Integer;
FPenColor: TAlphaColor;
FPenThickness: Single;
private
procedure SetPenColor(const Value: TAlphaColor);
procedure SetPenThickness(const Value: Single);
function GetEmpty: Boolean;
protected
procedure CMGesture(var EventInfo: TGestureEventInfo); override;
procedure Paint; override;
procedure DoResized; override;
public
constructor Create(AOwner: TComponent); override;
procedure Clear;
property Empty: Boolean read GetEmpty;
published
property PenColor: TAlphaColor read FPenColor write SetPenColor;
property PenThickness: Single read FPenThickness write SetPenThickness;
end;
implementation
uses
{$IFDEF IOS}
FMX.TU2Signature.iOS,
{$ENDIF IOS}
{$IFDEF ANDROID}
FMX.TU2Signature.Android,
{$ENDIF}
System.SysUtils;
{ TSignatureControl }
constructor TSignature.Create(AOwner: TComponent);
begin
inherited;
{$IFDEF IOS}
FPath := TiOSBezierPath.Create;
{$ENDIF}
{$IFDEF ANDROID}
FPath :