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。虽然我不会用,但也是个想法。最后发现可以直接重写这个方法,反而把问题复杂化了。