车机上输入法显示不全问题解决方案

在车机上,导航栏通常在左侧,目的是离驾驶员近一点,方便操作。最近遇到一个问题,发现输入法的键盘会超出屏幕,显示不全。屏幕的物理分辨率为1920x720,输入法为AOSP里原生的输入法,代码路径在/packages/inputmethods/LatinIME 。
全屏页面,输入法显示正常:
image.png
当左侧有导航栏时候,显示效果如下,右侧有部分超出了屏幕,输入法显示不全。
img_v3_028f_63ce34ca-6d75-46b1-b859-06cc9f3c73dg.jpg
用布局检查器查看,键盘是一个自定义view,宽度为1920,期望的宽度应该是1920 - 130(导航栏的宽度) = 1790。
img_v3_028f_2f67e17f-5d40-4ecf-8c2d-a32a3f68040g.jpg
用sougou输入法,存在同样的问题
image.png
image.png

1 排查思路

按常理, 子view通常是不会超出父view的,但是对于直接继承View的自定义view,如果在onMeasure里设置的宽度大于父布局的宽度,就会出现子view超出父view的情况。
看看输入法的代码,重点看看MainKeyboardView,onMeasure的实现在父类KeyboardView

//packages/inputmethods/LatinIME/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
    final Keyboard keyboard = getKeyboard();
    if (keyboard == null) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        return;
    }
    // The main keyboard expands to the entire this {@link KeyboardView}.
    final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
    Log.d(TAG, "width:" + width + ",mOccupiedWidth:" + keyboard.mOccupiedWidth);

    final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
    setMeasuredDimension(width, height);
}

这里可以加个打印,把width打印出来,发现,不管页面是否有左侧导航栏,width都是1920,keyboard.mOccupiedWidth的值也一直是1920。我尝试把width改为1790, 看效果,键盘的布局是不会超出屏幕了,但是依然显示不全,看源码发现,每个按键的宽度是根据keyboard.mOccupiedWidth计算得来的。 直接把keyboard.mOccupiedWidth改为1790呢? 不行,mOccupiedWidth是final变量, 把final去掉再修改呢? 改了,还是不行,因为计算按键宽度的逻辑在onMeasure之前。
所以要重点看看keyboard.mOccupiedWidth的值是怎么来的。代码有点多,我也懒得细看代码调用流程,猜测应该有地方会获取屏幕的分辨率,然后赋值给mOccupiedWidth,搜索代码果然找到获取屏幕分辨率的地方,代码如下。 在这里,我直接return 1790, 然后看上面onMeasure里的width也变成了1790, 再看看在有导航栏的页面,输入法显示也OK。
问题就这么解决了? 肯定没这么简单,如果这里直接返回1790, 在全屏页面下,输入法的右侧会空出一个导航栏的宽度。

//packages/inputmethods/LatinIME/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
public static int getDefaultKeyboardWidth(final Resources res) {
    final DisplayMetrics dm = res.getDisplayMetrics();
    return dm.widthPixels; // 尝试直接返回1790
}

2 解决方案

现在问题转变为:在全屏页面,getDefaultKeyboardWidth 需要返回1920, 而在带有导航栏的页面,需要返回1790。
需要解决的问题有2个:

  1. getDefaultKeyboardWidth在输入法的源码里,怎么更改dm.widthPixels的值?直接在输入法源码吗?

    因为搜狗输入法也有同样的问题,我没法改搜狗输入法的源码,所以只能再想想在哪里修改。
    
  2. 怎么知道导航栏有没有显示?

2.1 怎么修改dm.widthPixels的值?

看看getDefaultKeyboardWidth 的调用栈

at com.android.inputmethod.latin.utils.ResourceUtils.getDefaultKeyboardWidth(ResourceUtils.java:189)
at com.android.inputmethod.keyboard.KeyboardSwitcher.loadKeyboard(KeyboardSwitcher.java:115)
at com.android.inputmethod.latin.LatinIME.onStartInputViewInternal(LatinIME.java:994)
at com.android.inputmethod.latin.LatinIME$UIHandler.onStartInputView(LatinIME.java:510)
at com.android.inputmethod.latin.LatinIME.onStartInputView(LatinIME.java:816)
at android.inputmethodservice.InputMethodService.showWindowInner(InputMethodService.java:1863)
at android.inputmethodservice.InputMethodService.showWindow(InputMethodService.java:1803)
at android.inputmethodservice.InputMethodService$InputMethodImpl.showSoftInput(InputMethodService.java:572)
at android.inputmethodservice.IInputMethodWrapper.executeMessage(IInputMethodWrapper.java:207)
at com.android.internal.os.HandlerCaller$MyHandler.handleMessage(HandlerCaller.java:37)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6683)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:982)

