고급 스타일러스 기능

Android와 ChromeOS는 Android 및 iOS에서 작동하는 앱을 빌드하는 데 도움이 되는 다양한 API를 사용자에게 탁월한 스타일러스 환경을 제공합니다. 이 MotionEvent 클래스가 노출됨 스타일러스 압력, 스타일러스 압력 등 화면과의 스타일러스 상호작용에 관한 정보 방향, 기울기, 마우스 오버, 손바닥 감지 지연 시간이 짧은 그래픽 및 모션 예측 라이브러리는 스타일러스 화면 렌더링을 개선하여 종이와 펜으로 읽는 것 같은 자연스러운 경험입니다.

MotionEvent

MotionEvent 클래스는 위치와 같은 사용자 입력 상호작용을 나타냅니다. 화면의 터치 포인터의 움직임도 제어할 수 있습니다. 스타일러스 입력의 경우 MotionEvent 압력, 방향, 기울기, 마우스 오버 데이터도 노출합니다.

이벤트 데이터

MotionEvent 데이터에 액세스하려면 구성요소에 pointerInput 수정자를 추가합니다.

@Composable
fun Greeting() {
    Text(
        text = "Hello, Android!", textAlign = TextAlign.Center, style = TextStyle(fontSize = 5.em),
        modifier = Modifier
            .pointerInput(Unit) {
                awaitEachGesture {
                    while (true) {
                        val event = awaitPointerEvent()
                        event.changes.forEach { println(it) }
                    }
                }
            },
    )
}
<ph type="x-smartling-placeholder">

MotionEvent 객체는 UI의 다음과 같은 측면과 관련된 데이터를 제공합니다. 이벤트를 사용합니다.

  • 작업: 기기와의 물리적 상호작용(화면 터치) 포인터를 화면 표면 위로 이동, 포인터를 화면 위로 가져옴 표면
  • 포인터: 화면과 상호작용하는 객체 식별자(손가락, 스타일러스, 마우스
  • 축: 데이터 유형 — x 및 y 좌표, 압력, 기울기, 방향, 및 마우스 오버 (거리)

작업

스타일러스 지원을 구현하려면 사용자가 어떤 작업을 하는지 이해해야 합니다. 있습니다.

MotionEvent는 모션을 정의하는 다양한 ACTION 상수를 제공합니다. 이벤트를 수신합니다. 스타일러스에 가장 중요한 작업은 다음과 같습니다.

작업 설명
ACTION_DOWN
ACTION_POINTER_DOWN
포인터가 화면에 닿습니다.
ACTION_MOVE 화면에서 포인터가 움직입니다.
ACTION_UP
ACTION_POINTER_UP
포인터가 더 이상 화면에 닿지 않습니다.
ACTION_CANCEL 이전 또는 현재 모션 세트를 취소해야 하는 경우입니다.

ACTION_DOWN 시 앱에서 새로운 획 시작과 같은 작업을 실행할 수 있습니다. ACTION_MOVE,로 획을 그리고 ACTION_UP가 트리거됩니다.

특정 작업의 ACTION_DOWN에서 ACTION_UP까지 MotionEvent 작업 집합입니다. 포인터를 모션 세트라고 합니다

포인터

대부분의 화면은 멀티 터치입니다. 시스템은 각 손가락에 포인터를 할당하지만 스타일러스, 마우스 또는 화면과 상호작용하는 기타 포인팅 객체입니다. 포인터 index를 사용하면 특정 포인터에 대한 축 정보를 가져올 수 있습니다. 터치하는 첫 번째 손가락의 위치 또는 두 번째 손가락의 위치.

포인터 인덱스 범위는 0부터 MotionEvent#pointerCount() 드림 빼세요.

포인터의 축 값은 getAxisValue(axis, pointerIndex) 메서드로 액세스할 수 있습니다. 포인터 인덱스가 생략되면 시스템은 첫 번째 있습니다.

MotionEvent 객체에는 사용 중인 포인터 유형에 관한 정보가 포함됩니다. 나 포인터 인덱스를 반복하고 getToolType(pointerIndex) 드림 메서드를 사용하여 축소하도록 요청합니다.

포인터에 관한 자세한 내용은 멀티 터치 처리를 참고하세요. 동작을 사용합니다.

스타일러스 입력

다음을 사용하여 스타일러스 입력을 필터링할 수 있습니다. TOOL_TYPE_STYLUS:

val isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex)

