DELPHI盒子
!实时搜索: 盒子论坛 | 注册用户 | 修改信息 | 退出
检举帖 | 全文检索 | 关闭广告 | 捐赠
技术论坛
 用户名
 密  码
自动登陆(30天有效)
忘了密码
≡技术区≡
DELPHI技术
lazarus/fpc/Free Pascal
移动应用开发
Web应用开发
数据库专区
报表专区
网络通讯
开源项目
论坛精华贴
≡发布区≡
发布代码
发布控件
文档资料
经典工具
≡事务区≡
网站意见
盒子之家
招聘应聘
信息交换
论坛信息
最新加入: qiaoguoqiang
今日帖子: 0
在线用户: 2
导航: 论坛 -> DELPHI技术 斑竹:liumazi,sephil  
作者:
男 wang_80919 (Flying Wang) ★☆☆☆☆ -
普通会员
2014/3/30 20:34:17
标题:
完美解决 XE5 安卓下 虚拟键盘 隐藏没有事件的 BUG。 浏览:4943
加入我的收藏
楼主: 本代码首发在 QQ群
①FireMonkey[DELPHI]XE5  165232328
----------------------------------------------
(C)(P)Flying Wang
作者:
男 wang_80919 (Flying Wang) ★☆☆☆☆ -
普通会员
2014/3/30 20:34:30
1楼: (**********)
(*          *)
(*     编写:爱吃猪头肉 & Flying Wang     *)
(*      上面的版权声明请不要移除。      *)
(*          2014-03-15          *)
(*          *)
(**********)


找到 XE5 安装的 
FMX.VirtualKeyboard.Android.pas
将他们另存到(复制到)其他目录,例如您的工程目录。

将新复制出的文件加入到您的工程中。

【第一步】
打开 FMX.VirtualKeyboard.Android.pas 找到
function TVirtualKeyboardAndroid.GetVirtualKeyBoardState: TVirtualKeyBoardState;
begin
  if FError then
    Result := [vksError]
  else
    Result := [];
  if IsAutoShow then
    Result := Result + [vksAutoShow];
  if not FError then
  begin
    if FState = vkbsVisible then
      Result := Result + [vksVisible];
  end;
end;


将上面的函数修改为
//Fix Error By 爱吃猪头肉 & Flying Wang
var
  LastVirtualKeyboardHeight: Single = 0;
  IsProcess_VisibleEvent: Boolean = False;
  IsTimerRunning: Boolean = False;
  VirtualKeyboardAndroid: TVirtualKeyboardAndroid;
  LastTimerRunning: TDateTime = 0;

function GetIsTimerRunning: Boolean;
begin
  if IsTimerRunning then
  begin
    if MilliSecondsBetween(Now, LastTimerRunning) > 1000 then
    begin
      IsTimerRunning := False;
    end;
  end;
  Result := IsTimerRunning;
end;

function ObtainKeyboardRect: TRect;
var
  ContentRect, TotalRect: JRect;
begin
  ContentRect := TJRect.Create;
  TotalRect := TJRect.Create;
  MainActivity.getWindow.getDecorView.getWindowVisibleDisplayFrame(ContentRect);
  MainActivity.getWindow.getDecorView.getDrawingRect(TotalRect);
  Result := TRectF.Create(ConvertPixelToPoint(TPointF.Create(TotalRect.left, TotalRect.top + ContentRect.height)),
    ConvertPixelToPoint(TPointF.Create(TotalRect.right, TotalRect.bottom))).Truncate;
end;

function GetVirtualKeyboardHeight: Single;
var
  KeyboardRect: TRect;
begin
  Result := 0;
  KeyboardRect := ObtainKeyboardRect;
  //目前设置为 低于 30 就算隐藏。
  if (KeyboardRect.Width < 30) or (KeyboardRect.Height < 30) then
  begin
    exit;
  end;
  Result := KeyboardRect.Height;
end;

procedure ProcessVirtualKeyboardEvent(Process_VisibleEvent: Boolean = False);
var
  VirtualKeyboard: IFMXVirtualKeyboardService;
  VirtualKeyboardAndroid: TVirtualKeyboardAndroid;
  KeyboardRect: TRect;
  VirtualKeyboardHeight: Single;
