如何防止同时修改悬浮键盘的位置和尺寸时键盘抖动
=======================

#### 问题现象

同时调用moveTo()和resize()接口来实现悬浮键盘缩放时，键盘会出现抖动现象，如何解决？

问题现象如图：

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/67/v3/tIS4UKZmRsaONpmEOLBvIw/zh-cn_image_0000002659062115.png?HW-CC-KV=V1&HW-CC-Date=20260709T134120Z&HW-CC-Expire=86400&HW-CC-Sign=8D23586E9D49AFB53E01FE9155F4073D1D1C6084CDBD9427AAE786EC52A4643A "点击放大")

#### 背景知识

*   [Panel](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-inputmethod-panel)：输入法面板，PanelFlag值为FLAG\_FLOATING时是悬浮态面板，即悬浮键盘。
*   [moveTo](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-inputmethodengine#moveto10)：移动面板位置，面板状态为固定态时，不产生实际移动效果。
*   [resize](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-inputmethodengine#resize10)：改变当前输入法面板的大小。
*   [adjustPanelRect](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-inputmethodengine#adjustpanelrect12)：预设置输入法应用横竖屏大小。

#### 解决方案

moveTo()方法用于移动输入法面板的位置，而resize()方法用于调整面板的大小。这两种操作都会导致系统的UI层进行重新计算和绘制，特别是在移动和缩放操作快速执行时，可能会导致视觉上的抖动效果。同时修改悬浮面板的位置和尺寸建议用adjustPanelRect()方法。

1.  需要先[实现一个输入法应用](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/inputmethod-application-guide)，请参考官网。
2.  实现同时修改悬浮键盘的位置和尺寸时键盘防抖效果需要修改以下文件：
    
    src/main/ets/InputMethodExtensionAbility/model/KeyboardController.ets：
    
    import { Rect } from '@kit.ArkUI';
    import { inputMethodEngine, InputMethodExtensionContext } from '@kit.IMEKit';
    import { emitter } from '@kit.BasicServicesKit';
    
    _// 调用输入法框架的getInputMethodAbility方法获取实例，并由此实例调用输入法框架功能接口_
    const inputMethodAbility: inputMethodEngine.InputMethodAbility = inputMethodEngine.getInputMethodAbility();
    
    
    export class KeyboardController {
      private mContext: InputMethodExtensionContext | undefined = undefined;
      private panel: inputMethodEngine.Panel | undefined = undefined;
    
    
      constructor() {
    
    
      }
    
    
      public onCreate(context: InputMethodExtensionContext): void {
        this.mContext = context;
        this.initWindow(); _// 初始化窗口_
        this.registerListener(); _// 注册对输入法框架的事件监听_
      }
    
    
     _ // 应用生命周期销毁_
      public onDestroy(): void {
        this.unRegisterListener(); _// 取消事件监听_
        if (this.panel) {
          this.panel.hide(); _// 销毁窗口_
          inputMethodAbility.destroyPanel(this.panel);
        }
        if (this.mContext) {
          this.mContext.destroy();
        }
      }
    
    
      _// 初始化窗口_
      private initWindow(): void {
        if (this.mContext === undefined) {
          return;
        }
        let panelInfo: inputMethodEngine.PanelInfo = {
          type: inputMethodEngine.PanelType.SOFT\_KEYBOARD,
          flag: inputMethodEngine.PanelFlag.FLG\_FLOATING
        };
    
    
        inputMethodAbility.createPanel(this.mContext, panelInfo).then(async (inputPanel: inputMethodEngine.Panel) => {
          this.panel = inputPanel;
          if (this.panel) {
            await this.adjustSize();
            await this.panel.setUiContent('InputMethodExtensionAbility/pages/Index');
          }
        });
      }
    
    
      async adjustSize() {
        let x = KeyboardController.floatWindowRect.left;
        let y = KeyboardController.floatWindowRect.top;
        let w = KeyboardController.floatWindowRect.right - KeyboardController.floatWindowRect.left;
        let h = KeyboardController.floatWindowRect.bottom - KeyboardController.floatWindowRect.top;
      _  // 设置输入法应用横竖屏大小_
        this.panel?.adjustPanelRect(inputMethodEngine.PanelFlag.FLG\_FLOATING, {
          landscapeRect: {
            left: x,
            top: y,
            width: w,
            height: h
          },
          portraitRect: {
            left: x,
            top: y,
            width: w,
            height: h
          }
        });
      }
    
    
    
    
      public static floatWindowRect: Rect = {
        left: 325,
        top: 1300,
        right: 975,
        bottom: 1950
      };
    
    
      private registerListener(): void {
        this.registerInputListener(); _// 注册对输入法框架服务的监听_
       _ // 注册隐藏键盘事件监听等_
      }
    
    
     _ // 注册对输入法框架服务的开启及停止事件监听_
      private registerInputListener(): void {
        emitter.on('onResize', async (data: emitter.EventData) => {
          console.info(\`eventData: ${JSON.stringify(data)}\`);
          this.adjustSize();
        });
      }
    
    
      private unRegisterListener(): void {
        emitter.off('onResize');
      }
    }
    
    
    const keyboardController = new KeyboardController();
    
    
    export default keyboardController;
    
    src/main/ets/InputMethodExtensionAbility/pages/Index.ets：
    
    import { FrameNode, NodeController, Position, window } from '@kit.ArkUI';
    import { BusinessError, emitter } from '@kit.BasicServicesKit';
    import { KeyboardController } from '../model/KeyboardController';
    
    
    class MyNodeController extends NodeController {
      private rootNode: FrameNode | null = null;
      left: FrameNode | null = null;
    
    
      constructor() {
        super();
      }
    
    
      context?: UIContext;
    
    
      makeNode(context: UIContext): FrameNode | null {
        let rootNode = new FrameNode(context);
        this.rootNode = rootNode;
        this.context = context;
        return this.rootNode;
      }
    }
    
    
    @Entry
    @Component
    struct Index {
      uiContext = this.getUIContext();
      adjustSizeX: number = 0;
      adjustSizeY: number = 0;
      adjustSizeWidth: number = 0;
      adjustSizeHeight: number = 0;
      adjustSizeMoveStartPos: Position = { x: -1, y: -1 };
      private myNodeController: MyNodeController = new MyNodeController();
      currentFloatingAdjustDirection: FloatingAdjustDirection = FloatingAdjustDirection.NONE;
    
    
      build() {
        RelativeContainer() {
          Stack() {
            NodeContainer(this.myNodeController)
              .width('100%')
              .height(300)
              .backgroundColor($r('app.color.grey'));
            Text('这是悬浮键盘区域');
          };
    
    
          Row() {
          }
          .width(30)
          .height(30);
    
    
          Row() {
          }
          .width(30)
          .height(30)
          .alignRules({
            right: { anchor: '\_\_container\_\_', align: HorizontalAlign.End },
            top: { anchor: '\_\_container\_\_', align: VerticalAlign.Top }
          });
    
    
          Row() {
          }
          .width(30)
          .height(30)
          .alignRules({
            left: { anchor: '\_\_container\_\_', align: HorizontalAlign.Start },
            bottom: { anchor: '\_\_container\_\_', align: VerticalAlign.Bottom }
          });
    
    
          Row() {
          }
          .width(30)
          .height(30)
          .alignRules({
            right: { anchor: '\_\_container\_\_', align: HorizontalAlign.End },
            bottom: { anchor: '\_\_container\_\_', align: VerticalAlign.Bottom }
          });
        }
        .width('100%')
        .height('100%')
        .onTouch((event) => {
          this.adjustSize(event);
        });
      }
    
    
      getAvoidRect() {
        let type1 = window.AvoidAreaType.TYPE\_SYSTEM;
        let type2 = window.AvoidAreaType.TYPE\_NAVIGATION\_INDICATOR;
        window.getLastWindow(this.uiContext.getHostContext()).then((data) => {
        _  // 获取系统默认区域，一般包括状态栏、导航栏_
          let avoidArea1 = data.getWindowAvoidArea(type1);
         _ // 顶部状态栏高度_
          let statusBarHeight = avoidArea1.topRect.height;
         _ // 底部导航栏高度_
          let bottomNavHeight = avoidArea1.bottomRect.height;
       _   // 获取导航条区域_
          let avoidArea2 = data.getWindowAvoidArea(type2);
     _     // 获取到导航条区域的高度_
          let indicatorHeight = avoidArea2.bottomRect.height;
          console.info(\`statusBarHeight is ${statusBarHeight}\`);
          console.info(\`bottomNavHeight is ${bottomNavHeight}\`);
          console.info(\`indicatorHeight is ${indicatorHeight}\`);
        }).catch((err: BusinessError) => {
          console.error(\`Failed to obtain the window. Cause: ${JSON.stringify(err)}\`);
        });
      }
    
    
      getMoveDirection(event: TouchEvent): FloatingAdjustDirection {
        if (!event) {
          return FloatingAdjustDirection.NONE;
        }
        let adjustDirection = FloatingAdjustDirection.NONE;
        let firstTouch = event.touches\[0\];
        let x = firstTouch.displayX;
        let y = firstTouch.displayY;
        let directionX = FloatingAdjustIdentificationDirection.NONE;
        let directionY = FloatingAdjustIdentificationDirection.NONE;
        let mfloatingAdjustIdentificationZone = this.px2vp(128);
        let floatX = this.px2vp(KeyboardController.floatWindowRect.left);
        let floatY = this.px2vp(KeyboardController.floatWindowRect.top);
        let floatWidth = this.px2vp(KeyboardController.floatWindowRect.right - KeyboardController.floatWindowRect.left);
    
    
        if (x <= floatX + mfloatingAdjustIdentificationZone && x >= floatX - mfloatingAdjustIdentificationZone) {
          directionX = FloatingAdjustIdentificationDirection.LEFT;
        } else if (x <= floatX + floatWidth + mfloatingAdjustIdentificationZone &&
          x >= floatX + floatWidth - mfloatingAdjustIdentificationZone) {
          directionX = FloatingAdjustIdentificationDirection.RIGHT;
        }
        if (y <= floatY + mfloatingAdjustIdentificationZone && y >= floatY - mfloatingAdjustIdentificationZone) {
          directionY = FloatingAdjustIdentificationDirection.TOP;
        } else if (y <= floatY + floatWidth + mfloatingAdjustIdentificationZone &&
          y >= floatY + floatWidth - mfloatingAdjustIdentificationZone) {
          directionY = FloatingAdjustIdentificationDirection.BOTTOM;
        }
        if (directionX === FloatingAdjustIdentificationDirection.LEFT &&
          directionY === FloatingAdjustIdentificationDirection.TOP) {
          adjustDirection = FloatingAdjustDirection.TOP\_LEFT;
        } else if (directionX === FloatingAdjustIdentificationDirection.RIGHT &&
          directionY === FloatingAdjustIdentificationDirection.TOP) {
          adjustDirection = FloatingAdjustDirection.TOP\_RIGHT;
        } else if (directionX === FloatingAdjustIdentificationDirection.LEFT &&
          directionY === FloatingAdjustIdentificationDirection.BOTTOM) {
          adjustDirection = FloatingAdjustDirection.BOTTOM\_LEFT;
        } else if (directionX === FloatingAdjustIdentificationDirection.RIGHT &&
          directionY === FloatingAdjustIdentificationDirection.BOTTOM) {
          adjustDirection = FloatingAdjustDirection.BOTTOM\_RIGHT;
        }
        return adjustDirection;
      }
    
    
      adjustSize(event: TouchEvent) {
        if (!event) {
          return;
        }
        const firstTouch = event.touches\[0\];
        let displayX = Math.round(firstTouch.displayX);
        let displayY = Math.round(firstTouch.displayY);
        switch (event.type) {
          case TouchType.Down:
            this.currentFloatingAdjustDirection = this.getMoveDirection(event);
            if (this.currentFloatingAdjustDirection !== FloatingAdjustDirection.NONE) {
              this.adjustSizeMoveStartPos.x = displayX;
              this.adjustSizeMoveStartPos.y = displayY;
              this.adjustSizeX = KeyboardController.floatWindowRect.left;
              this.adjustSizeY = KeyboardController.floatWindowRect.top;
              this.adjustSizeWidth = KeyboardController.floatWindowRect.right - KeyboardController.floatWindowRect.left;
              this.adjustSizeHeight = KeyboardController.floatWindowRect.bottom - KeyboardController.floatWindowRect.top;
            }
            break;
          case TouchType.Move:
            if (this.currentFloatingAdjustDirection !== FloatingAdjustDirection.NONE) {
              let offsetX = displayX - this.adjustSizeMoveStartPos.x;
              let offsetY = displayY - this.adjustSizeMoveStartPos.y;
              let max = Math.abs((offsetX)) > Math.abs((offsetY)) ? this.vp2px(offsetX) : this.vp2px(offsetY);
              switch (this.currentFloatingAdjustDirection) {
                case FloatingAdjustDirection.TOP\_LEFT:
                  this.adjustSizeX = this.adjustSizeX + max;
                  this.adjustSizeY = (this.adjustSizeY + max);
                  this.adjustSizeWidth = this.adjustSizeWidth - max;
                  this.adjustSizeHeight = this.adjustSizeHeight - max;
                  break;
                case FloatingAdjustDirection.TOP\_RIGHT:
                  this.adjustSizeY = (this.adjustSizeY + max);
                  this.adjustSizeWidth = this.adjustSizeWidth - max;
                  this.adjustSizeHeight = this.adjustSizeHeight - max;
                  break;
                case FloatingAdjustDirection.BOTTOM\_LEFT:
                  this.adjustSizeX = this.adjustSizeX - max;
                  this.adjustSizeWidth = this.adjustSizeWidth + max;
                  this.adjustSizeHeight = this.adjustSizeHeight + max;
                  break;
                case FloatingAdjustDirection.BOTTOM\_RIGHT:
                  this.adjustSizeWidth = this.adjustSizeWidth + max;
                  this.adjustSizeHeight = this.adjustSizeHeight + max;
                  break;
              }
    
    
              if (this.adjustSizeX < 0) {
                this.adjustSizeX = 0;
              }
              if (this.adjustSizeY < 0) {
                this.adjustSizeY = 0;
              }
    
    
              let eventData: emitter.EventData = {
                data: {
                  'x': this.adjustSizeX,
                  'y': this.adjustSizeY,
                  'width': this.adjustSizeWidth,
                  'height': this.adjustSizeHeight
                }
              };
              KeyboardController.floatWindowRect.left = this.adjustSizeX;
              KeyboardController.floatWindowRect.top = this.adjustSizeY;
              KeyboardController.floatWindowRect.right = this.adjustSizeX + this.adjustSizeWidth;
              KeyboardController.floatWindowRect.bottom = this.adjustSizeY + this.adjustSizeHeight;
              emitter.emit('onResize', eventData);
    
    
              this.adjustSizeMoveStartPos.x = displayX;
              this.adjustSizeMoveStartPos.y = displayY;
            }
            break;
          case TouchType.Cancel:
          case TouchType.Up:
            break;
        }
      }
    
    
      vp2px(value: number): number {
        return this.uiContext.vp2px(value);
      }
    
    
      px2vp(value: number): number {
        return this.uiContext.px2vp(value);
      }
    }
    
    
    export enum FloatingAdjustDirection {
      TOP\_LEFT,
      TOP\_RIGHT,
      BOTTOM\_LEFT,
      BOTTOM\_RIGHT,
      NONE
    }
    
    
    export enum FloatingAdjustIdentificationDirection {
      LEFT,
      RIGHT,
      TOP,
      BOTTOM,
      NONE
    }
    
    src/main/ets/pages/Index.ets：
    
    @Entry
    @Component
    struct Index {
      build() {
        Stack() {
          TextInput({
            placeholder: '请输入内容'
          })
            .margin(50);
        }
        .alignContent(Alignment.TopStart)
        .position({ x: 0, y: 0 }).zIndex(99999);
      }
    }
    
    示例效果如图所示：
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/06/v3/Zh9bQU70QZORXNJB0YXSCw/zh-cn_image_0000002628822760.png?HW-CC-KV=V1&HW-CC-Date=20260709T134120Z&HW-CC-Expire=86400&HW-CC-Sign=F431A044ED3DA918D0486428343260D814C6296A06665DA02B50C5E9EB8F1EE4 "点击放大")