또한 스타일러스는 지우개로 사용된다고 보고할 수 있습니다. TOOL_TYPE_ERASER:

val isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex)

스타일러스 축 데이터

ACTION_DOWNACTION_MOVE는 스타일러스에 관한 축 데이터, 즉 x와 y 좌표, 압력, 방향, 기울기, 마우스 오버

이 데이터에 액세스할 수 있도록 MotionEvent API는 다음을 제공합니다. getAxisValue(int), 여기서 매개변수는 다음 축 식별자 중 하나입니다.

getAxisValue()의 반환 값
AXIS_X 모션 이벤트의 X 좌표입니다.
AXIS_Y 모션 이벤트의 Y 좌표입니다.
AXIS_PRESSURE 터치 스크린 또는 터치패드의 경우 손가락, 스타일러스 또는 기타 포인터로 적용된 압력입니다. 마우스나 트랙볼의 경우 기본 버튼을 누르면 1, 누르지 않으면 0입니다.
AXIS_ORIENTATION 터치 스크린 또는 터치패드의 경우 기기의 수직면을 기준으로 손가락, 스타일러스 또는 기타 포인터의 방향입니다.
AXIS_TILT 라디안으로 표시되는 스타일러스의 기울기 각도입니다.
AXIS_DISTANCE 화면과 스타일러스의 거리입니다.

예를 들어 MotionEvent.getAxisValue(AXIS_X)는 다음 함수의 x 좌표를 반환합니다. 있습니다.

멀티 터치 처리도 참고하세요. 동작을 사용합니다.

위치

다음 호출을 사용하여 포인터의 x 및 y 좌표를 가져올 수 있습니다.

x 및 y 좌표가 매핑된 화면에 스타일러스 그리기
그림 1. 스타일러스 포인터의 x 및 y 화면 좌표

압력

다음 명령어를 사용하여 포인터 압력을 가져올 수 있습니다. MotionEvent#getAxisValue(AXIS_PRESSURE) 또는 첫 번째 포인터의 경우 MotionEvent#getPressure()

터치 스크린 또는 터치패드의 압력 값은 0 (아니요 1)이지만 화면에 따라 더 높은 값이 반환될 수 있습니다. 있습니다.

저압에서 고압의 연속선을 나타내는 스타일러스 획입니다. 왼쪽의 획은 좁고 희미하여 낮은 압력을 나타냅니다. 획이 왼쪽에서 오른쪽으로 가면서 더 넓고 진해져 맨 오른쪽에서는 가장 넓고 진해 최고 압력을 나타냅니다.
그림 2. 압력 표현: 왼쪽은 낮은 압력, 오른쪽은 높은 압력
를 통해 개인정보처리방침을 정의할 수 있습니다.
를 통해 개인정보처리방침을 정의할 수 있습니다.

방향

방향은 스타일러스가 가리키는 방향을 나타냅니다.

포인터 방향은 getAxisValue(AXIS_ORIENTATION) 또는 getOrientation() 를 누릅니다.

스타일러스의 경우 방향은 0에서 파이 (π) 사이의 라디안 값으로 반환됩니다. 또는 0에서 -pi를 시계 반대 방향으로 설정합니다.

방향을 사용하면 실제 브러시를 구현할 수 있습니다. 예를 들어 스타일러스가 평면 브러시를 나타내는 경우 평면 브러시의 너비는 사용합니다.

그림 3. 왼쪽으로 약 -0.57라디안을 가리키는 스타일러스

기울기

기울기는 화면을 기준으로 스타일러스의 경사를 측정합니다.

기울기는 스타일러스의 양의 각도(라디안)를 반환합니다. 여기서 0은 파이/2는 화면에 직각이고 π/2는 표면에서 평평합니다.

기울기 각도는 getAxisValue(AXIS_TILT)( 첫 번째 포인터로 이동).

기울기를 사용하면 다음과 같이 실제 도구에 최대한 가깝게 재현할 수 있습니다. 기울어진 연필로 음영을 모방합니다.

