lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

입력 이벤트

Android에는 사용자와 애플리케이션의 상호작용으로부터 이벤트를 가로채는 방법이 여러 가지 있습니다. 사용자 인터페이스 내의 이벤트가 관련된 경우, 이러한 방식은 이벤트를 사용자가 상호작용하는 특정 View 객체로부터 캡처하는 것입니다. 이에 필요한 수단은 View 클래스가 제공합니다.

레이아웃을 작성하는 데 사용하게 되는 여러 가지 View 클래스 내에서 UI 이벤트에 유용해 보이는 여러 공개 콜백 메서드를 찾을 수 있습니다. 이러한 메서드는 해당 객체에서 각각의 작업이 발생할 때 Android 프레임워크가 호출합니다. 예를 들어 뷰(예: 버튼)를 하나 터치하면 해당 객체에서 onTouchEvent() 메서드가 호출됩니다. 그러나 이것을 가로채려면 클래스를 확장하고 메서드를 재정의해야 합니다. 다만 그런 이벤트를 처리하기 위해 모든 View 객체를 다 확장하는 것은 타당성이 없습니다. 이 때문에 View 클래스에 일련의 중첩된 인터페이스가 있고 거기에 훨씬 쉽게 정의할 수 있는 콜백에 있습니다. 이와 같은 인터페이스를 일명 이벤트 리스너라고 하며, UI와의 사용자 상호작용을 캡처하는 데 아주 적합합니다.

사용자 상호작용을 수신 대기하는 데에는 이벤트 리스너를 사용하는 것이 좀 더 보편적이지만, 사용자 지정 구성 요소를 빌드하기 위해 View 클래스를 확장하고자 하는 상황이 올 수도 있습니다. 어쩌면 Button 클래스를 확장하여 무언가 더 복잡한 것을 만들고자 할 수도 있습니다. 이런 경우, 클래스에 대한 기본 이벤트 동작을 클래스 이벤트 핸들러를 사용하여 정의할 수 있습니다.

이벤트 리스너

이벤트 리스너란 View 클래스 내에 있는 인터페이스로, 하나의 콜백 메서드가 포함되어 있습니다. 이러한 메서드는 리스너가 등록된 뷰가 UI 안의 항목과 사용자의 상호작용으로 인하여 트리거되었을 때 Android 프레임워크가 호출합니다.

이벤트 리스너 인터페이스에 포함된 콜백 메서드는 다음과 같습니다.

onClick()
View.OnClickListener에서 온 것입니다. 이 메서드는 사용자가 항목을 터치하거나 (터치 모드에 있을 때), 탐색 키 또는 트랙볼을 사용하여 해당 항목에 포커스를 맞추고 적절한 'Enter' 키를 누르거나 트랙볼을 누르면 호출됩니다.
onLongClick()
View.OnLongClickListener에서 온 것입니다. 이 메서드는 사용자가 항목을 길게 누르거나(터치 모드에 있을 때), 탐색 키 또는 트랙볼을 사용하여 해당 항목에 포커스를 맞추고 적절한 'Enter' 키를 누르거나 트랙볼을 누를 때 호출됩니다(1초간).
onFocusChange()
View.OnFocusChangeListener에서 온 것입니다. 이것이 호출되는 것은 사용자가 탐색 키 또는 트랙볼을 사용하여 항목 쪽으로 이동하거나 항목에서 멀어질 때입니다.
onKey()
View.OnKeyListener에서 온 것입니다. 이 메서드는 사용자가 항목에 포커스를 맞추고 있으면서 기기에 있는 하드웨어 키를 누르거나 키에서 손을 떼면 호출됩니다.
onTouch()
View.OnTouchListener에서 온 것입니다. 이것이 호출되는 것은 사용자가 터치 이벤트로서의 자격을 만족하는 작업을 수행하는 경우로, 여기에 누르기, 손 떼기와 화면에서 이루어지는 모든 움직임 동작(항목의 경계 내에서)이 포함됩니다.
onCreateContextMenu()
View.OnCreateContextMenuListener에서 온 것입니다. 이것을 호출하는 것은 컨텍스트 메뉴가 구축되는 중일 때입니다(정체된 "길게 클릭"의 결과로). 메뉴 개발자 가이드에 있는 컨텍스트 메뉴 관련 논의를 참조하세요.

