대형 화면에서의 입력 호환성

대형 화면 기기에서 사용자는 키보드, 마우스, 트랙패드, 스타일러스 또는 게임패드를 사용하여 앱과 더 많은 상호작용을 합니다. 앱이 외부 기기의 입력을 허용하도록 사용 설정하려면 다음 사항을 따르세요.

  • 기본 키보드 지원 테스트(예: Tab과 화살표 키 키보드로 탐색, Enter 키로 텍스트 입력 확인, 미디어 앱에서 스페이스바를 사용한 재생 및 일시중지)
  • 해당하는 경우 표준 단축키 추가(예: 실행취소는 Ctrl + Z, 저장은 Ctrl + S)
  • 마우스 오른쪽 버튼으로 클릭 시 컨텍스트 메뉴 표시, 마우스 오버 시 아이콘 변경, 맞춤 뷰에서 마우스 휠/트랙패드 스크롤 이벤트와 같은 방식으로 기본 마우스 상호작용 테스트
  • 특정 앱에 사용되는 입력 장치 테스트(예: 그리기 앱용 스타일러스, 게임용 게임 컨트롤러 및 음악 앱용 MIDI 컨트롤러)
  • 데스크톱 환경에서 앱을 돋보이게 만들 수 있는 고급 입력 지원 기능 고려(예: DJ 앱의 크로스페이더로 사용되는 터치패드, 게임용 마우스 캡처, 키보드 중심의 사용자를 위한 확장 단축키)

키보드

앱이 키보드 입력에 반응하는 방식을 통해 원활한 대형 화면 환경을 구현할 수 있습니다. 키보드 입력에는 탐색, 키 입력, 단축키의 세 가지 종류가 있습니다.

키보드 탐색은 터치 중심 앱에서는 거의 구현되지 않지만, 사용자는 앱을 사용하면서 키보드가 있으면 키보드를 이용한 이동이 가능할 것으로 기대합니다. 또한 휴대전화, 태블릿, 폴더블, 데스크톱 기기에서 접근성과 관련한 요구사항을 가진 사용자에게 필수적일 수 있습니다.

많은 앱에서 간단한 화살표 키와 Tab 탐색만으로도 충분하며 이는 대부분 Android 프레임워크에 의해 자동으로 처리됩니다. 예를 들어 Button 뷰는 기본적으로 포커스가 가능하며, 키보드 탐색은 일반적으로 추가 코드 없이 작동해야 합니다. 기본적으로 포커스가 가능하지 않은 뷰에 키보드 탐색을 사용 설정하려면 개발자는 이러한 뷰를 포커스 가능으로 표시해야 합니다. 프로그래매틱 방식으로 또는 XML에서 처리할 수 있습니다(아래 참고). 자세한 내용은 포커스 처리를 참고하세요.

Kotlin

yourView.isFocusable = true

Java

yourView.setFocusable(true);

또는 다음과 같이 레이아웃 파일에서 focusable 속성을 설정할 수 있습니다.

android:focusable="true"

포커스가 사용 설정되면 Android 프레임워크는 위치를 기반으로 포커스 가능한 모든 뷰의 탐색 매핑을 생성합니다. 이 매핑은 일반적으로 예상대로 작동하며 추가 작업이 필요하지 않습니다. 기본 매핑이 앱의 요구에 적합하지 않을 때는 다음과 같이 재정의할 수 있습니다.

Kotlin

// Arrow keys
yourView.nextFocusLeftId = R.id.view_to_left
yourView.nextFocusRightId = R.id.view_to_right
yourView.nextFocusTopId = R.id.view_above
yourView.nextFocusBottomId = R.id.view_below

// Tab key
yourView.nextFocusForwardId = R.id.next_view

Java

// Arrow keys
yourView.setNextFocusLeftId(R.id.view_to_left);
yourView.setNextFocusRightId(R.id.view_to_left);
yourView.setNextFocusTopId(R.id.view_to_left);
yourView.setNextFocusBottomId(R.id.view_to_left);

// Tab key
yourView.setNextFocusForwardId(R.id.next_view);

각각의 출시 전에 키보드만 사용하여 앱의 모든 기능에 액세스해 보는 것이 좋습니다. 마우스나 터치 입력 없이 가장 일반적인 작업에 쉽게 액세스할 수 있어야 합니다.

접근성과 관련한 요구사항을 가진 사용자에게는 키보드 지원이 필수적일 수 있습니다.

키 입력