스타일러스가 화면 표면에서 약 40도 기울어졌습니다.
그림 4. 직각에서 약 0.785라디안(45도) 기울어진 스타일러스
를 통해 개인정보처리방침을 정의할 수 있습니다.
를 통해 개인정보처리방침을 정의할 수 있습니다.

마우스 오버

화면과 스타일러스의 거리는 다음을 사용하여 구할 수 있습니다. getAxisValue(AXIS_DISTANCE) 이 메서드는 0.0에서 값을 반환합니다. 스타일러스가 화면에서 멀어짐에 따라 더 높은 값으로 조정됨 마우스 오버 화면과 스타일러스의 촉 (포인트) 사이의 거리는 제조업체입니다. 구현은 앱에 중요한 기능의 경우 정확한 값에 의존하지 마세요.

스타일러스 마우스 오버를 사용하면 브러시의 크기를 미리 보거나 버튼이 선택됩니다.

그림 5. 화면 위에 마우스 오버된 스타일러스. 스타일러스가 화면 표면을 터치하지 않아도 앱이 반응합니다.

참고: Compose는 UI 요소의 상호작용 상태에 영향을 주는 수정자를 제공합니다.

  • hoverable: 포인터 들어가기 및 나가기 이벤트를 사용하여 마우스 오버가 가능한 구성요소를 구성합니다.
  • indication: 상호작용이 발생할 때 이 구성요소의 시각적 효과를 그립니다.

손바닥 움직임 무시, 탐색, 원치 않는 입력

멀티 터치 화면에서 원치 않는 터치가 등록될 수 있습니다. 예를 들어 사용자가 필기를 할 때 자연스럽게 손바닥을 화면에 대고 지지합니다. 손바닥 움직임 무시는 이러한 동작을 감지하고 마지막 MotionEvent 세트를 취소해야 합니다.

따라서 원치 않는 터치가 발생할 수 있도록 사용자 입력 기록을 유지해야 합니다. 화면에서 제거될 수 있고 합법적인 사용자 입력이 있습니다.

ACTION_CANCEL 및 FLAG_CANCELED

ACTION_CANCELFLAG_CANCELED 값: 두 가지 모두 이전 MotionEvent 세트가 취소되므로, 예를 들어 마지막 ACTION_DOWN에서 취소된 것을 실행취소할 수 있습니다. 획을 정의할 수 있습니다.

ACTION_CANCEL

Android 1.0(API 수준 1)에 추가되었습니다.

ACTION_CANCEL은 이전 모션 이벤트 세트를 취소해야 함을 나타냅니다.

다음 중 하나라도 감지되면 ACTION_CANCEL이 트리거됩니다.

  • 탐색 동작
  • 손바닥 움직임 무시

ACTION_CANCEL가 트리거되면 다음을 사용하여 활성 포인터를 식별해야 합니다. getPointerId(getActionIndex()) 그런 다음 입력 기록에서 해당 포인터로 만든 획을 삭제하고 장면을 다시 렌더링합니다.

FLAG_CANCELED

Android 13(API 수준 33)에 추가되었습니다.

FLAG_CANCELED 드림 위로 올라가는 포인터가 의도치 않은 사용자 터치였음을 나타냅니다. 플래그는 일반적으로 사용자가 실수로 화면을 터치할 때 설정 손바닥을 화면에 대면 됩니다.

플래그 값에 액세스하는 방법은 다음과 같습니다.

val cancel = (event.flags and FLAG_CANCELED) == FLAG_CANCELED

플래그가 설정된 경우 마지막 MotionEvent 집합을 실행취소해야 합니다. 이 포인터에서 ACTION_DOWN입니다.

ACTION_CANCEL과 마찬가지로 포인터는 getPointerId(actionIndex)로 찾을 수 있습니다.

그림 6. 스타일러스 획과 손바닥 터치로 MotionEvent 세트가 생성됩니다. 손바닥 터치가 취소되고 디스플레이가 다시 렌더링됩니다.

전체 화면, 더 넓은 화면, 탐색 동작