begin
  IsProcess_VisibleEvent := Process_VisibleEvent;
  IsTimerRunning := True;
  LastTimerRunning := Now;
  VirtualKeyboardHeight := GetVirtualKeyboardHeight;

  KeyboardRect := ObtainKeyboardRect;
  if TPlatformServices.Current.SupportsPlatformService(IFMXVirtualKeyboardService,
    IInterface(VirtualKeyboard)) then
  begin
    VirtualKeyboardAndroid := TVirtualKeyboardAndroid(VirtualKeyboard);
  end
  else
  begin
    exit;
  end;

  //由于需要本代码完全接管消息发送。所以不能用 FState。
  //if VirtualKeyboardAndroid.FState = vkbsVisible then

  //当上次是显示,当本次不显示的时候。
  if (LastVirtualKeyboardHeight >=1) and (VirtualKeyboardHeight < 1) then
  begin
    TThread.Synchronize(nil,
      procedure
      begin
        VirtualKeyboardAndroid.SetState(TVirtualKeyboardAndroid.TvkbState.vkbsHidden);
        TMessageManager.DefaultManager.SendMessage(VirtualKeyboardAndroid, TVKStateChangeMessage.Create(False, KeyboardRect), True);
      end);
  end
  //当上次是不显示,本次是显示的时候
  else if (LastVirtualKeyboardHeight < 1) and (VirtualKeyboardHeight >= 1) then
  begin
    TThread.Synchronize(nil,
      procedure
      begin
        VirtualKeyboardAndroid.SetState(TVirtualKeyboardAndroid.TvkbState.vkbsVisible);
        if IsProcess_VisibleEvent then
        begin
          TMessageManager.DefaultManager.SendMessage(VirtualKeyboardAndroid, TVKStateChangeMessage.Create(True, KeyboardRect), True);
        end;
    end);
//目前不建议支持高度变化。
//只有横竖切换才发生。
//各位可以在 Form 的 Resize 事件中处理。
//  end
//  //显示中,高度变了。
//  else if
//    (VirtualKeyboardAndroid >= 1) and (LastVirtualKeyboardHeight >=1) and
//    (LastVirtualKeyboardHeight <> VirtualKeyboardAndroid)
//    then
//  begin
//    TThread.Synchronize(nil,
//      procedure
//      begin
//        VirtualKeyboardAndroid.SetState(TVirtualKeyboardAndroid.TvkbState.vkbsVisible);
//        if IsProcess_VisibleEvent then
//        begin
//          TMessageManager.DefaultManager.SendMessage(VirtualKeyboardAndroid, TVKStateChangeMessage.Create(True, KeyboardRect), True);
//        end;
//    end);
  end;
  LastVirtualKeyboardHeight := VirtualKeyboardHeight;
end;

function HideInputForFixVirtualKeyboardEvent :Boolean;
var
  VirtualKeyboard: IFMXVirtualKeyboardService;
  VirtualKeyboardAndroid: TVirtualKeyboardAndroid;
  TextView: JFMXTextEditorProxy;
begin
  Result := False;
  try
    Screen.ActiveForm.Focused := nil;
    if TPlatformServices.Current.SupportsPlatformService(IFMXVirtualKeyboardService,
      IInterface(VirtualKeyboard)) then
    begin
      VirtualKeyboardAndroid := TVirtualKeyboardAndroid(VirtualKeyboard);
    end
    else
    begin
      exit;
    end;
    TextView := MainActivity.getTextEditorProxy;
      CallInUIThread(
        procedure
        begin
          TextView.setFocusable(false);
          TextView.setFocusableInTouchMode(false);
        end);
      Result := True;
  except
    Application.HandleException(Screen.ActiveForm);
  end;
end;

function TVirtualKeyboardAndroid.GetVirtualKeyboardState: TVirtualKeyboardState;
var
  KeyboardRect: TRect;
begin
  if FError then
    Result := [vksError]
  else
    Result := [];
  if IsAutoShow then
    Result := Result + [vksAutoShow];
  if not FError then
  begin
    if (FState = vkbsVisible) then
    begin
      if GetVirtualKeyboardHeight < 1 then
      begin
        KeyboardRect := ObtainKeyboardRect;
        TThread.Synchronize(nil,
          procedure
          begin
          SetState(TVirtualKeyboardAndroid.TvkbState.vkbsHidden);
          //当使用 Timer 之后,就不需要这边的处理了。
          if not GetIsTimerRunning then
          TMessageManager.DefaultManager.SendMessage(Self, TVKStateChangeMessage.Create(False, KeyboardRect), True);
          end);
        end;
    end;
    if FState = vkbsVisible then
      Result := Result + [vksVisible];
  end;
end;

