FireMonkey下类似手机QQ侧滑菜单的实现

侧滑菜单效果图:



{*------------------------------------------------------------------------------
   侧滑菜单实现

  @author  清幽傲竹(fjfzcsm@qq.com)
  @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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值