EditText와 같이 화면 가상 키보드(IME)로 처리되는 텍스트 입력의 경우 앱은 개발자의 추가 작업 없이 대형 화면 기기에서 예상대로 작동해야 합니다. 프레임워크에서 예상할 수 없는 키 입력의 경우 앱이 동작을 직접 처리해야 합니다. 맞춤 뷰가 있는 앱의 경우에 특히 그렇습니다.

몇 가지 예로 Enter 키를 사용하여 메시지를 보내는 채팅 앱, 스페이스바로 재생을 시작/중지하는 미디어 앱, w, a, s, d 키로 움직임을 제어하는 게임이 있습니다.

대부분의 앱은 아래와 같이 onKeyUp() 콜백을 재정의하고 수신된 키 코드마다 예상되는 동작을 추가합니다.

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
    return when (keyCode) {
        KeyEvent.KEYCODE_ENTER -> {
            sendChatMessage()
            true
        }
        KeyEvent.KEYCODE_SPACE -> {
            playOrPauseMedia()
            true
        }
        else -> super.onKeyUp(keyCode, event)
    }
}

Java

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_ENTER) {
        sendMessage();
        return true;
    } else if (KeyEvent.KEYCODE_SPACE){
        playOrPauseMedia();
        return true;
    } else {
        return super.onKeyUp(keyCode, event);
    }
}

키가 해제되면 onKeyUp 이벤트가 발생합니다. 이 콜백을 사용하면 키를 누른 채로 있거나 천천히 손을 뗄 때 앱이 여러 onKeyDown 이벤트를 처리할 필요가 없습니다. 키가 눌리는 순간을 인식하고 싶어 하거나 사용자가 키보드 키를 누르고 있어야 하는 게임과 앱은 onKeyDown() 이벤트를 검색하고 반복된 onKeyDown 이벤트를 자체적으로 처리할 수 있습니다.

키보드 지원 제공에 관한 자세한 내용은 키보드 작업 처리를 참고하세요.

단축키

하드웨어 키보드를 사용할 때는 일반적인 Ctrl, Alt, Shift 기반 단축키가 필요합니다. 앱에서 이러한 단축키를 구현하지 않으면 사용자가 앱 사용 환경에 불편을 겪을 수 있습니다. 또한 고급 사용자는 개별 앱별로 자주 사용하는 작업을 위한 단축키도 유용하게 사용합니다. 단축키가 있으면 앱을 편리하게 사용할 수 있으며 단축키가 없는 앱과 차별화됩니다.

몇 가지 일반적인 단축키로는 Ctrl + S(저장), Ctrl + Z(실행취소) 및 Ctrl + Shift + Z(다시 실행)가 있습니다. 고급 단축키의 몇 가지 예는 VLC 미디어 플레이어 단축키 목록을 참고하세요.

dispatchKeyShortcutEvent()를 사용하여 단축키를 구현할 수 있습니다. 이 메서드는 지정된 키 코드의 모든 메타 키 조합(Alt, Ctrl, Shift)을 대체합니다. 특정 메타 키를 확인하려면 KeyEvent.isCtrlPressed(), KeyEvent.isShiftPressed(), KeyEvent.isAltPressed(), KeyEvent.hasModifiers()를 사용합니다.

다른 키 입력 처리(예: onKeyUp(), onKeyDown())에서 단축키 코드를 분리하면 코드 유지관리가 더 쉬워지며 모든 경우에 메타 키 확인을 수동으로 구현할 필요 없이 메타 키의 기본 수용을 사용 설정할 수 있습니다. 모든 메타 키 조합을 허용하면 다양한 키보드 레이아웃 및 운영체제에 익숙한 사용자에게 더 편리할 수 있습니다.

Kotlin

override fun dispatchKeyShortcutEvent(event: KeyEvent): Boolean {
  return when (event.keyCode) {
    KeyEvent.KEYCODE_O -> {
      openFile() // Ctrl+O, Shift+O, Alt+O
      true
    }
    KeyEvent.KEYCODE_Z-> {
      if (event.isCtrlPressed) {
        if (event.isShiftPressed) {
          redoLastAction() // Ctrl+Shift+Z pressed
          true
        } else {
          undoLastAction() // Ctrl+Z pressed
          true
        }
      }
    }
    else -> {
      return super.dispatchKeyShortcutEvent(event)
    }
  }
}

Java