缺点:我的山寨机上 26 的高度就没有输入法了。但是不知道其他的机器是多少。
事实上,只检查高度就可以,为了安全起见,才 高度 宽度 都检查的。


【第二步】
找到 
procedure TVKListener.onVirtualKeyboardShown; 和 
procedure TVKListener.onVirtualKeyboardHidden; 这两个函数
分别修改为下面的代码。
procedure TVKListener.onVirtualKeyboardShown;
begin
  TThread.Synchronize(nil,
    procedure
    begin
      FKeyboardService.SetState(TVirtualKeyboardAndroid.TvkbState.vkbsVisible);
      //当使用 Timer 之后,就不需要这边的处理了。
      if (not IsProcess_VisibleEvent) or (not GetIsTimerRunning) then
        TMessageManager.DefaultManager.SendMessage(Self, TVKStateChangeMessage.Create(true, ObtainKeyboardRect), True);
    end);
  FEvent.SetEvent;
end;

procedure TVKListener.onVirtualKeyboardHidden;
begin
  TThread.Synchronize(nil,
    procedure
    begin
      FKeyboardService.SetState(TVirtualKeyboardAndroid.TvkbState.vkbsHidden);
      //当使用 Timer 之后,就不需要这边的处理了。
      if not GetIsTimerRunning then
        TMessageManager.DefaultManager.SendMessage(Self, TVKStateChangeMessage.Create(false, ObtainKeyboardRect), True);
    end);
  FEvent.SetEvent;
end;

注意:这样改完之后,输入框之间切换,将不会有消息。


【第三步】
然后到 文件的前面(implementation 之前),定义


///  <summary>
///    When < 1, it means VirtualKeyBoard Hided.
///  </summary>
function GetVirtualKeyBoardHeight: Single;

///  <summary>
///    <para>
///      Use a timer to call me. it will fix VirtualKeyboard Hide Message.
///    </para>
///    <para>
///      用一个 TIMER 调用本函数,可以修复虚拟键盘的隐藏消息。
///    </para>
///  </summary>
///  <param name="Process_VisibleEvent">
///    是否处理虚拟键盘的显示事件
///  </param>
procedure ProcessVirtualKeyboardEvent(Process_VisibleEvent: Boolean = False);

///  <summary>
///    强制输入控件隐藏输入。
///  </summary>
function HideInputForFixVisualKeyboradEvent :Boolean;







下面是【使用方法】。

1. 放一个 Timer 例如叫 TimerForVKeyborad。300ms 一次。
procedure TForm1.TimerForVKeyboradTimer(Sender: TObject);
begin
{$IFDEF ANDROID}
  ProcessVisualKeyboradEvent;
{$ENDIF}
end;


2. 完成如下事件。其实完全可以对每个输入框的按下事件处理。参考 4 。
procedure TForm1.FormVirtualKeyboardHidden(Sender: TObject; KeyboardVisible: Boolean; const Bounds: TRect);
begin
  Memo1.Lines.Add('键盘隐藏了');
  Memo1.GoToTextEnd;
  if Focused <> Edit1.AsIControl then //这是为了配合 Edit1 的按下事件做的判断。
    Focused := nil; //这个代码其实也可以。
//{$IFDEF ANDROID}
//  HideInputForFixVisualKeyboradEvent;
//{$ENDIF}
end;

3. 完成如下事件。
procedure TForm1.FormVirtualKeyboardShown(Sender: TObject; KeyboardVisible: Boolean; const Bounds: TRect);
begin
  Memo1.Lines.Add('键盘显示了');
  Memo1.GoToLineEnd;
end;

4 或者对每个输入框的按下事件处理。
uses
  FMX.Platform,
  FMX.VirtualKeyboard;
procedure TForm1.Edit1Click(Sender: TObject);
var
  VirtualKeyboard: IFMXVirtualKeyboardService;