이러한 메서드는 각자의 인터페이스 안에 거주하는 유일한 주민입니다. 이러한 메서드 중 하나를 정의하고 이벤트를 처리하려면 액티비티 내의 중첩된 인터페이스를 구현하거나 익명의 클래스로 정의하면 됩니다. 그런 다음 구현의 인스턴스 하나를 각각의 View.set...Listener() 메서드에 전달하세요 (예: setOnClickListener()를 호출한 다음 이를 OnClickListener의 구현에 전달합니다.)

아래의 예시는 버튼에 대하여 온-클릭 리스너를 등록하는 방법을 보여줍니다.

// Create an anonymous implementation of OnClickListener
private OnClickListener mCorkyListener = new OnClickListener() {
    public void onClick(View v) {
      // do something when the button is clicked
    }
};

protected void onCreate(Bundle savedValues) {
    ...
    // Capture our button from layout
    Button button = (Button)findViewById(R.id.corky);
    // Register the onClick listener with the implementation above
    button.setOnClickListener(mCorkyListener);
    ...
}

OnClickListener를 액티비티의 일부로 구현하는 것이 더 편리할 수도 있습니다. 이렇게 하면 추가적인 클래스 부하와 객체 할당을 피할 수 있습니다. 예:

public class ExampleActivity extends Activity implements OnClickListener {
    protected void onCreate(Bundle savedValues) {
        ...
        Button button = (Button)findViewById(R.id.corky);
        button.setOnClickListener(this);
    }

    // Implement the OnClickListener callback
    public void onClick(View v) {
      // do something when the button is clicked
    }
    ...
}

위의 예시에서 onClick() 콜백에는 반환 값이 없지만 다른 이벤트 리스너 메서드 중에는 부울 값을 반환해야만 하는 것도 있다는 점을 유의하세요. 그 이유는 이벤트에 따라 다릅니다. 이런 필수 사항이 적용되는 몇몇 메서드의 경우, 이유는 다음과 같습니다.

  • onLongClick() - 이 메서드는 부울 값을 반환하여 이벤트를 완전히 사용하였으며 더 이상 전달되지 않아야 하는지 여부를 나타냅니다. 다시 말해, true를 반환하면 이벤트를 처리했으며 여기에서 중단해야 한다는 것을 의미하며 false를 반환하면 이벤트를 아직 처리하지 않았거나 이 이벤트를 다른 온-클릭 리스너로 계속 진행해야 할지 나타내는 것입니다.
  • onKey() - 이 메서드는 부울 값을 반환하여 이벤트를 완전히 사용하였으며 더 이상 전달되지 않아야 하는지 여부를 나타냅니다. 다시 말해, true을 반환하여 이벤트를 처리했으며 여기에서 중단해야 한다는 것을 나타냅니다. 이벤트를 아직 처리하지 않았거나 이 이벤트가 계속 다른 온-키 리스너를 기다려야 하는 경우에는 false를 반환합니다.
  • onTouch() - 이 메서드는 부울 값을 반환하여 리스너가 이 이벤트를 사용하는지 여부를 나타냅니다. 여기서 중요한 점은 이 이벤트에는 서로 연달아 발생하는 여러 개의 작업이 있을 수 있다는 것입니다. 그러므로 'down' 작업 이벤트를 수신했을 때 false를 반환하면, 해당 이벤트를 사용하지 않았으며 이 이벤트로부터 이어지는 이후의 작업에 흥미가 없음을 나타내는 것이 됩니다. 따라서 이 이벤트 내의 다른 모든 작업에 대해 호출되지 않습니다(예: 손가락 동작 또는 최종적인 'up' 작업 이벤트 등).