@Override
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
  if (event.getKeyCode() == KeyEvent.KEYCODE_O) {
      openFile(); // Ctrl+O, Shift+O, Alt+O
      return true;
  } else if(event.getKeyCode() == KeyEvent.KEYCODE_Z) {
      if (event.isCtrlPressed()) {
          if (event.isShiftPressed()) {
              redoLastAction();
              return true;
          }
          else {
              undoLastAction();
              return true;
          }
      }
  }
  return super.dispatchKeyShortcutEvent(event);
}

위와 동일한 방식으로 KeyEvent.isCtrlPressed(), KeyEvent.isShiftPressed(), KeyEvent.isAltPressed()를 확인하여 onKeyUp()에서 단축키를 구현할 수도 있습니다. 메타 동작이 단축키보다는 오히려 앱 동작에 관한 수정이라면 이 방법이 유지관리하기 더 쉬울 수 있습니다. W가 '앞으로 걷기'를 의미하고 Shift + W가 '앞으로 달리기'를 의미할 때를 예로 들 수 있습니다.

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
  return when(keyCode) {
    KeyEvent.KEYCODE_W-> {
      if (event.isShiftPressed) {
        if (event.isCtrlPressed) {
          flyForward() // Ctrl+Shift+W pressed
          true
        } else {
          runForward() // Shift+W pressed
          true
        }
      } else {
        walkForward() // W pressed
        true
      }
    }
    else -> super.onKeyUp(keyCode, event)
  }
}

Java

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_W) {
        if (event.isShiftPressed()) {
            if (event.isCtrlPressed()) {
                flyForward(); // Ctrl+Shift+W pressed
                return true;
            } else {
                runForward(); // Shift+W pressed
                return true;
            }
        } else {
            walkForward();
            return true;
        }
    }
    return super.onKeyUp(keyCode, event);
}

스타일러스

대부분의 대형 화면 기기에는 스타일러스가 함께 제공되며 Android 앱은 이를 터치스크린 입력으로 처리합니다. 일부 기기에는 Wacom Intuos와 같은 USB 또는 블루투스 그리기 테이블이 있을 수도 있습니다. Android 앱은 블루투스 입력을 수신할 수 있지만 USB 입력에서는 작동하지 않습니다.

스타일러스 이벤트는 View.onTouchEvent()View.onGenericMotionEvent()를 통해 터치스크린 이벤트로 보고되며 SOURCE_STYLUS 유형의 MotionEvent.getSource()를 포함합니다.

MotionEvent는 다음과 같이 추가 데이터도 포함합니다.

이전 포인트

Android는 입력 이벤트를 일괄 처리하며 프레임당 한 번씩 전달합니다. 스타일러스 펜은 디스플레이보다 훨씬 자주 이벤트를 보고할 수 있습니다. 그리기 앱을 만들 때는 다음과 같은 getHistorical API를 사용하여 최근에 발생한 이벤트를 확인하는 것이 중요합니다.

  • MotionEvent.getHistoricalX()
  • MotionEvent.getHistoricalY()
  • MotionEvent.getHistoricalPressure()
  • MotionEvent.getHistoricalAxisValue()

손바닥 움직임 무시

사용자는 스타일러스를 사용하여 앱에서 그리거나 쓰거나 상호작용할 때 손바닥으로 화면을 터치하기도 합니다. 이러한 터치 이벤트(ACTION_DOWN 또는 ACTION_POINTER_DOWN으로 설정)를 시스템이 인식하여 의도치 않은 터치로 무시하기 전에 이벤트가 앱에 보고되는 경우가 있습니다.

Android는 MotionEvent를 전달하여 손바닥 터치 이벤트를 취소합니다. 앱이 ACTION_CANCEL을 수신하면 동작을 취소합니다. 앱이 ACTION_POINTER_UP을 수신하면 FLAG_CANCELED가 설정되었는지 확인합니다. 설정되어 있는 경우 동작을 취소합니다.

FLAG_CANCELED만 확인해서는 안 됩니다. Android 13부터 편의를 위해 시스템이 ACTION_CANCEL 이벤트에 FLAG_CANCELED를 설정하지만 이전 버전에서는 그렇지 않습니다.

Android 12

Android 12(API 수준 32) 이하에서는 단일 포인터 터치 이벤트에만 손바닥 거부 감지가 가능합니다. 손바닥 터치가 유일한 포인터인 경우 시스템은 모션 이벤트 객체에 ACTION_CANCEL을 설정하여 이벤트를 취소합니다. 다른 포인터가 다운이면 시스템은 ACTION_POINTER_UP을 설정하는데 이는 손바닥 거부를 감지하는 데 충분하지 않습니다.

Android 13

