共计 7078 个字符,预计需要花费 18 分钟才能阅读完成。
download:PHP+Go 开发仿简书,实战高并发高可用微服务架构完结无密
Android 完满解决了输入框被遮挡的问题。
序
前段时间呈现了 webview 的输入框被软键盘挡住的问题。通过解决,顺便做了一些输入框栏目的汇总。
在失常状况下,输入框被阻塞
个别状况下,输入框被输入法屏蔽。个别能够通过设置 softInputMode 为 window 来解决。
window.getAttributes()。softInputMode = WindowManager。LayoutParams.XXX
有三种状况:
(1)SOFT_INPUT_ADJUST_RESIZE: 布局将由软键盘置顶。
(2)SOFT_INPUT_ADJUST_PAN: 只向上推输入框 (即只向上推一部分间隔)
(3)SOFT_INPUT_ADJUST_NOTHING: 什么都不做(就是什么都不做)
SOFT_INPUT_ADJUST_PAN 和 SOFT_INPUT_ADJUST_RESIZE 的区别在于,SOFT_INPUT_ADJUST_PAN 只是把输入框放在下面,而 SOFT_INPUT_ADJUST_RESIZE 会把整个布局放在下面,会有一种输入框显示和暗藏时布局高度动态变化的视觉效果。
如果你的输入框梗塞,个别能够通过设置 SOFT_INPUT_ADJUST_PAN 来解决。如果你的输入框没有被屏蔽,然而软键盘弹出来了,布局就会被推上去。如果不想上推,能够设置 SOFT_INPUT_ADJUST_NOTHING。
SoftInputMode 是 window 的属性。你在 Mainifest 中为 Activity 设置,也为 window 设置。如果是 Dialog 或者 popupwindow,能够通过 getWindow()间接设置。失常状况下,设置该属性能够解决问题。
Webview 的输入框被阻止
然而,如果 Webview 的输入框被阻止,则设置该属性可能有效。
在 Webview 的状况下,SOFT_INPUT_ADJUST_PAN 将不起作用。而后,如果是 Webview,并且你依然关上沉迷模式,则 SOFT_INPUT_ADJUST_RESIZE 和 SOFT_INPUT_ADJUST_PAN 都将不起作用。
我去查资料,发现这是经典的 5497 期。许多在线解决方案都是通过 androidbug 5497 解决办法。这个解决办法很好找,我就不贴了。原理是监控视图树的变动,而后计算高度,再动静设置。这个计划的确能够解决问题,然而我感觉这个操作有很多不可控因素。说白了,某些模型或者状况下会有其余 bug,会导致你写一些判断逻辑来应答非凡状况。
解决办法是不必沉迷模式,而后用 SOFT_INPUT_ADJUST_RESIZE 就能够解决。然而有时候这个窗口显示的时候须要沉迷模式,特地是一些适宜刘海温和水滴屏的场景。
setSystemUiVisibility(视图。SYSTEM UI FLAG _ LAYOUT _全屏)
复制代码
我的第一反馈是扭转布局。
窗户。setLayout(ViewGroup。LayoutParams.MATCH_PARENT,ViewGroup。layout params . WRAP _ CONTENT);
复制代码
这样能够失常向上推子弹框,然而控件外部也应用了 WRAP_CONTENT,导致 SOFT_INPUT_ADJUST_RESIZE 扭转了布局,而后就不能复原原样了,也就是会变形。而 SOFT_INPUT_ADJUST_RESIZE 如果 WRAP_CONTENT 不应用固定高度也是有效的。
没关系,还有方法。在 MATCH_PARENT 的状况下,咱们把 fitSystemWindows 设置为 true,然而这个属性会在顶部让出一个平安间隔,成果就是向下偏移状态栏的高度。
在这种状况下,能够设置边距来解决顶部偏移的问题。
params . top margin = status height = = 0?-120:-status height;
view . setlayoutparams(params);
复制代码
此操作能够解决顶部偏移的问题,但布局可能会被垂直压缩。这个我还没有齐全测试过。我感觉你的布局高度固定的话,可能不会受影响。然而我的 webview 是自适应的,webview 里的内容也是自适应的,所以我呈现了版面垂直压缩的状况。例如,视图的高度是 800,状态栏的高度是 100。设置 fitSystemWindows 后,成果是视图显示 700,paddingTop 100。对于这种成果,设置 params.topMargin =-100,而后视图显示 700 和 paddingTop 100。它能够在视觉上打消顶部偏移,但没有解决布局的垂直压缩问题。
所以最终的解决方案是扭转 WindowInsets 的 Rect(我稍后会解释这意味着什么)
具体操作是将以下两种办法增加到您的自定义视图中
@笼罩
public void setFitsSystemWindows(boolean fitSystemWindows){
fitSystemWindows = true
super . setfitssystemwindows(fitSystemWindows);
}
@笼罩
受爱护的布尔 fitSystemWindows(矩形插入){
Log.v(“mmp “,” 测试顶部偏移量:”+inserts . top);
insets . top = 0;
返回 super . fitsystemwindows(insets);
}
复制代码
总结
WebView+ 沉迷模式下解决输入框被软键盘遮挡问题的步骤:
Window.getattributes()。软输出模式设置为软输出调整大小。
将视图的 fitSystemWindows 设置为 true,我的 webview 中的输入框被屏蔽,因而设置 webview 而不是父视图。
重写 fitSystemWindows 办法,并将 insets 的顶部设置为 0。
窗口镶嵌
依照下面 3 个步骤,就能够解决 webview 输入框梗塞的问题了,然而如果你想晓得为什么,原理是什么。你须要理解 WindowInsets。咱们的沉迷式操作 setSystemUiVisibility 和设置 fitSystemWindows 属性,以及重写 fitSystemWindows 办法,都与 WindowInsets 无关。
WindowInsets 是利用于窗口的零碎视图的插入。例如状态栏 STATUS_BAR 和导航栏 NAVIGATION_BAR。会被视图援用,所以咱们要做的具体操作,就是操作视图。
还有一个重要的问题。不同版本的 WindowInsets 有肯定的差别。Android28、Android29 和 Android30 都有肯定的差别。比方 29 里有一个 android.graphics.Insets 类,28 里没有。咱们能够在 29 中获取而后查看 top、left 等四个属性,然而只能查看。是最终的,不能间接拿进去批改。
不过这段 WindowInsets 其实能够讲很多内容,当前能够拿进去独自做一篇。上面简略介绍一下。你只须要指定咱们如何解决上述问题的原理,就是这个货色。
源代码剖析
在理解了 WindowInsets 之后,我将带您简略浏览一下 setFitsSystemWindows 的源代码。置信你会印象更粗浅。
public void setFitsSystemWindows(boolean fitSystemWindows){
setFlags(fitSystemWindows?FITS_SYSTEM_WINDOWS : 0,FITS SYSTEM WINDOWS);
}
复制代码
它只是在这里设置了一个标记。如果你看看它的正文 (我不会贴在这里),它会带你到受爱护的布尔 fitsystemwindows (rectinserts) 的办法 (我稍后会说为什么我去这个办法)
@已弃用
受爱护的布尔 fitSystemWindows(矩形插入){
if((mprivateflags 3 & pflag 3 APPLYING INSETS)= = 0){
if (insets == null) {
// 依据定义,Null insets 曾经被应用。
// 此调用无奈利用插入,因为没有可利用的插入,
// 所以返回 false。
返回 false
}
// 如果咱们不在分派较新的 apply insets 调用的过程中,
// 这意味着咱们不在兼容门路中。差遣到新的
// 利用 insets 门路并从那里获取内容。
尝试 {
mprivateflags 3 | = pf lag 3 FITTING SYSTEM _ WINDOWS;
返回 dispatchapplywindowsets(new window insets(insets))。is consumed();
}最初 {
mprivateflags 3 & = ~ pflag 3 FITTING SYSTEM _ WINDOWS;
}
} 否则 {
// 咱们是从较新的利用插入门路调用的。
// 执行规范回退行为。
返回 fitSystemWindowsInt(insets);
}
}
复制代码
(mprivateflags 3 & pflag 3 applying inserts)= = 0 这个判断前面会简略形容。你只须要晓得失常状况是执行 fitSystemWindowsInt(insets)。
还有 fitSystemWindows 叫什么?向前跳转,能够看到调用了 onapplywindowsets,而 onapplywindowsets 是由 dispatchApplyWindowInsets 调用的。其实这里没必要往前看。可见这是一种分配机制。没错,这就是 WindowInsets 的散发机制,相似于 View 的事件散发机制。向前看被 viewgroup 称为。如前所述,这里不详细描述 windowinserts,所以这里也不开展 windowinserts 的散发机制。你只须要先晓得有这么一个货色。
public window insets dispatchapplywindowsets(window insets insets){
尝试 {
mprivateflags 3 | = pf lag 3 APPLYING INSETS;
if (mListenerInfo!= null & & mlistenerinfo . monapplywindowsetslistener!= null) {
返回 mlistenerinfo . monapplywindowsetslistener . onapplywindowsets(this,insets);
}否则 {
返回 onapplywindowsets(insets);
}
}最初 {
mprivateflags 3 & = ~ pflag 3 APPLYING INSETS;
}
}
复制代码
假如 mPrivateFlags3 为 0,pflag3 applying inserts 为 20,0 和 20 做 OR 运算,也就是 20。而后判断是否存在 mOnApplyWindowInsetsListener。这个听者是不是咱们在里面做过。
setonapplywindowinsets listener(new onapplywindowsinsetslistener(){
@笼罩
ApplyWindowInsets 上的公共 WindowInsets(视图 v,WindowInsets insets) {
……
返回 insets
}
});
复制代码
假如没有,调用 onApplyWindowInsets。
ApplyWindowInsets 上的公共 WindowInsets(WindowInsets insets){
if((mprivateflags 3 & pflag 3 FITTING SYSTEM _ WINDOWS)= = 0){
// 咱们不是从对 fitSystemWindows 的间接调用中被调用的,
// 调用它作为后备,以防咱们在重写它的类中
// 并且具备要执行的逻辑。
if(fitSystemWindows(insets . getsystemwindowinsetsarrect()){
返回 insets . consumesystemwindowinsets();
}
}否则 {
// 咱们是从对 fitSystemWindows 的间接调用中被调用的。
if(fitSystemWindowsInt(insets . getsystemwindowinsetsarrect()){
返回 insets . consumesystemwindowinsets();
}
}
返回 insets
}
复制代码
rivate flags 3 & pflag 3 fitting system _ windows 是 20 和 40 的 AND 运算,也就是 0,所以调用 fitSystemWindows。
而 fitSystemWindows(mprivateflags 3 & pflag 3 applying inserts)= = 0)是 20 和 20 的 And 运算,不是 0,所以调用 fitSystemWindowsInt。
在剖析的这一点上,咱们须要联合下面的思路来解决 bug。事实上,咱们须要获取 rectinserts 参数并批改它的 top。
setonapplywindowinsets listener(new onapplywindowsinsetslistener(){
@笼罩
ApplyWindowInsets 上的公共 WindowInsets(视图 v,WindowInsets insets) {
……
返回 insets
}
});
复制代码
setOnApplyWindowInsetsListener 回调中的 Insets 能够失去类 android.graphics.Insets,然而只能看到 top 是什么,没有方法批改。当然,你能够看看下面是什么,而后像我下面那样设置,边距。
params . top margin =-top;
复制代码
如果你的布局没有垂直变形,也没多大关系。如果变形了,就不能用这个办法了。从源代码来看,这个过程次要波及三种办法。咱们能够看到,最好的终点是 fitSystemWindows。因为 onApplyWindowInsets 和 dispatchApplyWindowInsets 是散发机制的办法,如果要从这里开始,可能会呈现过程凌乱等问题。
所以咱们这样做是为了解决 fitSystemWindows = true 的顶部偏移量。
@笼罩
public void setFitsSystemWindows(boolean fitSystemWindows){
fitSystemWindows = true
super . setfitssystemwindows(fitSystemWindows);
}
@笼罩
受爱护的布尔 fitSystemWindows(矩形插入){
Log.v(“mmp “,” 测试顶部偏移量:”+inserts . top);
insets . top = 0;
返回 super . fitsystemwindows(insets);
}
复制代码
倒退
以上曾经解决了问题,这里是拓展思路。
fitSystemWindows 办法受到爱护,这将导致您重写它。然而如果咱们不能通过继承来实现这个过程呢?
其实这是一个解决问题的思路。咱们须要晓得为什么会这样,原理是什么。比方这里咱们晓得这个 fitSystemWindows 造成的顶部偏移是 insets 的顶部造成的。这个你得先晓得,不然你就不晓得怎么解决这个问题了。只能去网上找他人的办法,一个一个试。那我怎么晓得是 insets 的顶部?这须要肯定的浏览源代码的能力,也要晓得这个货色的设计思路是什么。当你晓得有这样一个货色的时候,想方法去获取它,扭转数据。
这里,咱们应用继承受爱护办法的属性来获取 insets。如果这个过程无奈通过传承实现呢?比方这是因为 fitSystemWindows 是 view 的办法,而咱们的自定义视图只是继承了 view。如果是外部写的实现这个操作的类呢?
在这种状况下,通常有两种操作与万金油进行比拟:
你写一个类来继承它的类,而后在你写的类中批改 insets,而后通过反射注入 View。
动静代理
事实上,我最后的想法是应用动静代理,所以我立刻推出了代码。
公共类 WebViewProxy 实现 InvocationHandler {
公有对象 relObj
公共对象 new proxy instance(Object Object){
this.relObj = object
返回 proxy . newproxyinstance(rel obj . getclass()。getClassLoader(),relObj.getClass()。getInterfaces(),this);
}
@笼罩
公共对象调用 (对象代理、办法办法、对象[] 参数)抛出 Throwable {
尝试 {
if (“fitSystemWindows “。equals(method.getName()) && args!= null && args.length == 1){
Log.v(“mmp “,” 测试代理成果 ”+args);
}
}catch(异样 e){
e . printstacktrace();
}
返回代理;
}
}
复制代码
webview proxy proxy = new webview proxy();
View View proxy =(View)proxy . newproxyinstance(mWebView);
复制代码
而后我发现 fitSystemWindows 不是接口办法,白费力气。然而如果 fitSystemWindows 是一个接口办法,我能够通过动静代理增加反射来批改这里的 insets。尽管我不会用,但也是个想法。最初发现能够间接重写这个办法,反而把问题复杂化了。