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系统本身的一个BUG。

为什么文章开头说这是个坑呢?

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

“issue 5497详情传送门9758; 이슈 5497 - android -WebView adjustResize windowSoftInputMode가 activity가 전체 화면 모드일 때 깨짐 - Android 오픈 소스 프로젝트 - 이슈 트래커 - Google 프로젝트 호스팅

물론, 누가 구멍을 파든지는 어쩔 수 없지만, 결국은 개발자가 해결해야 합니다.

구멍을 만나면, 두 가지 방법으로 지나갈 수 있습니다: 덮어놓기 또는 덮어놓기.

덮어놓기 자세

전문에서 설명했듯이, 구멍이 나는 조건은: WebView를 사용하는 activity가 전체 화면 모드나 adjustPan 모드를 사용하는 경우입니다.

따라서 덮어놓기 자세는 매우 간단합니다——

activity에 WebView가 있으면 전체 화면 모드를 사용하지 마시고, 그 windowSoftInputMode 값을 adjustResize로 설정하면 됩니다.

어떻게 보세요? 매우 간단하지 않나요?

그러나 때로는 전체 화면 모드와 WebView를 동시에 사용해야 할 때가 있습니다. 이럴 때, 덮어놓기는 안됩니다. 새로운 덮어놓기 자세가 필요합니다. 幸いなことに,개발자의 지혜는 무한합니다. 이 구멍은 이렇게 오랫동안 있었지만 여전히 해결책을 찾은 사람들이 있습니다.

AndroidBug5497Workaround

저는 개인적으로 가장 좋은 해결책이 이것이라고 생각합니다:AndroidBug5497Workaround에 필요한 것은 마법 같은 AndroidBug5497Workaround 클래스.

이름에서 알 수 있듯이, 이는 주로 ”에 대처하기 위해 사용됩니다5497”문제의, 사용 방법도 매우 간단합니다:

AndroidBug5497Workaround 클래스를 프로젝트에 복사

필요한 activity의 onCreate 메서드에 AndroidBug 추가5497Workaround.assistActivity(this)만으로도 됩니다.

테스트 결과, 대부분의 Android 버전에서 사용할 수 있으며, 효과는 adjustResize 설정과 유사합니다.

대비 사진을 보세요:

우리 회사 앱의 특정 사용WebView의 전체 화면 모드 Activity 페이지에서 왼쪽에서 오른쪽으로 순서로: 소프트 키보드가 없는 스타일, 소프트 키보드가 입력 상자를 가려는 효과, 그리고 AndroidBug 사용5497Workaround 이후의 최종 효과.

그 원리는 무엇인가요?

이 화려한 AndroidBug5497Workaround 클래스는 사실상 복잡하지 않으며, 수십 줄의 코드만 있습니다. 먼저 여기에 적습니다:

public class AndroidBug5497Workaround {
// 자세한 정보는 다음 웹사이트를 참조하세요: https://code.google.com/p/android/issues/detail?id=5497
// 이 클래스를 사용하려면 간단히 assistActivity()를 이미 content view가 설정된 Activity에서 호출하세요.
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의可视 상태가 변경될 때, 알림 콜백을 수행합니다.

—— '소프트 키보드 펼쳐짐'은 이 이벤트를 발생시키는 원인 중 하나입니다. (소프트 키보드 펼쳐짐은 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.마지막 단계, 높이 재설정

우리가 계산한 사용 가능 높이는 현재 시각적으로 볼 수 있는 인터페이스 높이입니다. 하지만 현재 인터페이스의 실제 높이는 사용 가능 높이보다 더 큰 소프트 키보드의 거리로 구성되어 있습니다.

따라서, 마지막 단계는 인터페이스 높이를 사용 가능 높이로 설정하는 것입니다 - 대성공입니다.

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의 화면 높이가 다시 설정되는 경우, 코드가 키보드가弹出시에만 응답하는 것을 보장할 수 있습니다.

결론

결론적으로, 이렇게 됩니다:

WebView가 없는 일반 Activity(adjustpan 또는 adjustResize를 사용합니다)

WebView가 포함되어 있으면:

a) 전면 모드가 아니면 adjustResize를 사용할 수 있습니다.

b) 전면 모드이면 AndroidBug를 사용합니다.5497Workaround를 처리합니다.

위에 설명한 것은 편집자가 여러분들에게 소개한 Android 소프트 키보드가 입력 필드를 가려버리는 최종 해결책입니다. 여러분들에게 도움이 되길 바랍니다. 어떤 질문이나 의문이 있다면, 댓글을 달아 주시고, 편집자는 즉시 답변을 드리겠습니다. 또한,呐喊 교본 웹사이트에 대한 여러분들의 지지에 깊이 감사드립니다!

고지사항: 이 문서의 내용은 인터넷에서 가져왔으며, 원 저작자의 소유입니다. 이 컨텐츠는 인터넷 사용자가 자발적으로 기여하고 자체적으로 업로드한 것이며, 이 웹사이트는 소유권을 가지지 않으며, 인공적인 편집 처리를 하지 않았으며, 이에 대한 법적 책임도 부담하지 않습니다. 저작권 문제가 있는 컨텐츠를 발견하면, 이메일을 notice#w로 보내 주시기 바랍니다.3codebox.com(이메일을 보내면, #을 @으로 변경해 주시고, 관련 증거를 제공해 주세요. 일단 조사가 완료되면, 이 사이트는 즉시 의심스러운 저작권 내용을 삭제합니다.)