앱이 전체 화면이며 가장자리 근처에 실행 가능한 요소가 있는 경우(예: 캔버스에 액세스할 수도 있습니다. 내비게이션을 표시하거나 앱을 백그라운드로 이동하면 영향을 주지 않습니다.

그림 7. 앱을 백그라운드로 이동하는 스와이프 동작

동작으로 인해 앱에서 원치 않는 터치가 트리거되지 않도록 하려면 인셋ACTION_CANCEL입니다.

손바닥 움직임 무시, 탐색, 원치 않는 입력도 참고하세요. 섹션으로 이동합니다.

사용 setSystemBarsBehavior() 드림 메서드와 BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE / WindowInsetsController 탐색 동작으로 원치 않는 터치 이벤트가 발생하지 않도록 할 수 있습니다.

// Configure the behavior of the hidden system bars.
windowInsetsController.systemBarsBehavior =
    WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE

인셋 및 동작 관리에 관한 자세한 내용은 다음을 참고하세요.

짧은 지연 시간

지연 시간은 하드웨어, 시스템, 애플리케이션에서 처리하는 데 필요한 시간입니다. 사용자 입력을 렌더링할 수 있습니다

지연 시간 = 하드웨어 및 OS 입력 처리 + 앱 처리 + 시스템 합성

  • 하드웨어 렌더링
를 통해 개인정보처리방침을 정의할 수 있습니다. <ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">지연 시간으로 인해 렌더링된 획이 스타일러스 위치보다 뒤처집니다. 렌더링된 획과 스타일러스 위치 사이의 간격이 지연 시간을 나타냅니다.</ph> <ph type="x-smartling-placeholder">
</ph> 그림 8. 지연 시간으로 인해 렌더링된 획이 스타일러스 위치보다 뒤처집니다.

지연 시간 소스

  • 터치 스크린 (하드웨어)에 스타일러스 등록: 초기 무선 연결 스타일러스와 OS가 등록 및 동기화를 위해 통신할 때
  • 터치 샘플링 레이트 (하드웨어): 터치스크린이 초당 표시되는 횟수 포인터가 표면에 닿는지 확인합니다(60~1000Hz).
  • 입력 처리 (앱): 색상, 그래픽 효과, 변환 적용 영향을 줄 수 있습니다.
  • 그래픽 렌더링(OS + 하드웨어): 버퍼 전환, 하드웨어 처리

지연 시간이 짧은 그래픽

지연 시간이 짧은 Jetpack 그래픽 라이브러리 사용자 입력과 화면 렌더링 사이의 처리 시간을 줄일 수 있습니다.

라이브러리는 다중 버퍼 렌더링을 피하여 처리 시간을 단축하고 즉, 프런트 버퍼 렌더링 기법을 활용하여 화면

전면 버퍼 렌더링

전면 버퍼는 화면이 렌더링에 사용하는 메모리입니다. 가장 가깝습니다. 앱에 직접 그림을 그릴 수 있습니다. 지연 시간이 짧은 라이브러리를 사용하면 전면 버퍼에 직접 렌더링할 수 있습니다. 이렇게 하면 일반적인 다중 버퍼 렌더링에서 발생할 수 있는 버퍼 스와핑 방지 이중 버퍼 렌더링 (가장 일반적인 사례)을 사용할 수 있습니다.

앱이 화면 버퍼에 쓰고 화면 버퍼에서 읽습니다.
그림 9. 전면 버퍼 렌더링
앱이 화면 버퍼로 전환되는 다중 버퍼에 씁니다. 앱이 화면 버퍼에서 읽습니다.
그림 10. 다중 버퍼 렌더링

전면 버퍼 렌더링은 전체 화면을 새로 고치는 데 사용하도록 설계되지 않았습니다. 다음으로 바꿉니다. 앱이 버퍼로 콘텐츠를 렌더링하고, 이 버퍼로부터 있습니다. 결과적으로는 한 번에 여러 번 테어링 (아래 참고)

지연 시간이 짧은 라이브러리는 Android 10 (API 수준 29) 이상에서 사용할 수 있습니다. Android 10 (API 수준 29) 이상을 실행하는 ChromeOS 기기에서 지원됩니다.

종속 항목

짧은 지연 시간 라이브러리는 전면 버퍼 렌더링을 위한 구성요소를 제공합니다. 있습니다. 라이브러리가 앱 모듈에 종속 항목으로 추가됩니다. build.gradle 파일:

dependencies {
    implementation "androidx.graphics:graphics-core:1.0.0-alpha03"
}

GLFrontBufferRenderer 콜백

지연 시간이 짧은 라이브러리에는 GLFrontBufferRenderer.Callback 드림 인터페이스에 포함되어 있으며, 이 클래스는 다음 메서드를 정의합니다.

지연 시간이 짧은 라이브러리는 사용하는 데이터 유형에 대해 독단적이지 않습니다. GLFrontBufferRenderer

그러나 라이브러리는 이 데이터를 수백 개의 데이터 포인트 스트림으로 처리합니다. 따라서 메모리 사용량과 할당을 최적화하도록 데이터를 설계해야 합니다.

콜백

렌더링 콜백을 사용 설정하려면 GLFrontBufferedRenderer.CallbackonDrawFrontBufferedLayer()onDrawDoubleBufferedLayer()를 재정의합니다. GLFrontBufferedRenderer는 콜백을 사용하여 데이터를 최대한 빠르게 렌더링합니다. 있습니다.

val callback = object: GLFrontBufferedRenderer.Callback<DATA_TYPE> {
   override fun onDrawFrontBufferedLayer(
       eglManager: EGLManager,
       bufferInfo: BufferInfo,
       transform: FloatArray,
       param: DATA_TYPE
   ) {
       // OpenGL for front buffer, short, affecting small area of the screen.
   }
   override fun onDrawMultiDoubleBufferedLayer(
       eglManager: EGLManager,
       bufferInfo: BufferInfo,
       transform: FloatArray,
       params: Collection<DATA_TYPE>
   ) {
       // OpenGL full scene rendering.
   }
}
GLFrontBufferedRenderer 인스턴스 선언

다음을 제공하여 GLFrontBufferedRenderer 준비 SurfaceView 및 사용할 수 있습니다. GLFrontBufferedRenderer는 렌더링을 최적화합니다. 콜백을 사용하여 전면 및 이중 버퍼에 전달합니다.

var glFrontBufferRenderer = GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks)
렌더링

