输入框被软键盘遮挡的解决办法(Ability、Dialog都可以)-鸿蒙开发者社区-51CTO.COM

输入框被软键盘遮挡的解决办法(Ability、Dialog都可以) 原创 精华

没用的喵叔
发布于 2021-7-8 08:56
浏览
5收藏

@toc

先放代码:

-----> 喵叔catuncle / TestTextField <-----

因为后来代码更新了,所以文章内容只是解决问题的一个思路。以代码为准

理论上讲通过自定义Adapter,可以适配所有的情况

处理前后对比

处理前 处理前后
输入框被软键盘遮挡的解决办法(Ability、Dialog都可以)-鸿蒙开发者社区 输入框被软键盘遮挡的解决办法(Ability、Dialog都可以)-鸿蒙开发者社区

问题现状

安卓上面,输入框被软键盘遮挡,很简单

xml 配置
android:windowSoftInputMode="adjustPan"
或者,java 配置
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);

这样,软键盘弹出后,输入框就会自动上移。

鸿蒙上也有类似的设置,但是貌似没效果

getWindow().setInputPanelDisplayType(WindowManager.LayoutConfig.INPUT_ADJUST_PAN);

解决过程

原理:

  1. 布局文件用ScrollView包起来
  2. 监听根布局大小变化,变小了,证明输入法弹出了。
  3. 滚动ScrollView,使当前焦点控件显示在软键盘上方。

核心代码:

public class MainAbilitySlice extends AbilitySlice {
    private EventHandler mainHandler = new EventHandler(EventRunner.getMainEventRunner());
    private MyTask myTask = null;
    class MyTask implements Runnable {
        private final int softHeight;
        private final ScrollView root;
        private final Rect decorRect;

        public MyTask(int softHeight, ScrollView root, Rect decorRect) {
            this.softHeight = softHeight;
            this.root = root;
            this.decorRect = decorRect;
        }

        @Override
        public void run() {
            Timber.d("onRefreshed() called with: softHeight = [ %s ]", softHeight);
            Component focusView = root.findFocus();
            int focusTop = focusView.getLocationOnScreen()[1];//焦点控件的左上角
            root.fluentScrollByY(focusTop + focusView.getHeight() - decorRect.top - decorRect.getHeight() + 100);
        }
    }

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        getWindow().setInputPanelDisplayType(WindowManager.LayoutConfig.INPUT_ADJUST_PAN);
        super.setUIContent(ResourceTable.Layout_ability_main);

        Optional<Display> display = DisplayManager.getInstance().getDefaultDisplay(getContext());
        Point pt = new Point();
        display.get().getSize(pt);
        int screenHeight = pt.getPointYToInt();//不包括状态栏(手机时间、wifi显示的那一部分,) 2211,状态栏是129,加起来就是2340
        Timber.d("onRefreshed() called with: screenHeight = [ %s ]", screenHeight);

        ScrollView root = (ScrollView) findComponentById(ResourceTable.Id_root);
        root.setLayoutRefreshedListener(new Component.LayoutRefreshedListener() {
            @Override
            public void onRefreshed(Component component) {
                //包括标题栏,但不包括状态栏。默认 大小 (0,129,1080,2340),top=129即状态栏 , height=2211。 同android的decorView
                Rect decorRect = new Rect();
                component.getWindowVisibleRect(decorRect);
                Timber.d("onRefreshed() called with: rect = [ %s ]", decorRect);
                if (decorRect.getHeight() == 0) {
                    //刚进入界面可能为0
                    return;
                }
                int softHeight = screenHeight - decorRect.getHeight();
                Timber.d("onRefreshed() called with: softHeight = [ %s ]", softHeight);

                if (softHeight > 100) {//当输入法高度大于100判定为输入法打开了
                    if (myTask != null) {
                        mainHandler.removeTask(myTask);
                        myTask = null;
                    }
                    mainHandler.postTask(myTask = new MyTask(softHeight, root, decorRect), 100);
                }
            }
        });
    }
}

----> 完整代码见文末 <----

