側滑菜單效果圖:
{*------------------------------------------------------------------------------
側滑菜單實現
@author 清幽傲竹([email protected])
@version 2016/04/09 1.0 Initial revision.
@todo 側滑菜單實現
@comment 側滑實現參考:http://blog.csdn.net/guolin_blog/article/details/8714621
//側滑實現參考:http://blog.csdn.net/guolin_blog/article/details/8714621
使用方法(舉例往FmainForm上增加側滑菜單面板):
1、放置左側菜單面板TRectangle,命名爲menu_rect,設置屬性:menu_rect.Align=MostLeft;
2、放置內容面板TRectangle,命名爲content_rect,設置屬性:content_rect.Align=Left;
3、FmainForm.Touch.InteractiveGestures.pan需設置爲true
4、procedure TFMain.FormCreate(Sender: TObject);
begin
FEglSlideMenu := TEglSlideMenu.Create(self,menu_rect,content_rect);
end;
procedure TFMain.FormGesture(Sender: TObject;
const EventInfo: TGestureEventInfo; var Handled: Boolean);
begin
FEglSlideMenu.OnGestureDo(sender,EventInfo,Handled);
end;
procedure TFMain.ScrollToMenuImgClick(Sender: TObject);
begin
FEglSlideMenu.scrollToMenu;
end;
5、如果在某個控件上滑動沒有效果,請把該控件的Touch.InteractiveGestures.pan設置爲true,然後OnGesture事件實現指向到FMain.FormGesture事件實現
6、在設計時如要隱藏menu_rect,請把 menu_rect.Margin.left設置爲0-menu_rect.width,如menu_rect寬度爲257,則把 menu_rect.Margin.left設置爲-257,即可
達到隱藏效果,切記不要把 menu_rect.visible設置爲false;
-------------------------------------------------------------------------------}
unit UEglSlideMenu;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,FMX.Controls.Presentation, FMX.Objects, FMX.Layouts,
FMX.Ani, FMX.Gestures,System.Generics.Collections;
type
TxDistanceObj = class
distance: Single;
timeStamp: Cardinal;
end;
TSlideMenuParam = record
SNAP_VELOCITY: Single;//滾動顯示和隱藏menu時,手指滑動需要達到的速度
screenWidth: Integer;//屏幕寬度值
leftEdge: Single;//menu最多可以滑動到的左邊緣。值由menu佈局的寬度來定,marginLeft到達此值之後,不能再減少。
rightEdge: Single;//menu最多可以滑動到的右邊緣。值恆爲0,即marginLeft到達0之後,不能增加。
menuPadding: Single;//menu完全顯示時,留給content的寬度值。
xDown: Single;//記錄手指按下時的橫座標。
xMove: Single;//記錄手指移動時的橫座標。
xUp: Single;//記錄手指擡起時的橫座標。
isMenuVisible: Boolean;//menu當前是顯示還是隱藏。只有完全顯示或隱藏menu時纔會更改此值,滑動過程中此值無效。
distanceX: Single;
MenuLeftMargin: Single;//menurect.margin.left
xDistanceList: TObjectList<TxDistanceObj>;//記錄每次x軸移動的距離 ,<移動的距離,時間戮>
end;
TEglSlideMenu = class(TObject)
private
X1, Y1: Single; // 用來判斷是否要執行單擊事件(記錄onmousedown的座標)
form: TForm;
content_rect: TRectangle;
content_rect_mask: TRectangle;
menu_rect: TRectangle;
FSlideMenuParam: TSlideMenuParam;
procedure content_rect_maskMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
procedure content_rect_maskMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
procedure InitSlideMenuParam;//初始化側滑菜單參數
/// <summary>
/// 判斷當前手勢的意圖是不是想顯示content。如果手指移動的距離是負數,且當前menu是可見的,則認爲當前手勢是想要顯示content。
/// </summary>
/// <returns>當前手勢想顯示content返回true,否則返回false</returns>
function wantToShowContent(): Boolean;
/// <summary>
/// 判斷當前手勢的意圖是不是想顯示menu。如果手指移動的距離是正數,且當前menu是不可見的,則認爲當前手勢是想要顯示menu。
/// </summary>
/// <returns>當前手勢想顯示menu返回true,否則返回false。 </returns>
function wantToShowMenu(): Boolean;
/// <summary>
/// 判斷是否應該滾動將menu展示出來。如果手指移動距離大於屏幕的1/2,或者手指移動速度大於SNAP_VELOCITY,
/// 就認爲應該滾動將menu展示出來。
/// </summary>
/// <returns>如果應該滾動將menu展示出來返回true,否則返回false。 </returns>
function shouldScrollToMenu(): Boolean;
/// <summary>
/// 獲取手指在content界面滑動的速度。
/// </summary>
/// <returns>滑動速度,以每秒鐘移動了多少像素值爲單位</returns>
function getScrollVelocity(): Single;
/// <summary>
/// 判斷是否應該滾動將content展示出來。如果手指移動距離加上menuPadding大於屏幕的1/2,
/// 或者手指移動速度大於SNAP_VELOCITY, 就認爲應該滾動將content展示出來。
/// </summary>
/// <returns>如果應該滾動將content展示出來返回true,否則返回false。 </returns>
function shouldScrollToContent(): Boolean;
/// <summary>
/// 將屏幕滾動到content界面,滾動速度設定爲-30.
/// </summary>
procedure scrollToContent();
public
constructor Create(aForm: TForm;aLeftMenuRect,aContentRect: TRectangle);
destructor Destroy; override;
procedure OnGestureDo(Sender: TObject; const EventInfo: TGestureEventInfo;
var Handled: Boolean);
/// <summary>
/// 將屏幕滾動到menu界面,滾動速度設定爲30.
/// </summary>
procedure scrollToMenu();
end;
implementation
{ TEglSlideMenu }
procedure TEglSlideMenu.content_rect_maskMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
X1 := X; // 記錄鼠標按下時的座標值
Y1 := Y;
end;
procedure TEglSlideMenu.content_rect_maskMouseUp(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
if Sqrt(Sqr(abs(X - X1)) + Sqr(abs(Y - Y1))) < 5 then // 防止鼠標單擊時與滑動衝突
begin
scrollToContent;
end;
end;
constructor TEglSlideMenu.Create(aForm: TForm;aLeftMenuRect, aContentRect: TRectangle);
begin
form := aForm;
menu_rect := aLeftMenuRect;
if not menu_rect.Visible then menu_rect.Visible := True;
content_rect := aContentRect;
Assert(menu_rect.Align = TAlignLayout.MostLeft,'menu_rect.Align需設置爲MostLeft');
Assert(content_rect.Align = TAlignLayout.Left,'(content_rect.Align需設置爲Left');
Assert(TInteractiveGesture.Pan in form.Touch.InteractiveGestures,'form.Touch.InteractiveGestures.pan需設置爲true');
content_rect_mask := TRectangle.Create(aForm);
content_rect_mask.Parent := content_rect;
content_rect_mask.Fill.Kind := TBrushKind.None;
content_rect_mask.Stroke.Kind := TBrushKind.None;
content_rect_mask.Align := TAlignLayout.Contents;
content_rect_mask.OnMouseDown := content_rect_maskMouseDown;
content_rect_mask.OnMouseUp := content_rect_maskMouseUp;
content_rect_mask.Touch.InteractiveGestures := [TInteractiveGesture.Pan];
content_rect_mask.OnGesture := OnGestureDo;
InitSlideMenuParam;
end;
destructor TEglSlideMenu.Destroy;
begin
inherited;
end;
procedure TEglSlideMenu.OnGestureDo(Sender: TObject;
const EventInfo: TGestureEventInfo; var Handled: Boolean);
var
DetalX: Double;
DetalY: Double;
moveX: Double;
aScale: Double;
aObj: TxDistanceObj;
begin
if EventInfo.GestureID = System.UITypes.igiPan then
begin
if TInteractiveGestureFlag.gfBegin in EventInfo.Flags then // 點擊開始移動
begin
// 手指按下時,記錄按下時的橫座標
//xDown = event.getRawX();
FSlideMenuParam.xDown := EventInfo.Location.X;
//創建記錄x軸每次移動的距離對象
if FSlideMenuParam.xDistanceList = nil then
begin
FSlideMenuParam.xDistanceList := TObjectList<TxDistanceObj>.Create();
end;
end
else if (not(TInteractiveGestureFlag.gfBegin in EventInfo.Flags)) and (not(TInteractiveGestureFlag.gfEnd in EventInfo.Flags)) then // 正在移動
begin
//xMove = event.getRawX();
FSlideMenuParam.xMove := EventInfo.Location.X;
//int distanceX = (int) (xMove - xDown);
FSlideMenuParam.distanceX := FSlideMenuParam.xMove - FSlideMenuParam.xDown;
aObj := TxDistanceObj.Create;
aObj.distance := Abs(FSlideMenuParam.distanceX);
aObj.timeStamp := TThread.GetTickCount;
FSlideMenuParam.xDistanceList.add(aObj);
//log.d('eagle',Self,Format('distanceX:%f;leftEdge:%f',[FSlideMenuParam.distanceX,FSlideMenuParam.leftEdge]));
if FSlideMenuParam.isMenuVisible then
begin
FSlideMenuParam.MenuLeftMargin := FSlideMenuParam.distanceX;
end
else
begin
FSlideMenuParam.MenuLeftMargin := FSlideMenuParam.leftEdge + FSlideMenuParam.distanceX;
end;
if FSlideMenuParam.MenuLeftMargin < FSlideMenuParam.leftEdge then
begin
FSlideMenuParam.MenuLeftMargin := FSlideMenuParam.leftEdge;
end
else if FSlideMenuParam.MenuLeftMargin > FSlideMenuParam.rightEdge then
begin
FSlideMenuParam.MenuLeftMargin := FSlideMenuParam.rightEdge;
end;
//log.d('eagle',Self,Format('MenuLeftMargin:%f',[FSlideMenuParam.MenuLeftMargin]));
menu_rect.Margins.Left := FSlideMenuParam.MenuLeftMargin;
content_rect_mask.Visible := FSlideMenuParam.MenuLeftMargin <> FSlideMenuParam.leftEdge;
end
else if TInteractiveGestureFlag.gfEnd in EventInfo.Flags then //移動結束
begin
// Exit;
//xUp = event.getRawX();
FSlideMenuParam.xUp := EventInfo.Location.X;
if wantToShowMenu then
begin
if shouldScrollToMenu then
scrollToMenu
else
scrollToContent;
end
else if wantToShowContent then
begin
if shouldScrollToContent then
scrollToContent
else
scrollToMenu;
end;
if FSlideMenuParam.xDistanceList <> nil then
begin
{$IFDEF AUTOREFCOUNT}
FSlideMenuParam.xDistanceList.DisposeOf;
{$ELSE}
FSlideMenuParam.xDistanceList.Free;
{$ENDIF}
end;
end;
end;
end;
function TEglSlideMenu.getScrollVelocity: Single;
var
aCurTimeStamp: Cardinal;
aObj: TxDistanceObj;
i: Integer;
aCount: Integer;
begin
Result := 0;
aCurTimeStamp := TThread.GetTickCount;
for i := FSlideMenuParam.xDistanceList.Count-1 downto 0 do
begin
if (aCurTimeStamp - FSlideMenuParam.xDistanceList[i].timeStamp) > 1000 then Break;
Result := Result + FSlideMenuParam.xDistanceList[i].distance;
end;
end;
procedure TEglSlideMenu.InitSlideMenuParam;
begin
menu_rect.Align := TAlignLayout.MostLeft;
content_rect.Align := TAlignLayout.Left;
content_rect_mask.Visible := False;
FSlideMenuParam.SNAP_VELOCITY := 200;
FSlideMenuParam.screenWidth := Screen.Width;
FSlideMenuParam.rightEdge := 0;
FSlideMenuParam.menuPadding := FSlideMenuParam.screenWidth - menu_rect.width;//原爲80,現改爲左側菜單寬度恆定
FSlideMenuParam.xDistanceList := nil;
//將menu的寬度設置爲屏幕寬度減去menuPadding
menu_rect.Width := FSlideMenuParam.screenWidth - FSlideMenuParam.menuPadding;
// 左邊緣的值賦值爲menu寬度的負數
FSlideMenuParam.leftEdge := 0 - menu_rect.Width;
// menu的leftMargin設置爲左邊緣的值,這樣初始化時menu就變爲不可見
menu_rect.Margins.Left := FSlideMenuParam.leftEdge;
FSlideMenuParam.isMenuVisible := False;
// 將content的寬度設置爲屏幕寬度
content_rect.Width := FSlideMenuParam.screenWidth;
{$IFDEF MSWINDOWS}
content_rect.Width:= form.Width;//PC上的效果,方便開發調試
{$ENDIF}
end;
procedure TEglSlideMenu.scrollToContent;
begin
menu_rect.AnimateFloat('Margins.Left', FSlideMenuParam.leftEdge, 0.2, TAnimationType.In, TInterpolationType.Linear);
FSlideMenuParam.isMenuVisible := False;
content_rect_mask.Visible := False;
end;
procedure TEglSlideMenu.scrollToMenu;
begin
Assert(menu_rect.Visible,'menu_rect.Visible不可設置爲false');
menu_rect.AnimateFloat('Margins.Left', 0, 0.2, TAnimationType.In, TInterpolationType.Linear);
FSlideMenuParam.isMenuVisible := True;
content_rect_mask.Visible := True;
end;
function TEglSlideMenu.shouldScrollToContent: Boolean;
begin
//return xDown - xUp + menuPadding > screenWidth / 2 || getScrollVelocity() > SNAP_VELOCITY;
Result := (FSlideMenuParam.xDown - FSlideMenuParam.xUp + FSlideMenuParam.menuPadding) > (FSlideMenuParam.screenWidth / 2.0);
Result := Result or (getScrollVelocity > FSlideMenuParam.SNAP_VELOCITY);
end;
function TEglSlideMenu.shouldScrollToMenu: Boolean;
begin
//return xUp - xDown > screenWidth / 2 || getScrollVelocity() > SNAP_VELOCITY;
Result := (FSlideMenuParam.xUp - FSlideMenuParam.xDown) > FSlideMenuParam.screenWidth / 2.0;
Result := Result or (getScrollVelocity > FSlideMenuParam.SNAP_VELOCITY);
end;
function TEglSlideMenu.wantToShowContent: Boolean;
begin
// Result := xUp - xDown < 0 && isMenuVisible
Result := (FSlideMenuParam.xUp - FSlideMenuParam.xDown) < 0;
Result := Result and FSlideMenuParam.isMenuVisible;
end;
function TEglSlideMenu.wantToShowMenu: Boolean;
begin
//return xUp - xDown > 0 && !isMenuVisible;
Result := (FSlideMenuParam.xUp - FSlideMenuParam.xDown) > 0;
Result := Result and (not FSlideMenuParam.isMenuVisible);
end;
end.