FireMonkey下類似手機QQ側滑菜單的實現

側滑菜單效果圖:



{*------------------------------------------------------------------------------
   側滑菜單實現

  @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.


源碼附件:http://download.csdn.net/detail/csm2432/9493901

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章