特别说明: 滚动操作为什么要delay 100毫秒?因为点击一个输入框Component.LayoutRefreshedListener有时会反复调用多次,而且间隔时间小于10毫秒,所以会造成滚动距离不准确。用postTask之后,每次调用的时候会把之前的task remove掉,以最新的一次为准。

计算滚动距离

其中上面的大红框是decorRect(即当前Ability可视区域),下面的大黑框是输入法显示区域。其中,软键盘弹出后,输入框被软键盘挡住,图中的小红框。

所以,要滚动的距离就是图中的C=A-B
输入框被软键盘遮挡的解决办法(Ability、Dialog都可以)-鸿蒙开发者社区

可以优化的点:(有兴趣的朋友可以试一下,记得在评论区分享你的结果)

  1. 如果是Dialog中的输入框,当前的计算方法是否正确?
  2. 如果不用ScrollView,还有别的解决办法吗?
  3. 抽取出工具类或工具方法,代码复用。

2021-08-02优化

  1. 封装了TextFieldHelper工具类

    自动上移TextField。监听软键盘显示、隐藏。

  2. 对于不同的布局处理TextField的移动有差异,所以定义了ITFHAdapter来适配。

    我已经预定义了ScrollViewTFHAdapterDirectionTFHAdapter处理根布局为ScrollViewDirectionalLayout的情况。

    预定义了DlgTFHAdapter处理CommonDialog的情况(通过debug发现CommonDialog的根布局也是ScrollView,但是Dialog有一些特殊情,所以通过继承ScrollViewTFHAdapter来实现)

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2021-8-2 16:29:36修改
5
收藏 5
回复
举报
5条回复
按时间正序
/
按时间倒序
mb609898e2cfb86
mb609898e2cfb86

很有用的分享,回去试一下。

回复
2021-7-8 09:57:36
Whyalone
Whyalone

就贼细节

回复
2021-7-8 10:09:01
Matrixfhd
Matrixfhd

此类应用场景,鸿蒙应该有类似安卓上的简单原生配置方法,不知鸿蒙是否已实现。(楼主说了getWindow().setInputPanelDisplayType(WindowManager.LayoutConfig.INPUT_ADJUST_PAN);配置无效)

回复
2021-7-8 10:14:24
awa_Liny
awa_Liny

这个解决办法在横屏下会报错诶
Selected stacktrace:
java.lang.NullPointerException: Attempt to invoke virtual method 'int[] ohos.agp.components.Component.getLocationOnScreen()' on a null object reference
 at com.liny.hmos.reallinystools.slice.MainAbilitySlice$MyTask.run(MainAbilitySlice.java:46)
 at ohos.eventhandler.EventHandler.distributeEvent(EventHandler.java:887)
 at ohos.eventhandler.O00000O.run(PlatformEventRunner.java:73)
 at android.os.Handler.handleCallback(Handler.java:900)
 at android.os.Handler.dispatchMessage(Handler.java:103)
 at android.os.Looper.loop(Looper.java:219)
 at android.app.ActivityThread.main(ActivityThread.java:8668)
 at java.lang.reflect.Method.invoke(Native Method)
 at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1109)

报错的位置↓

        @Override
        public void run() {
            Timber.d("onRefreshed() called with: softHeight = [ %s ]", softHeight);
            Component focusView = root.findFocus();
            int focusTop = focusView.getLocationOnScreen()[1];//焦点控件的左上角
            root.fluentScrollByY(focusTop + focusView.getHeight() - decorRect.top - decorRect.getHeight() + 180);
        }
    }
3
回复
2022-2-25 22:27:45
琥珀琉璃51cto
琥珀琉璃51cto 回复了 Matrixfhd
此类应用场景,鸿蒙应该有类似安卓上的简单原生配置方法,不知鸿蒙是否已实现。(楼主说了getWindow().setInputPanelDisplayType(WindowManager.LayoutConfig.INPUT_ADJUST_PAN);配置无效)

现在可以了

回复
2022-3-18 19:28:26
回复
    相关推荐