English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Solución definitiva al problema de que el teclado suave de Android bloquea el cuadro de entrada

Introducción

Después de mucho desarrollo, inevitablemente se encontrarán con todo tipo de trampas.

En el camino del desarrollo de Android, la trampa de 'el teclado virtual obstruye el cuadro de entrada' es una trampa enorme que lleva mucho tiempo - vengan, veamos lentamente.

Capítulo de introducción

La situación más básica, como se muestra en la figura: en la parte inferior de la página hay un EditText, si no se realiza ninguna acción de procesamiento, entonces cuando se despliega el teclado virtual, podría obstruir el EditText.

El tratamiento de esta situación es realmente simple, solo necesita configurar en el archivo AndroidManifest el valor de activity: android:windowSoftInputMode en adjustPan o adjustResize, como así:

<activity>
android:name=".MainActivity"
android:windowSoftInputMode="adjustPan" >
...
</activity>

一般来说,它们都可以解决问题,当然,adjustPan和adjustResize的效果略有不同。

adjustPan是把整个界面向上平移,使输入框露出,不会改变界面的布局;

adjustResize则是重新计算弹出软键盘之后的界面大小,相当于用更少的界面区域来显示内容,输入框自然也就在其中。

↑↑↑ 好的,这只是入门,基本上地球上所有的Android工程师都能搞定。

别急,看下面~

加上WebView试试看?陷阱来了……

上面的入门篇中,软键盘是由原生的EditText触发的弹出。而在H5、Hybrid几乎已经成为App标配的时候,我们经常还会遇到的情况是:软键盘是由WebView中的网页元素所触发的弹出。

情况描述

这时候,情况就会变得复杂了:

首先,页面不是全屏模式的情况下,给activity设置adjustPan会失效。

其次,页面是全屏模式的情况下,adjustPan和adjustResize都会失效。

——解释一下,这里的全屏模式即是页面是全屏的,包括Application或activity使用了Fullscreen主题、使用了『状态色着色』、『沉浸式状态栏』、『Immersive Mode』等等——总之,基本上只要是App自己接管了状态栏的控制,就会产生这种问题。

下面这个表格可以简单列举具体的情况。

为什么说它是一个陷阱?”issue 5497”

上面表格中的这种情况并非是Google所期望的,理想的情况当然是它们都能正常生效——所以这实际上是Android系统本身的一个BUG。

为什么文章开头说这是一个陷阱呢?

——因为这个BUG从Android1.x时代(2009年)就已经被报告了,而一直到了如今的Android7.0(2016年)还没有修复……/(ㄒoㄒ)/
这可以说不仅是一个陷阱,而且还是一个官方挖的陷阱~

“issue 5497”,详情传送门9758; Problema 5497 - android -El modo de ajuste de tamaño de WebView windowSoftInputMode se rompe cuando el activity está en modo de pantalla completa - Proyecto de código abierto de Android - Rastreador de problemas - Google Project Hosting

Por supuesto, independientemente de quién haya cavado el agujero, finalmente es el desarrollador quien debe resolverlo.

Después de encontrar el agujero, hay dos métodos para pasarlo: evitar o llenar.

Postura para evitar el agujero

Como se mostró anteriormente, las condiciones para el agujero son: el activity con WebView utiliza el modo de pantalla completa o el modo adjustPan.

Entonces, la postura para evitar el agujero es muy simple:

Si el activity tiene WebView, no utilice el modo de pantalla completa y configure su valor de windowSoftInputMode en adjustResize.

¿Qué piensas, ¿no es muy simple?

Pero a veces, es necesario que el modo de pantalla completa y WebView coexistan, en este caso, es inaceptable evitar el agujero, necesitamos una nueva postura para llenar el agujero. Afortunadamente, la inteligencia de los desarrolladores es ilimitada, este agujero ha existido durante muchos años, y aún así, algunas personas han encontrado algunas soluciones.

AndroidBug5497Solución_alternativa

Personalmente, creo que la mejor solución es esta:AndroidBug5497Solución_alternativasolo necesita un AndroidBug mágico5497Solución_alternativa

Solución_alternativa.5497El nombre lo dice todo, es específicamente para luchar contra

Coloca AndroidBug5497Copia la clase Solución_alternativa al proyecto.

Agregue una línea de AndroidBug en el método onCreate del activity que necesita llenar el agujero.5497Solución_alternativa.assistActivity(this) es suficiente.

Después de las pruebas, básicamente es compatible con todas las versiones de Android, y el efecto es básicamente equivalente a ajustar el tamaño de la pantalla.

Veamos una imagen de comparación:

Desde una página de Activity en modo de pantalla completa que utiliza WebView de nuestra aplicación, de izquierda a derecha respectivamente: estilo sin teclado virtual, efecto de teclado virtual que obstruye el cuadro de entrada, y el uso de AndroidBug5497El efecto final después de la Solución alternativa.

¿Cuál es su principio?

Este AndroidBug impresionante5497La clase Solución alternativa, en realidad no es muy compleja, solo tiene unas pocas decenas de líneas de código, primero aquí:

public class AndroidBug5497Solución alternativa {
// Para obtener más información, consulte https://code.google.com/p/android/issues/detail?id=5497
// Para usar esta clase, simplemente invoca assistActivity() en una Activity que ya tiene su vista de contenido configurada.
public static void assistActivity (Activity activity) {
new AndroidBug5497Workaround(activity);
}
private View mChildOfContent;
private int usableHeightPrevious;
private FrameLayout.LayoutParams frameLayoutParams;
private AndroidBug5497Workaround(Activity activity) {
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
possiblyResizeChildOfContent();
}
});
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
}
private void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != usableHeightPrevious) {
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard/4)) {
// probablemente el teclado se ha vuelto visible
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
else {
// probablemente el teclado se ha vuelto invisible
frameLayoutParams.height = usableHeightSansKeyboard;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return (r.bottom - r.top);// En modo de pantalla completa: return r.bottom
}
}

El código hace más o menos lo siguiente:

1.Encontrar el View raíz de activity

Vamos a ver el código de entrada:

FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);

Entre ellos, el View al que se refiere android.R.id.content en la primera línea, es el View raíz que los desarrolladores pueden controlar en todas las interfaces de Activity de Android.

如果Activity是全屏模式,那么android.R.id.content就占据了整个屏幕区域。

如果Activity是普通的非全屏模式,那么android.R.id.content就占据了除状态栏之外的所有区域。

其他情况,如Activity是弹窗、或者7.0以后的分屏样式等,android.R.id.content也是弹窗的范围或分屏所在的半个屏幕——这些情况较少,就暂且不考虑了。

我们经常使用的setContentView(View view)/setContent(int layRes)实际上就是将我们指定的View或layRes放入android.R.id.content中,成为它的子View。

因此,然后,第二行content.getChildAt(0)获取到的mChildOfContent,实际上就是用来获取我们通过setContentView放入的View。

2.设置一个监听器来监听View树的变化

mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener({ //简化了写法
possiblyResizeChildOfContent();
});

View.getViewTreeObserver()可以获取一个ViewTreeObserver对象——这个对象是一个观察者,专门用于监听当前View树发生的一些变化。这里注册的addOnGlobalLayoutListener,会在当前View树的全局布局(GlobalLayout)发生变化或其中的View可视状态发生变化时,进行通知回调。

——“软键盘弹出”,这是一个触发事件的原因。 (软键盘弹出会导致GlobalLayout发生变化)

也就是说,现在可以监听到“软键盘弹出”的事件了。

3.界面变化后,获取“可用高度”

一旦弹出软键盘,接下来的任务是获取界面变化后的可用高度(开发者可以用它来显示内容的高度)。

直接看代码:

private int computeUsableHeight() {
Rect rect = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(rect);
// rect.top realmente es la altura del área de estado. Si es un tema de pantalla completa, return rect.bottom directamente puede
return (rect.bottom - rect.top);
}

View.getWindowVisibleDisplayFrame(Rect rect), esta línea de código puede obtener el Rect - es el área rectangular restante de la interfaz después de quitar la barra de título y la parte cubierta por el teclado virtual, como se muestra en la figura, el área dentro del cuadro rojo.

diagrama de la región Rect

también se puede ver:

el valor de rect.top es realmente la altura de la barra de título. (en realidad, también se utiliza a menudo como método para obtener la altura de la barra de título)

altura de la pantalla-rect.bottom, es la altura del teclado virtual. (también ha aparecido el método para obtener la altura del teclado virtual)
en este momento, hay:

en modo de pantalla completa, altura disponible = rect.bottom

en modo no completo de pantalla, altura disponible = rect.bottom - rect.top

4.El último paso, resetear la altura

la altura disponible que hemos calculado es la altura de la interfaz visible visualmente en este momento. Pero la altura real de la interfaz actual es mayor que la altura disponible en una distancia del teclado virtual.

por lo tanto, el último paso es establecer la altura de la interfaz en la altura disponible - ¡el trabajo está terminado!

private void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != usableHeightPrevious) {
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard/4)) {
// probablemente el teclado se ha vuelto visible
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
else {
// probablemente el teclado se ha vuelto invisible
frameLayoutParams.height = usableHeightSansKeyboard;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}

/4la verificación de )1/4la altura de la pantalla, se procederá a ajustar la altura nuevamente, básicamente garantizando que el código solo responda al despliegue del teclado virtual.

Resumen

Resumiendo, es así:

Activity normal (sin WebView), utilice directamente adjustpan o adjustResize

Si se incluye WebView:

a) Si no es modo de pantalla completa, se puede usar adjustResize

b) Si es modo de pantalla completa, utilice AndroidBug5497Solución de contorno.

La solución definitiva que el editor le presenta a todos sobre cómo evitar que el teclado virtual de Android bloquee la caja de entrada, espero que sea de ayuda para todos. Si tiene alguna pregunta, déjeme un mensaje y el editor responderá a tiempo. También agradezco mucho el apoyo de todos a la página web de tutorial de alarido!

Declaración: El contenido de este artículo se obtiene de la red, es propiedad del autor original, el contenido se contribuye y se carga por los usuarios de Internet, este sitio no posee los derechos de propiedad, no se ha procesado editorialmente y no asume ninguna responsabilidad legal. Si encuentra contenido sospechoso de infracción de derechos de autor, por favor envíe un correo electrónico a: notice#oldtoolbag.com (al enviar un correo electrónico, reemplace # con @ para denunciar y proporcione evidencia. Una vez verificada, este sitio eliminará inmediatamente el contenido sospechoso de infracción de derechos de autor.)

Te gustará