看看showWindowInner, 因为这个方法是距离输入法代码最近的地方。

//frameworks/base/core/java/android/inputmethodservice/InputMethodService.java
void showWindowInner(boolean showInput) {
    //省略部分代码
    if (mShowInputRequested) {
            if (!mInputViewStarted) {
                //注释1
                if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                mInputViewStarted = true;
                onStartInputView(mInputEditorInfo, false);
            }
        } else if (!mCandidatesViewStarted) {
            if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
            mCandidatesViewStarted = true;
            onStartCandidatesView(mInputEditorInfo, false);
        }
    //省略部分代码
}

发现,每次输入法弹出时,都会调到注释1处,而且这段代码在framework里,但是运行在输入法进程。尝试在这里改dm.widthPixels。

//frameworks/base/core/java/android/inputmethodservice/InputMethodService.java
void showWindowInner(boolean showInput) {
    //省略部分代码
    if (mShowInputRequested) {
            if (!mInputViewStarted) {
                //注释1
                if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                mInputViewStarted = true;
                Resources res = getResources();
                if (res != null) {
                    final DisplayMetrics dm = res.getDisplayMetrics();
                    dm.widthPixels = 1790;
                    Log.d(TAG, "mInputEditorInfo:" + mInputEditorInfo.packageName);
                }
                onStartInputView(mInputEditorInfo, false);
            }
        } else if (!mCandidatesViewStarted) {
            if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
            mCandidatesViewStarted = true;
            onStartCandidatesView(mInputEditorInfo, false);
        }
    //省略部分代码
}

先在这里写死dm.widthPixels = 1790; 编译后,push到系统,发现在带有导航栏的页面,输入法显示OK。说明这里改dm.widthPixels的值是有效的。
接下来再看看怎么获取导航栏是否显示, 系统没有相关的API。 如果有读者知道,请告知下,谢谢!

2.2 怎么知道导航栏有没有显示?

看PhoneWindowManager,发现如下代码,每次导航栏显示或者隐藏,会回调到这里。我能想到的是在这里用系统属性记录下导航栏是否显示。

//frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
private final BarController.OnBarVisibilityChangedListener mNavBarVisibilityListener =
        new BarController.OnBarVisibilityChangedListener() {
    @Override
    public void onBarVisibilityChanged(boolean visible) {
        mAccessibilityManager.notifyAccessibilityButtonVisibilityChanged(visible);
    }
};

修改如下:

//frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
private final BarController.OnBarVisibilityChangedListener mNavBarVisibilityListener =
        new BarController.OnBarVisibilityChangedListener() {
    @Override
    public void onBarVisibilityChanged(boolean visible) {
        if (visible) {
            SystemProperties.set("sys.navigationbar.show", "true");
        } else {
            SystemProperties.set("sys.navigationbar.show", "false");
        }
        mAccessibilityManager.notifyAccessibilityButtonVisibilityChanged(visible);
    }
};

2.3 终极解决方案

在InputMethodService里通过系统属性sys.navigationbar.show获取导航栏是否显示,然后修改dm.widthPixels的值

//frameworks/base/core/java/android/inputmethodservice/InputMethodService.java
void showWindowInner(boolean showInput) {
    //省略部分代码
    if (mShowInputRequested) {
            if (!mInputViewStarted) {
                //注释1
                if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                mInputViewStarted = true;
                Resources res = getResources();
                if (res != null) {
                    final DisplayMetrics dm = res.getDisplayMetrics();
                    String naviShow = SystemProperties.get("sys.navigationbar.show");
                    if ("true".equals(naviShow)) {
                        dm.widthPixels = 1790; //减去导航栏的宽度
                    } else {
                        dm.widthPixels = 1920;
                    }
                    Log.d(TAG, "mInputEditorInfo:" + mInputEditorInfo.packageName);
                }
                onStartInputView(mInputEditorInfo, false);
            }
        } else if (!mCandidatesViewStarted) {
            if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
            mCandidatesViewStarted = true;
            onStartCandidatesView(mInputEditorInfo, false);
        }
    //省略部分代码
}

3 搜狗输入法的问题

但是上述解决办法只对AOSP原生的输入法生效。对搜狗输入法不生效。
推测的原因:
搜狗输入法进程开机启动,在进程起来时,拿到的屏幕宽度就是1920, 后面不再获取屏幕宽度,所以在导航栏显示的页面,键盘会超出屏幕,但如果此时把搜狗输入法杀掉,再次弹出输入法,此时拿到的屏幕宽度就是我修改后的1790, 此时输入法显示正常。 这需要搜狗输入法适配下车机。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值