renderFrontBufferedLayer() 드림 메서드를 호출하여 onDrawFrontBufferedLayer() 콜백을 트리거합니다.

이중 버퍼 렌더링은 commit() 드림 함수를 사용하여 onDrawMultiDoubleBufferedLayer() 콜백을 트리거합니다.

다음 예에서는 프로세스가 전면 버퍼로 렌더링됩니다. 즉, 사용자가 화면에 그리기를 시작하고 (ACTION_DOWN) 이동할 때 렌더링됩니다. 포인터를 반환합니다 (ACTION_MOVE). 프로세스가 이중 버퍼로 렌더링됨 포인터가 화면 표면을 벗어날 때 (ACTION_UP).

이때 requestUnbufferedDispatch() 드림 입력 시스템이 모션 이벤트를 일괄 처리하지 않고 즉시 사용할 수 있습니다.

when (motionEvent.action) {
   MotionEvent.ACTION_DOWN -> {
       // Deliver input events as soon as they arrive.
       view.requestUnbufferedDispatch(motionEvent)
       // Pointer is in contact with the screen.
       glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE)
   }
   MotionEvent.ACTION_MOVE -> {
       // Pointer is moving.
       glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE)
   }
   MotionEvent.ACTION_UP -> {
       // Pointer is not in contact in the screen.
       glFrontBufferRenderer.commit()
   }
   MotionEvent.CANCEL -> {
       // Cancel front buffer; remove last motion set from the screen.
       glFrontBufferRenderer.cancel()
   }
}

렌더링 권장사항 및 금지사항

✓ 권장사항

화면의 작은 부분, 필기, 그리기, 스케치

✗ 부적절한 예

전체 화면 업데이트, 화면 이동, 확대/축소. 테어링이 발생할 수 있습니다.

테어링

테어링은 화면 버퍼가 실행되는 동안 화면을 새로고침할 때 발생합니다. 동시에 수정됩니다. 화면의 일부에는 새 데이터가 표시되고 다른 일부에는 새 데이터가 표시됩니다. 이전 데이터가 표시됩니다.