begin
{$IFDEF ANDROID}
  //当没有选中自己的时候不自动弹出。
  if Focused = Edit1.AsIControl then exit;
  if GetVirtualKeyboardHeight < 1 then
  begin
    if TPlatformServices.Current.SupportsPlatformService(IFMXVirtualKeyboardService,
      IInterface(VirtualKeyboard)) then
    begin
      if not (vksVisible in VirtualKeyboard.VirtualKeyBoardState) then
      begin
        if (vksAutoShow in VirtualKeyboard.VirtualKeyBoardState) then
          VirtualKeyboard.ShowVirtualKeyboard(Edit1);
      end;
    end;
  end;
{$ENDIF}
end;
----------------------------------------------
(C)(P)Flying Wang
作者:
男 wang_80919 (Flying Wang) ★☆☆☆☆ -
普通会员
2014/3/30 20:36:17
2楼: 效果图
此帖子包含附件:
JPEG 图像
大小:230.0K
----------------------------------------------
(C)(P)Flying Wang
作者:
男 bpearl (bpearl) ▲▲▲▲△ -
普通会员
2014/3/30 22:12:26
3楼: 第一时间顶老虎的贴!
----------------------------------------------
丢了编码快10年,现在重新捡起来!
作者:
男 wang_80919 (Flying Wang) ★☆☆☆☆ -
普通会员
2014/3/30 22:34:54
4楼: function HideInputForFixVirtualKeyboardEvent :Boolean;
函数里头有垃圾代码。应该删掉多余的。

function HideInputForFixVirtualKeyboardEvent :Boolean;
var
  TextView: JFMXTextEditorProxy;
begin
  Result := False;
  try
    Screen.ActiveForm.Focused := nil;
    TextView := MainActivity.getTextEditorProxy;
      CallInUIThread(
        procedure
        begin
          TextView.setFocusable(false);
          TextView.setFocusableInTouchMode(false);
        end);
      Result := True;
  except
    Application.HandleException(Screen.ActiveForm);
  end;
end;
这样就可以了。
----------------------------------------------
(C)(P)Flying Wang
作者:
男 shileizi (sl) ★☆☆☆☆ -
普通会员
2014/3/31 8:14:26
5楼: mark
----------------------------------------------
-
作者:
男 wang_80919 (Flying Wang) ★☆☆☆☆ -
普通会员
2014/3/31 14:30:02
6楼: 再加个声明。

禁止团购盗版的人使用本人发表的代码。

所谓团购盗版,就是瞒着作者或者作者不同意的团购。

如果作者同意了,那就不是盗版了。
----------------------------------------------
(C)(P)Flying Wang
作者:
男 alongdu (alongdu) ★☆☆☆☆ -
普通会员
2014/3/31 22:18:12
7楼: mark一下下
----------------------------------------------
-
作者:
男 wang_80919 (Flying Wang) ★☆☆☆☆ -
普通会员
2014/4/1 9:43:08
8楼: if Focused = Edit1.AsIControl then exit;
这个写反了,SORRY 。


4 或者对每个输入框的按下事件处理。
uses
  FMX.Platform,
  FMX.VirtualKeyboard;
procedure TForm1.Edit1Click(Sender: TObject);
var
  VirtualKeyboard: IFMXVirtualKeyboardService;
begin
{$IFDEF ANDROID}
  //当没有选中自己的时候不自动弹出。
  if Focused <> Edit1.AsIControl then exit;
  if GetVirtualKeyboardHeight < 1 then
  begin
    if TPlatformServices.Current.SupportsPlatformService(IFMXVirtualKeyboardService,
      IInterface(VirtualKeyboard)) then
    begin
      if not (vksVisible in VirtualKeyboard.VirtualKeyBoardState) then
      begin
        if (vksAutoShow in VirtualKeyboard.VirtualKeyBoardState) then
          VirtualKeyboard.ShowVirtualKeyboard(Edit1);
      end;
    end;
  end;
{$ENDIF}
end;
----------------------------------------------
(C)(P)Flying Wang
作者:
男 wang_80919 (Flying Wang) ★☆☆☆☆ -
普通会员
2014/4/1 9:46:48
9楼: HideInputForFixVirtualKeyboardEvent
打错字了。

HideInputForFixVirtualKeyboradEvent
----------------------------------------------
(C)(P)Flying Wang
作者:
男 wang_80919 (Flying Wang) ★☆☆☆☆ -
普通会员
2014/4/2 10:25:14
10楼: 其实,可以让 TIMER 先停止。
当 OnVirtualKeyboardShown 事件时,再开启TIEMR。
OnVirtualKeyboardHidden事件事,关闭TIMER。

这样就不浪费 CPU 了。

这个思路来自 onechen ,多谢。
----------------------------------------------
(C)(P)Flying Wang
作者:
男 wang_80919 (Flying Wang) ★☆☆☆☆ -
普通会员
2014/4/2 17:31:00
11楼: http://bbs.2ccc.com/topic.asp?topicid=451406
这个办法更好。
----------------------------------------------
(C)(P)Flying Wang
信息
登陆以后才能回复
Copyright © 2CCC.Com 盒子论坛 v3.0.1 版权所有 页面执行89.84375毫秒 RSS