Android 13(API 수준 33) 이상에서 손바닥 터치가 유일한 포인터인 경우 시스템은 모션 이벤트 객체에 ACTION_CANCELFLAG_CANCELED를 설정하여 이벤트를 취소합니다. 다른 포인터가 다운이면 시스템은 ACTION_POINTER_UPFLAG_CANCELED를 설정합니다.

앱에서 ACTION_POINTER_UP으로 모션 이벤트를 수신할 때마다 FLAG_CANCELED를 확인하여 이벤트가 손바닥 거부 또는 다른 이벤트 취소를 나타내는지 확인합니다.

메모 작성 앱

ChromeOS에는 등록된 메모 작성 앱을 사용자에게 표시하는 특별한 인텐트가 있습니다. 앱을 메모 작성 앱으로 등록하려면 Android 매니페스트에 다음을 추가합니다.

<intent-filter>
    <action android:name="org.chromium.arc.intent.action.CREATE_NOTE" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

앱이 등록되면 사용자는 이 앱을 기본 메모 작성 앱으로 선택할 수 있습니다. 새 메모가 요청되면 앱에서 스타일러스 입력을 위한 빈 메모를 생성할 것입니다. 사용자가 이미지(예: 스크린샷 또는 다운로드한 이미지)에 주석을 추가하려는 경우 content:// URI가 있는 하나 이상의 항목이 포함된 ClipData를 사용하여 앱이 실행됩니다. 앱은 처음 첨부된 이미지를 배경 이미지로 사용하는 메모를 생성하고 사용자가 스타일러스를 사용하여 화면에 그릴 수 있는 모드로 전환해야 합니다.

스타일러스 없이 메모 작성 인텐트 테스트

앱이 활성화된 스타일러스 없이 메모 작성 인텐트에 올바르게 응답하는지 테스트하려면 다음 방법을 사용하여 ChromeOS에서 메모 작성 옵션을 표시합니다.

  1. 개발자 모드로 전환하여 기기를 쓰기 가능하게 만듭니다.
  2. Ctrl + Alt + F2를 눌러 터미널을 엽니다.
  3. 명령어 sudo vi /etc/chrome_dev.conf를 실행합니다.
  4. i를 눌러 파일 끝의 새 줄에 --ash-enable-palette를 추가합니다.
  5. Esc 키를 누른 후 :, w, q를 입력하고 Enter 키를 눌러 저장합니다.
  6. Ctrl + Alt + F1을 눌러 일반 ChromeOS UI로 돌아갑니다.
  7. 로그아웃했다가 다시 로그인합니다.

이제 실행기에 스타일러스 메뉴가 있을 것입니다.

  • 실행기에서 스타일러스 버튼을 탭하고 새 메모를 선택합니다. 그러면 비어 있는 그리기 메모가 열립니다.
  • 스크린샷을 찍습니다. 실행기에서 스타일러스 버튼 > 화면 캡처를 선택하거나 이미지를 다운로드합니다. 알림에 '이미지에 주석 달기' 옵션이 있을 것입니다. 그러면 주석을 추가할 준비가 된 이미지와 함께 앱이 실행됩니다.

마우스 및 터치패드 지원

대부분의 앱은 일반적으로 세 가지 대형 화면 중심 이벤트, 즉 마우스 오른쪽 버튼 클릭, 마우스 오버, 드래그 앤 드롭만 처리하면 됩니다.

마우스 오른쪽 버튼 클릭

목록 항목을 길게 터치하는 것과 같이 앱이 컨텍스트 메뉴를 표시하게 하는 작업은 마우스 오른쪽 버튼 클릭 이벤트에도 반응해야 합니다. 마우스 오른쪽 버튼 클릭 이벤트를 처리하려면 앱에서 View.OnContextClickListener를 등록해야 합니다. 컨텍스트 메뉴 구성에 관한 자세한 내용은 컨텍스트 메뉴 만들기를 참고하세요.

Kotlin

yourView.setOnContextClickListener {
  showContextMenu()
  true
}

Java

yourView.setOnContextClickListener(v -> {
    showContextMenu();
    return true;
});

마우스 오버

개발자는 마우스 오버 이벤트를 처리하여 앱 레이아웃을 세련되고 사용하기 쉽게 만들 수 있습니다. 이는 맞춤 뷰의 경우에 특히 그렇습니다. 이와 관련한 가장 일반적인 두 가지 예는 다음과 같습니다.

  • 마우스 포인터 아이콘을 변경하여 요소에 클릭 가능 또는 수정 가능과 같은 상호작용 동작이 있는지 사용자에게 표시
  • 큰 목록 또는 그리드의 항목 위에 마우스 포인터를 가져가면 항목에 시각적 피드백 추가