Android 이미지의 상단과 하단이 화면 새로고침 시의 테어링으로 인해 잘못 정렬됩니다.
그림 11. 화면을 새로 고칠 때 상단에서 하단으로 발생한 테어링

모션 예측

Jetpack 모션 예측 라이브러리를 사용하면 사용자의 스트로크 경로를 추정하여 체감 지연 시간을 렌더러를 가리키게 합니다.

모션 예측 라이브러리는 실제 사용자 입력을 MotionEvent 객체로 가져옵니다. 객체에는 x 및 y 좌표, 압력 및 시간에 대한 정보가 포함되어 있습니다. 모션 예측기에서 이를 활용하여 미래의 MotionEvent을(를) 예측합니다. 객체입니다.

예측된 MotionEvent 객체는 추정치일 뿐입니다. 예측된 이벤트로 인해 인지된 지연 시간이지만 예측된 데이터는 실제 MotionEvent로 대체해야 함 데이터를 전송합니다.

모션 예측 라이브러리는 Android 4.4 (API 수준 19)부터 사용할 수 있으며 이상 및 Android 9 (API 수준 28) 이상을 실행하는 ChromeOS 기기에서 지원됩니다.

지연 시간으로 인해 렌더링된 획이 스타일러스 위치보다 뒤처집니다. 획과 스타일러스 사이의 간격은 예측 포인트로 채워집니다. 남은 간격이 인지된 지연 시간입니다.
그림 12. 모션 예측으로 줄어든 지연 시간

종속 항목

모션 예측 라이브러리는 예측 구현을 제공합니다. 이 라이브러리는 앱의 모듈 build.gradle 파일에 종속 항목으로 추가됩니다.

dependencies {
    implementation "androidx.input:input-motionprediction:1.0.0-beta01"
}

구현

모션 예측 라이브러리에는 MotionEventPredictor 드림 인터페이스에 포함되어 있으며, 이 클래스는 다음 메서드를 정의합니다.

  • record(): 사용자 작업의 기록으로 MotionEvent 객체 저장
  • predict(): 예측된 MotionEvent를 반환합니다.
MotionEventPredictor의 인스턴스를 선언합니다.
var motionEventPredictor = MotionEventPredictor.newInstance(view)
예측기에 데이터 제공
motionEventPredictor.record(motionEvent)
예측

when (motionEvent.action) {
   MotionEvent.ACTION_MOVE -> {
       val predictedMotionEvent = motionEventPredictor?.predict()
       if(predictedMotionEvent != null) {
            // use predicted MotionEvent to inject a new artificial point
       }
   }
}

모션 예측의 권장사항 및 금지사항

✓ 권장사항

새로운 예측 포인트가 추가되면 예측 포인트를 삭제합니다.

✗ 부적절한 예

최종 렌더링에는 예측 포인트를 사용하지 않습니다.

메모 작성 앱

ChromeOS를 사용하면 앱에서 일부 메모 작성 작업을 선언할 수 있습니다.

ChromeOS에서 앱을 메모 작성 앱으로 등록하려면 입력 호환성을 참고하세요.

Android에서 앱을 메모 작성 앱으로 등록하려면 메모 만들기를 참조하세요. 앱을 엽니다.

Android 14 (API 수준 34)에서는 ACTION_CREATE_NOTE 드림 인텐트를 사용하여 앱이 도어락에서 메모 작성 활동을 시작할 수 있게 해줍니다. 화면

ML Kit를 통한 디지털 잉크 인식

ML Kit 디지털 잉크로 인식, 앱이 수백 개의 디지털 표면에서 필기 텍스트를 인식할 수 있습니다. 있습니다. 스케치를 분류할 수도 있습니다.

ML Kit는 Ink.Stroke.Builder 드림 머신러닝 모델에서 처리할 수 있는 Ink 객체를 만드는 클래스 필기 입력을 텍스트로 변환할 수 있습니다.

모델은 필기 인식 외에도 동작 삭제할 수 있습니다.

디지털 잉크 인식 를 참조하세요.

추가 리소스

개발자 가이드

Codelab