IT_Programming/Android_Java

[펌] EditText Keyboard show/hide 이벤트 잡기 (Catch soft keyboard show/hidden events in Android)

JJun ™ 2016. 11. 26. 21:50



 출처

 : http://itpangpang.xyz/305

 : https://felhr85.net/2014/05/04/catch-soft-keyboard-showhidden-events-in-android/



EditText Keyboard show/hide

이벤트 잡기



이번에는 아주 유용한 코드를 하나 소개해보려고 합니다.


제가 EditText의 키보드가 올라오는 시점을 잡아야 되는 상황이 있었는데, 검색하다가 코드가 너무 좋아서 공유하려고 합니다.



안드로이드를 개발하면서 쉬운 것 같은데.. 은근히 어렵다고 생각한 부분이 EditText 부분입니다.

특히나 키보드 부분이죠.. SoftKeyboard가 올라왔을 때 화면 재구성부터, focus 관련등등


어쨋든, 이번에 개발하면서 필요했던 부분이 EditText의 SoftKeyboard가 show/hide 되는 순간을 잡아야되는 상황이었는데


처음에는 어렵다고 생각도 안했었습니다.

왜냐하면, 그냥 기본적으로 EditText가 포커스를 받으면, 키보드가 올라오니까...


et.setOnFocusChangeListener(new View.OnFocusChangeListener()
{
@Override
public void onFocusChange(View view, boolean hasFocus)
{
if(hasFocus)
{
new Handler().postDelayed(new Runnable()
{
@Override
public void run()
{
Toast.makeText(getApplication(),"키보드 올라옴",Toast.LENGTH_SHORT).show();
}
}, 500);
}
}
});


처음에는 잘 되는줄 알았습니다.

그리고 위 코드로 어떤 상황에서는 제대로 문제없이 사용할 수 있습니다

(requestFocus()로 요리조리 하면서 제어하면)


그런데 제 상황에서는 focus로만은 문제해결이 어려운 상황이었습니다.

(좀 복잡하긴 하지만.. 포커스 받은상태에서 back키를 눌러서 키보드를 내리는 상황이거나, EditText를 여러 개 사용할 때, 

 EditText에서 EditText로 포커스 이동 시 키보드가 항상 올라와있는 문제 등등 복합적으로...)


그래서 문제해결을 하기 위해서


여기 사이트를 발견하고야 말았습니다.


일단 소개하기 전에... 가장 중요한 조건상황이 하나 있는데

android:windowSoftInputMode="adjustResize"

InputMode가 adjustResize이여야 합니다.


개발이 원래 그렇듯.. 제 상황에서는 아주 문제없이 사용했습니다.

상황별로 안 먹히는 경우가 있을수도 있습니다. (안 먹히면 onMeasure() 메소드를 이용해서 해결하는 방법이 있습니다! 화이팅)


사이트에 들어가면 아주 친절하게 영어(..?)로 설명이 쫙 되있고, 또 한번더 친절하게

It is available here. 링크까지 걸어주셨습니다.


연결하면 gist.github에 아주 친절하게 풀 코드가 나옵니다.


그럼 사용해보자!


링크에 있는 SoftKeyboard.java 코드 설명은 생략하고 진행하겠습니다.

(다른분이 정성스럽게 짠 코드를.. 여기에서 분석한다는게.. 사용방법만 보겠습니다)



이렇게 프로젝트를 하나 열어서 SoftKeyboard 클래스를 하나 만듭니다.

그리고 위에 링크를 참고해서 코드를 채워넣습니다.


그리고 링크에서 알려준데로 레이아웃 전체를 감싸고 있는 Linearlayout(또는 Relative 등등이 되겠죠) 에 id를 부여해서, 

SoftKeyboard에게 넘겨줘야합니다.


[activity_main.xml]

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/ll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.tistory.itpangpang.edittextkeyboard.MainActivity">

<EditText
android:id="@+id/et_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="아이디를 입력하세요"
/>
<EditText
android:id="@+id/et_pw"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="패스워드를 입력하세요"
/>
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="로그인"/>
</LinearLayout>


전체를 감싸고 있는 LinearLayout에 ll이라는 id를 부여하였습니다.


그 다음 MainActivity에 이렇게 써봅니다.