하드웨어 키 이벤트는 항상 현재 포커스를 가진 뷰로 전달된다는 점을 명심하세요. 이들은 뷰 계층의 맨 위에서 시작하여 아래 방향으로 발송되어 적절한 목적지에 도달할 때까지 계속합니다. 뷰(또는 뷰의 하위 요소)가 현재 포커스를 갖고 있으면, 이벤트가 dispatchKeyEvent() 메서드를 통과하여 이동하는 것을 확인할 수 있습니다. 뷰를 통해 키 이벤트를 캡처하는 대신, 액티비티 내부의 모든 이벤트를 onKeyDown()onKeyUp()을 사용하여 수신할 수도 있습니다.

또한, 애플리케이션에 대한 텍스트 입력의 경우 대다수의 기기에는 소프트웨어 입력 메서드만 있다는 사실을 명심하세요. 그러한 메서드는 반드시 키 기반이 아니어도 됩니다. 음성 입력, 손글씨 등을 사용하는 것도 있습니다. 입력 메서드가 키보드 같은 인터페이스를 표시하더라도 일반적으로 onKeyDown() 이벤트군을 트리거하지는 않습니다. 특정 키 누름을 제어해야만 하는 UI는 절대 구축하면 안 됩니다. 이렇게 하면 애플리케이션이 하드웨어 키보드가 있는 기기에만 한정됩니다. 특히, 사용자가 Enter 키를 누를 때 입력의 유효성을 검사하기 위해 이와 같은 메서드에 의존해서는 안 됩니다. 대신, IME_ACTION_DONE과 같은 작업을 사용하여 애플리케이션이 반응할 것으로 기대되는 방식에 해당되는 입력 메서드에게 신호를 보내 의미 있는 방식으로 UI를 변경할 수 있게 하는 것이 좋습니다. 소프트웨어 입력 메서드가 어떻게 작동할지 임의로 추정하지 마시고, 이미 형식화된 텍스트를 애플리케이션에 제공해줄 것이라 믿으면 됩니다.

참고: Androids는 우선 이벤트 핸들러부터 호출하고, 그 다음에 클래스 정의로부터 가져온 적절한 기본 핸들러를 두 번째로 호출합니다. 따라서, 이와 같은 이벤트 리스너에서 true를 반환하면 이벤트가 다른 이벤트 리스너로 전파되는 것을 중지시킬 뿐만 아니라 뷰에 있는 기본 이벤트 핸들러로의 콜백도 차단하게 됩니다. 따라서 true를 반환하는 경우 해당 이벤트를 종료하고 싶은 것인지 확신해야 합니다.

이벤트 핸들러

뷰에서 사용자 지정 구성 요소를 구축하는 경우, 기본 이벤트 핸들러로 사용될 콜백 메서드를 여러 개 정의할 수 있게 됩니다. 사용자 지정 구성 요소에 대한 문서를 보면 이벤트 처리에 사용되는 몇 가지 보편적인 콜백을 확인할 수 있습니다. 다음은 그 몇 가지 예입니다.

개발자 여러분이 알아두어야 하는 다른 메서드가 몇 가지 더 있습니다. 이들은 View 클래스의 일부분이 아니지만, 이벤트를 처리할 수 있는 방식에 직접적으로 영향을 미칠 수 있는 것들입니다. 따라서 레이아웃 안에서 좀 더 복잡한 이벤트를 관리하는 경우, 이와 같은 다른 메서드도 고려하세요.

터치 모드

사용자가 방향 키 또는 트랙볼을 사용하여 사용자 인터페이스를 탐색하고 있는 경우, 작동 가능한 항목(예: 버튼)에 포커스를 맞춰 어느 항목이 입력이 허용되는지 사용자가 알 수 있도록 해야 합니다. 하지만 기기에 터치 기능이 있고 사용자가 인터페이스를 터치하여 인터페이스와의 상호작용을 시작하는 경우라면, 더 이상 항목을 강조 표시하거나 특정 뷰에 포커스를 맞추지 않아도 됩니다. 따라서 "터치 모드"라고 불리는 상호작용에 대한 모드가 따로 있습니다.

