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

Androidソフトキーボードが入力フィールドを隠す問題の究極の解決策

前書き

開発を続けるうちに、必ずどんな罠にも遭遇するでしょう。

そして、Android開発において「ソフトキーボードが入力フィールドを覆う」という罠は、長期間続く大きな罠です。来て、じっくり見てみましょう。

入門編

最も基本的な状況、図のように:ページの下部にEditTextがあります。何も処理しない場合、ソフトキーボードが弹出するとEditTextが隠れることがあります。

この状況の処理は非常に簡単で、AndroidManifestファイル内でactivityの設定にandroid:windowSoftInputModeの値をadjustPanまたはadjustResizeに設定するだけで良いです。こんな感じです:

<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システム自体のバグです。

なぜ記事の冒頭でこれは穴だと言ったのでしょうか?

——このバグはAndroid1.x時代(2009年)に報告され、今のAndroid7.0(2016年)の現在でも未修正です……/(^v^)/
これはただの穴ではなく、公式が掘った穴です~

issue 5497詳細は以下のリンクをクリックしてください。9758; Issue 5497 - android -WebView adjustResize windowSoftInputModeがアクティビティが全画面モードになったときに壊れます - Android Open Source Project - Issue Tracker - Google Project Hosting

もちろん、坑が誰が掘ったかに関わらず、最終的には開発者が解決する必要があります。

坑に直面したとき、二つの方法があります:避けたり、埋めたり。

避け方

前文のように、坑が発生する条件は:WebViewを持つactivityが全画面モードまたはadjustPanモードを使用している場合です。

避け方のポーズは非常にシンプルです——

activityにWebViewがある場合、全画面モードを使用しないでください。そして、windowSoftInputModeの値をadjustResizeに設定します。

どうですか、簡単ですか?

しかし、全画面モードとWebViewを同時に必要な時は、避けられない場合もあります。その場合、避け方は使えません。新しい避け方を探さなければなりません。幸運にも、開発者の知恵は無限です。この坑がこんなに長い間存在する中で、いくつかの解決策が見つかっています。

AndroidBug5497Workaround

私が思う最良の解決策はこれです:AndroidBug5497Workaround、そして魔法のAndroidBugが必要です。5497Workaroundクラス。

名前からもわかるように、これは「問題」に対して特別に使われるものです:5497「問題」の解決策を使う方法も非常に簡単です:

AndroidBug5497Workaroundクラスをプロジェクトにコピーしてください。

活動が必要なactivityのonCreateメソッドにAndroidBugを追加してください。5497Workaround.assistActivity(this)だけでできます。

テストの結果、基本的にすべてのAndroidバージョンで使用可能で、adjustResizeに設定した場合と効果がほぼ同じです。

対比図をご覧ください:

私たちのAppの某个使用WebViewの全画面モードActivityページ、左から右に順に:ソフトキーボードがないスタイル、ソフトキーボードが入力フィールドを覆う効果、そしてAndroidBugを使用しています。5497Workaroundの最終効果です。

その原理は何でしょうか?

この派手なAndroidBug5497Workaroundクラスは特に複雑ではなく、数十行のコードしかありません。まずここに貼り付けます:

public class AndroidBug5497Workaround {
// さらに情報が必要な場合は、https:をご覧ください。//code.google.com/p/android/issues/detail?id=5497
// このクラスを使用するには、content viewが設定されているActivityでassistActivity()を呼び出すだけです。
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)) {
// キーボードが表示されたかもしれません
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
}
// キーボードが隠れてしまったかもしれません
frameLayoutParams.height = usableHeightSansKeyboard;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return (r.bottom - r.top);// フルスクリーンモードでは:return r.bottom
}
}

コードは基本的に以下のようなことを行っています:

1.activityの根Viewを探します

エントリーポイントのコードを見てみましょう:

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