public class MainActivity extends AppCompatActivity
{
SoftKeyboard softKeyboard;
LinearLayout ll;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

ll = (LinearLayout)findViewById(R.id.ll);

InputMethodManager controlManager = (InputMethodManager)getSystemService(Service.INPUT_METHOD_SERVICE);
softKeyboard = new SoftKeyboard(ll, controlManager);

}
}



이제 가장 중요한 softKeyboard로 인한 화면변화에 대한 Callback 처리 부분을 보겠습니다.


softKeyboard.setSoftKeyboardCallback(new SoftKeyboard.SoftKeyboardChanged()
{
@Override
public void onSoftKeyboardHide()
{
new Handler(Looper.getMainLooper()).post(new Runnable()
{
@Override
public void run()
{
//키보드 내려왔을때
}
});
}

@Override
public void onSoftKeyboardShow()
{
new Handler(Looper.getMainLooper()).post(new Runnable()
{
@Override
public void run()
{
//키보드 올라왔을때
}
});
}
});


아마 링크에는 new Handler() 이 부분이 없을텐데,

키보드 show/hide 순간에 화면 변화 이벤트를 주기 위해서는 위와 같이 추가해주시면 됩니다.


그리고 해당 화면에서 벗어났을 때 Keyboard Callback을 제거하기 위해 onDestroy()에 unRegister 처리를 해줍니다.


@Override
public void onDestroy()
{
super.onDestroy();
softKeyboard.unRegisterSoftKeyboardCallback();
}


마지막으로 manifest에 windowSoftInputMode도 한번 더 adjustResize인지 확인해봅니다



여기까지 끝났으면 간단하게 키보드 show/hide가 되는 순간 Toast를 달아서 확인해보도록 하겠습니다.


완성 코드는 이렇게 되겠네요

import android.app.Service;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v7.app.AppCompatActivity;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity
{
SoftKeyboard softKeyboard;
LinearLayout ll;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

ll = (LinearLayout)findViewById(R.id.ll);

InputMethodManager controlManager = (InputMethodManager)getSystemService(Service.INPUT_METHOD_SERVICE);
softKeyboard = new SoftKeyboard(ll, controlManager);
softKeyboard.setSoftKeyboardCallback(new SoftKeyboard.SoftKeyboardChanged()
{
@Override
public void onSoftKeyboardHide()
{
new Handler(Looper.getMainLooper()).post(new Runnable()
{
@Override
public void run()
{
Toast.makeText(getApplication(),"키보드 내려감",Toast.LENGTH_SHORT).show();
}
});
}

@Override
public void onSoftKeyboardShow()
{
new Handler(Looper.getMainLooper()).post(new Runnable()
{
@Override
public void run()
{
Toast.makeText(getApplication(),"키보드 올라감",Toast.LENGTH_SHORT).show();
}
});
}
});
}
@Override
public void onDestroy()
{
super.onDestroy();
softKeyboard.unRegisterSoftKeyboardCallback();
}
}


결과 확인



잘 나왔죠? ㅎㅎ


저 같은 경우에는 EditText가 8개인 화면에서 각 EditText마다 키보드 올라왔을 때의 화면구조를 전부다 다르게 하기 위해서

(키보드 올라오고 내려오고 할때 animation 등등...)


위 링크의 도움을 받고 + 터치이벤트 + getCurrentFocus + requestFocus() 등등 조합해서 해결하였습니다.


참고사이트 : https://felhr85.net/2014/05/04/catch-soft-keyboard-showhidden-events-in-android/









Catch soft keyboard show/hidden events in Android


Android offers you an overwhelming api loaded with tons of overridable functions representing typical events you may face as a mobile developer. That is the reason because you are not usually worry about catching and handling a new event that you have never meet before, you guess, normally right, those smart kids from Mountain View implemented an event handler sometime ago. Well, this is not the case of soft keyboard. It can be easily show and hide with convenient methods but there is not an implemented way of catching when user choose to hide it. Due to this limitations I coded a simple snippet that I hope it will be useful. It is available here. It is pretty easy to use.

felHR85_SoftKeyboard.zip


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
Somewhere else in your code
*/
RelativeLayout mainLayout = findViewById(R.layout.main_layout); // You must use your root layout
InputMethodManager im = (InputMethodManager) getSystemService(Service.INPUT_METHOD_SERVICE);
     