터치 기능이 있는 기기의 경우, 사용자가 화면을 터치하면 기기가 터치 모드에 진입하게 됩니다. 이 시점부터는 isFocusableInTouchMode()가 참인 뷰에만 포커스를 맞출 수 있습니다. 예를 들어 텍스트 편집 위젯이 이에 해당됩니다. 버튼처럼 터치할 수 있는 다른 뷰의 경우 터치해도 주의를 끌 수 없으며 이를 누르면 단순히 온-클릭 리스너만 실행됩니다.

사용자가 방향 키를 누르거나 트랙볼로 스크롤 동작을 할 때마다 기기가 터치 모드를 종료하고 포커스를 맞출 뷰를 찾습니다. 이제 사용자는 사용자 인터페이스와 상호작용을 재개해도 되며, 화면을 터치하지 않아도 됩니다.

터치 모드 상태는 시스템 전체에 걸쳐 유지됩니다(모든 창과 액티비티 포함). 현재 상태를 쿼리하려면 isInTouchMode()를 호출하여 기기가 현재 터치 모드에 있는지 확인하면 됩니다.

포커스 처리

프레임워크가 사용자 입력에 응답하여 일상적인 포커스 이동을 처리합니다. 여기에는 뷰가 제거되거나 숨겨지거나, 또는 새 뷰를 사용할 수 있게 될 때 포커스를 변경하는 것이 포함됩니다. 뷰는 포커스를 받고자 하는 의향을 isFocusable() 메서드를 통해 나타냅니다. 뷰가 포커스를 받을 수 있는지 여부를 변경하려면 setFocusable()을 호출합니다. 터치 모드에 있는 경우, 어느 뷰가 isFocusableInTouchMode()로 포커스를 받는 것을 허용하는지 여부를 쿼리할 수 있습니다. 이것은 setFocusableInTouchMode()로 변경하면 됩니다.

포커스 이동은 주어진 방향에서 가장 가까운 이웃을 찾아내는 알고리즘을 기반으로 합니다. 드문 일이지만 기본 알고리즘이 개발자가 의도한 동작과 일치하지 않는 경우도 있습니다. 이러한 상황이라면, 레이아웃 파일에서 nextFocusDown, nextFocusLeft, nextFocusRight, nextFocusUp 등의 XML 특성을 사용하여 명시적 재정의를 제공하면 됩니다. 이러한 특성 중 하나를 포커스가 떠나고 있는 뷰에 추가합니다. 특성의 값을 포커스를 맞춰야 할 뷰의 ID가 되도록 정의합니다. 예:

<LinearLayout
    android:orientation="vertical"
    ... >
  <Button android:id="@+id/top"
          android:nextFocusUp="@+id/bottom"
          ... />
  <Button android:id="@+id/bottom"
          android:nextFocusDown="@+id/top"
          ... />
</LinearLayout>

보통은 이런 수직 레이아웃에서 첫 버튼부터 위로 이동하면 아무 데도 갈 수 없고, 두 번째 버튼에서 아래로 이동해도 마찬가지입니다. 이제 맨 위 버튼이 맨 아래 버튼을 nextFocusUp으로 정의했습니다(반대쪽도 마찬가지). 따라서 탐색 포커스가 위에서 아래로 갔다가 아래에서 위로 순환하게 됩니다.

뷰를 UI에서 포커스를 맞출 수 있는 것으로 선언하고자 하는 경우(일반적으로는 그렇지 않음), 뷰에 레이아웃 선언에서 android:focusable XML 특성을 추가합니다. 이 값을 true로 설정합니다. 터치 모드에 있을 때에도 android:focusableInTouchMode를 사용하여 뷰를 포커스를 받을 수 있는 것으로 선언할 수 있습니다.

특정 뷰에 포커스를 맞추도록 요청하려면, requestFocus()를 호출합니다.

포커스 이벤트를 수신 대기하려면(어떤 뷰에 포커스가 맞춰지거나 이를 잃는 경우 알림을 받으려면), onFocusChange()를 사용하면 됩니다. 이는 위의 이벤트 리스너 섹션에서 설명한 바와 같습니다.