Kotlin

// Change the icon to a "hand" pointer on hover,
// Highlight the view by changing the background.
yourView.setOnHoverListener { view, _ ->
  addVisualHighlighting(true)
  view.pointerIcon =
    PointerIcon.getSystemIcon(view.context,
    PointerIcon.TYPE_HAND)
  false // listener did not consume the event.
}

Java

yourView.setOnHoverListener((view, event) -> {
    addVisualHighlighting(true);
    view.setPointerIcon(PointerIcon
            .getSystemIcon(view.getContext(), PointerIcon.TYPE_HAND));
    return true;
});

드래그 앤 드롭

멀티 윈도우 환경에서 사용자는 앱 간에 항목을 드래그 앤 드롭할 수 있을 것으로 기대합니다. 이러한 기대는 화면 분할 모드의 태블릿, 휴대전화, 폴더블뿐만 아니라 데스크톱 기기에도 적용됩니다.

개발자는 사용자가 항목을 앱으로 드래그할 가능성이 있는지 고려해야 합니다. 몇 가지 일반적인 예를 들면 사진 편집기에서 사진을 받거나 오디오 플레이어에서 오디오 파일을 받거나 그리기 프로그램에서 사진을 받는 것을 예상할 수 있습니다.

드래그 앤 드롭 지원을 추가하려면 Android 드래그 앤 드롭 문서를 따르고 이 ChromeOS 블로그 게시물을 살펴보세요.

ChromeOS의 특별 고려사항

  • 앱 외부에서 드래그한 항목에 액세스하려면 requestDragAndDropPermissions를 통해 권한을 요청해야 합니다.
  • 항목을 다른 애플리케이션으로 드래그하려면 항목에 View.DRAG_FLAG_GLOBAL 플래그가 있어야 합니다.

고급 포인터 지원

마우스 및 터치패드 입력의 고급 처리를 실행하는 앱은 View.onGenericMotionEvent()에 관한 Android 문서를 따르고 MotionEvent.getSource()를 사용하여 SOURCE_MOUSESOURCE_TOUCHSCREEN을 구분합니다.

MotionEvent를 검토하여 필요한 동작을 구현합니다.

  • 움직임은 ACTION_HOVER_MOVE 이벤트를 생성합니다.
  • 버튼은 ACTION_BUTTON_PRESSACTION_BUTTON_RELEASE 이벤트를 생성합니다. 또한 getButtonState()를 사용하여 모든 마우스/트랙패드 버튼의 현재 상태를 확인할 수도 있습니다.
  • 마우스 휠 스크롤은 ACTION_SCROLL 이벤트를 생성합니다.

게임 컨트롤러

일부 대형 화면 Android 기기는 최대 4개의 게임 컨트롤러를 지원합니다. 개발자는 표준 Android 게임 컨트롤러 API를 사용하여 게임 컨트롤러를 처리해야 합니다(게임 컨트롤러 지원 참고).

버튼은 일반 매핑에 따라 일반 값에 매핑됩니다. 안타깝게도 모든 게임 컨트롤러 제조업체가 동일한 매핑 규칙을 따르는 것은 아닙니다. 사용자가 널리 사용되는 다양한 컨트롤러 매핑을 선택하도록 허용하면 훨씬 더 나은 환경을 제공할 수 있습니다. 자세한 내용은 게임패드 버튼 누름 처리를 참고하세요.

입력 변환 모드

ChromeOS는 기본적으로 입력 변환 모드를 사용 설정합니다. 대부분의 Android 앱에서 이 모드를 사용하면 앱이 데스크톱 환경에서 예상대로 작동합니다. 몇 가지 예로는 터치패드에서 두 손가락 스크롤, 마우스 휠 스크롤, 창 좌표에 원시 디스플레이 좌표 매핑 자동 사용 설정 등이 있습니다. 일반적으로 앱 개발자는 이러한 동작을 직접 구현할 필요가 없습니다.

앱에서 맞춤 입력 동작을 구현하거나(예: 터치패드에서 두 손가락 모으기 동작의 맞춤 정의) 이러한 입력 변환이 앱에서 예상하는 입력 이벤트를 제공하지 않는다면 Android 매니페스트에 다음 태그를 추가하여 입력 변환 모드를 사용 중지할 수 있습니다.

<uses-feature
    android:name="android.hardware.type.pc"
    android:required="false" />

추가 리소스