/*
Instantiate and pass a callback
*/
SoftKeyboard softKeyboard;
softKeyboard = new SoftKeyboard(mainLayout, im);
softKeyboard.setSoftKeyboardCallback(new SoftKeyboard.SoftKeyboardChanged()
{
  
    @Override
    public void onSoftKeyboardHide()
    {
        // Code here
    }
  
    @Override
    public void onSoftKeyboardShow()
    {
        // Code here
    }  
});
     
/*
Open or close the soft keyboard programatically
*/
softKeyboard.openSoftKeyboard();
softKeyboard.closeSoftKeyboard();
 
/*
SoftKeyboard can catch keyboard events when an EditText gains focus and keyboard appears
*/
 
/* Prevent memory leaks:
*/
@Override
public void onDestroy()
{
    super.onDestroy();
    softKeyboard.unRegisterSoftKeyboardCallback();
}

Besides this, you must add this line to your activity reference in your manifest.xml.

1
android:windowSoftInputMode="adjustResize"

While I were coding this snippet I encountered with another snipper which is pretty awesome. It is basically the same, involves subclassing your main layout but I think is a very elegant solution. Use which solution fits better with your needs. Happy coding!

UPDATE (06/15/14): This snippet has been updated with some improvements but It can be used in the same straightforward way and it handles keyboard events from EditText too
This snippet was too specific and it could not catch open keyboard events from EditText, it only worked if you opened and closed keyboard with provided functions and EditText opens keyboard as default behavior. Obviously this was a heavy shortcoming I did not notice before. So here it is my fix: The constructor of SoftKeyboard has been changed from:

1
public SoftKeyboard(View layout, InputMethodManager im)

to:

1
public SoftKeyboard(ViewGroup layout, InputMethodManager im)

This means it is necessary to pass a reference of main layout now. Maybe it is more strict now but it is necessary for something we are going to see next and, really, it was not working with whatever view.

SoftKeyboard class, when instantiated, it is going to try to get a reference, if possible, of every EditText available. This will allow it to handle focus changes when a EditText is selected and keyboard appears.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void initEditTexts()
{
    editTextList = new ArrayList<EditText>();
    int childCount = layout.getChildCount();
    for(int i=0;i<=childCount -1;i++)
    {
        View v = layout.getChildAt(i);
        if(v instanceof EditText)
        {
           EditText editText = (EditText) v;
           editText.setOnFocusChangeListener(this);
           editText.setCursorVisible(false);
           editTextList.add(editText);
        }
    }
}

SoftKeyboard implements the interface View.OnFocusChangeListener to handle properly focus changes on our EditTexts.

1
2
3
4
5
6
7
8
9
10
11
@Override
public void onFocusChange(View v, boolean hasFocus)
{  
    if(hasFocus && !isKeyboardShow)
    {
        layoutBottom = getLayoutCoordinates();
        softKeyboardThread.keyboardOpened();
        isKeyboardShow = true;
        tempView = v;
    }
}

I noticed the resize process when keyboard appears through an EditText gaining focus has some differences and it was breaking the logic of the thread which is checking the bottom position of the layout. So i added this silly loop to handle it.

1
2
3
4
5
6
7
// When keyboard is opened from EditText, initial bottom location is greater than layoutBottom
// and at some moment equals layoutBottom.
// That broke the previous logic, so I added this new loop to handle this.
while(currentBottomLocation >= layoutBottom)
{
    currentBottomLocation = getLayoutCoordinates();
}

There are some minor changes too but I think these are the most important changes. I have tested it but nothing is 100% bugs-free. I would love read your feedback about this modification.

UPDATE (10/07/14): Thanks to Francesco Verheye(verheye.francesco@gmail.com) this snippet can handle EditTexts attached to nested subViews. initEditText modifications:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void initEditTexts(ViewGroup viewgroup)
{
    if(editTextList == null)
        editTextList = new ArrayList<EditText>();
         
    int childCount = viewgroup.getChildCount();
    for(int i=0; i<= childCount-1;i++)
    {
        View v = viewgroup.getChildAt(i);
 
        if(v instanceof ViewGroup)
        {
        initEditTexts((ViewGroup) v);
    }
 
        if(v instanceof EditText)
        {
            EditText editText = (EditText) v;
            editText.setOnFocusChangeListener(this);
            //editText.setCursorVisible(false);
            editTextList.add(editText);
        }
    }
}




felHR85_SoftKeyboard.zip
0.0MB