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

Solution définitive au problème de clavier virtuel Android bloquant le champ d'entrée

Préambule

Après avoir développé pendant un certain temps, on ne peut éviter de rencontrer toutes sortes de problèmes.

Sur la route du développement Android, le trou de "le clavier virtuel masque le champ d'entrée" est un trou énorme qui a pris beaucoup de temps. Venez, voyons lentement.

Introduction

Dans le cas le plus simple, comme illustré dans l'image : il y a un EditText en bas de la page. Si aucune mesure n'est prise, le clavier virtuel pourrait masquer l'EditText.

Le traitement de cette situation est en fait très simple, il suffit de configurer le fichier AndroidManifest pour l'activité : la valeur de android:windowSoftInputMode doit être ajustPan ou ajustResize, comme suit :

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

一般来说,他们都可以解决问题,当然,adjustPan跟adjustResize的效果略有区别。

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

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

↑↑↑ OK,这只是入门,基本上地球上所有的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; Problème 5497 - android -WebView adjustResize windowSoftInputMode casse lorsque l'activity est en mode plein écran - Projet Open Source Android - Suivi des problèmes - Google Project Hosting

Bien sûr, peu importe qui a creusé ce trou, en fin de compte, c'est le développeur qui doit le résoudre.

Après avoir rencontré ce trou, il y a deux méthodes pour y passer : s'échapper ou le combler.

Posture de contournement

Comme mentionné précédemment, les conditions pour apparaitre ce trou sont : l'activity avec WebView utilise le mode plein écran ou le mode adjustPan.

La posture de contournement est donc très simple -

Si l'activity contient un WebView, n'utilisez pas le mode plein écran et mettez la valeur de windowSoftInputMode à adjustResize.

Ça ne semble pas compliqué, non ?

Mais il y a des moments où il est nécessaire de concilier le mode plein écran avec WebView, alors il n'est plus possible de s'échapper, nous avons besoin d'une nouvelle posture de contournement. Heureusement, l'intelligence des développeurs est infinie, ce trou a existé depuis tant d'années, et quelqu'un a trouvé quelques solutions.

AndroidBug5497Solution de contournement

Je pense que la meilleure solution est cette-ci :AndroidBug5497Solution de contournementIl ne nécessite qu'un AndroidBug magique5497Solution de contournement.

Comme son nom l'indique, il est spécialement utilisé pour lutter contre5497Les étapes à suivre sont super simples :

Prenez AndroidBug5497Copiez la classe Solution de contournement dans le projet.

Ajoutez une ligne de code AndroidBug dans la méthode onCreate de l'activity nécessitant un contournement.5497Solution de contournement.assistActivity(this) suffit.

Après tests, il est pratiquement utilisable sur toutes les versions d'Android, et l'effet est comparable à celui de l'ajustement de la taille de l'écran.

Voyons une image comparative :

provenant d'une page Activity en mode plein écran utilisant WebView de notre application, de gauche à droite : sans clavier virtuel, avec le clavier virtuel masquant le champ d'entrée, et en utilisant AndroidBug5497L'effet final après la Solution de contournement.

De quoi s'agit-il ?

Ce AndroidBug cool5497La classe Solution de contournement n'est pas très complexe, elle ne contient que quelques dizaines de lignes de code, je l'attache ici d'abord :

public class AndroidBug5497Solution de contournement {
// Pour plus d'informations, voir https://code.google.com/p/android/issues/detail?id=5497
// Pour utiliser cette classe, il suffit d'appeler assistActivity() sur une Activity qui a déjà sa vue de contenu définie.
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)) {
// Le clavier a probablement juste été rendu visible
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
} else {
// Le clavier a probablement juste été caché
frameLayoutParams.height = usableHeightSansKeyboard;

mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;


private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return (r.bottom - r.top);// En mode plein écran : return r.bottom

Le code a roughly fait ces quelques choses :

1.Trouver le View racine de l'activity

Regardons le code d'entrée :

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

Dans celui-ci, le View auquel fait référence android.R.id.content dans la première ligne est le View racine de la zone de contrôle que les développeurs peuvent contrôler sur toutes les interfaces Activity d'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.设置一个Listener监听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 est en réalité la hauteur de la barre d'état, si c'est un thème en plein écran, il suffit de return rect.bottom
return (rect.bottom - rect.top);

View.getWindowVisibleDisplayFrame(Rect rect), cette ligne de code peut obtenir le Rect - c'est la zone rectangulaire restante de l'interface après avoir supprimé la barre de titre et la partie cachée par le clavier virtuel, comme montré dans la figure, la zone dans le cadre rouge.

Schéma de l'illustration de la zone Rect

On peut également voir :

La valeur de rect.top, c'est la hauteur de la barre de titre. (En réalité, cela est souvent utilisé comme méthode pour obtenir la hauteur de la barre de titre)

Hauteur de l'écran-rect.bottom, c'est la hauteur du clavier virtuel. (La méthode pour obtenir la hauteur du clavier virtuel est également apparue)
Dans ce cas, il y a :

En mode plein écran, la hauteur utilisable = rect.bottom

En mode non plein écran, la hauteur utilisable = rect.bottom - rect.top

4.La dernière étape, réinitialiser la hauteur

La hauteur utilisable que nous calculons est la hauteur de l'interface visible visuellement. Mais la hauteur réelle de l'interface actuelle est supérieure à la hauteur utilisable d'une distance de la clé virtuelle.

Donc, la dernière étape, c'est de fixer la hauteur de l'interface à la hauteur utilisable - le travail est terminé.

private void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != usableHeightPrevious) {
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard/4)) {
// Le clavier a probablement juste été rendu visible
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
} else {
// Le clavier a probablement juste été caché
frameLayoutParams.height = usableHeightSansKeyboard;

mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;

/4du jugement )1/4de la hauteur de l'écran, le réglage de la hauteur sera effectué à nouveau, ce qui garantit généralement que le code ne réagit qu'à l'apparition du clavier virtuel.

Résumé

En résumé, c'est ainsi :

Activity normal (sans WebView), utilisez directement adjustpan ou adjustResize

Si WebView est inclus :

a) Si ce n'est pas en mode plein écran, vous pouvez utiliser adjustResize

b) Si c'est en mode plein écran, utilisez AndroidBug5497Solution de contournement

Les solutions définitives que l'éditeur vous présente aujourd'hui pour contourner le clavier virtuel Android qui masque le champ d'entrée, j'espère qu'elles vous seront utiles. Si vous avez des questions, laissez un message, je répondrai à temps. Je tiens également à remercier chaleureusement tous ceux qui soutiennent le site de tutoriels 'Cri'.

Déclaration : le contenu de cet article est来源于网络,à l'origine de l'auteur, le contenu est apporté par les utilisateurs d'Internet, ce site Web ne possède pas de propriété, n'a pas été traité par l'éditeur humain, ni n'assume la responsabilité des responsabilités juridiques pertinentes. Si vous trouvez du contenu suspect de violation de droits d'auteur, veuillez envoyer un e-mail à : notice#oldtoolbag.com (au moment de l'envoi d'un e-mail, veuillez remplacer # par @ pour signaler une violation, et fournir des preuves pertinentes. Une fois vérifié, ce site supprimera immédiatement le contenu suspect de violation de droits d'auteur.).

Vous pourriez aussi aimer