その中で、第一行のandroid.R.id.contentで指定されたViewは、AndroidのすべてのActivity画面で開発者が制御できる根Viewです。

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の可視状態が変更された場合に通知コールバックを実行します。

-「ソフトウェアキーボードが弹出された」は、このイベントをトリガーするソースの1つです。(ソフトウェアキーボードの弹出によりGlobalLayoutが変更されます)

つまり、今や「ソフトウェアキーボードが弹出された」イベントを監視することができます。

3.インターフェースが変更された後、「利用可能な高さ」を取得します。

ソフトウェアキーボードが弹出された後、次にやることは、変更後のインターフェースの利用可能な高さ(開発者がコンテンツを表示する高さとして使用できます)を取得することです。

直接看代码:

private int computeUsableHeight() {
Rect rect = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(rect);
// rect.topは、ステータスバーの高さです。フルスクリーントピックには、直接 return rect.bottom だけで十分です}}
return (rect.bottom - rect.top);
}

View.getWindowVisibleDisplayFrame(Rect rect)という行のコードは、取得できるRectが-図に示されるように、タイトルバーを除いた、ソフトウェアキーボードで隠された部分を除いた、残りの矩形領域です。

Rect領域の図

以下も分かります:

rect.topの値は、タイトルバーの高さです。(実際には、これもタイトルバーの高さを取得する方法としてよく使用されます)

スクリーン高さ-rect.bottomは、キーボードの高さです。(キーボードの高さを取得する方法も登場しました)
この時、以下があります:

フルスクリーンモードでは、利用可能な高さ = rect.bottom

フルスクリーンモードではありません、利用可能な高さ = rect.bottom - rect.top

4.最後に、高さをリセット

計算した利用可能な高さは、現在視覚的に見えるUIの高さです。しかし、現在のUIの実際の高さは、利用可能な高さよりもキーボードの距離が一つあります。

最後に、UIの高さを利用可能な高さに設定する-完璧に成功しました。

private void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != usableHeightPrevious) {
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard/4)) {
// キーボードが表示されたかもしれません
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
}
// キーボードが隠れてしまったかもしれません
frameLayoutParams.height = usableHeightSansKeyboard;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}

上記のコードには、"heightDifference > (usableHeightSansKeyboard"が追加されました。/4の判断を行います。これは、不要な干渉を除去するためです。OnGlobalLayoutイベントがトリガーされる原因は非常に多く、単にソフトキーボードの弹出の変化ではなく、さまざまな子Viewの隠し表示の変化なども含まれます。これらは、画面高さへの影響が限られています。この判断を追加することで、画面の高さが変化するときにのみ、画面の高さが変更されるため、干渉を最小限に抑えます。1/4のスクリーン高さが変更されると、高さの再設定が行われます。これにより、コードがソフトキーボードの弹出にのみ反応するようになります。

要約

要約すると、以下の通りです:

通常のActivity(WebViewなし)、adjustpanまたはadjustResizeを直接使用します。

WebViewが含まれている場合:

a) フルスクリーンモードでない場合、adjustResizeを使用できます。

b) フルスクリーンモードの場合、AndroidBugを使用します。5497Workaroundを用いて処理します。

以上は編集者が皆さんに紹介したAndroidソフトキーボードが入力フィールドを遮る究極の解決策です。皆さんに役立つことを願っています。何か疑問があれば、コメントを残してください。編集者はすぐに回答します。また、このサイトへのサポートに感謝しています。

声明:本文の内容はインターネットから取得しており、著作権者はすべての権利を有しています。インターネットユーザーが自発的に貢献し、自己でアップロードしたものであり、本サイトは所有権を持ちません。また、人工編集は行われておらず、関連する法的責任も負いません。著作権に疑問がある場合は、メールを送信してください:notice#oldtoolbag.com(メール送信時は、#を@に変更してください。報告を行い、関連する証拠を提供してください。一旦確認されると、本サイトは侵害された内容をすぐに削除します。)

基本チュートリアル
おすすめ