획 그리기

최적의 그리기 성능을 얻으려면 InProgressStrokesView 클래스의 startStroke(), addToStroke(), finishStroke() 메서드를 사용하여 MotionEvent 객체를 입력으로 전달합니다.

  1. UI 구성요소 설정

    InProgressStrokesView를 뷰 계층 구조에 통합합니다.

    <FrameLayout>
    
      <ScrollView
        android:id="@+id/my_content"
        android:width="match_parent"
        android:height="match_parent"
        >
        <!-- Your content here. -->
      </ScrollView>
    
      <androidx.ink.authoring.InProgressStrokesView
        android:id="@+id/in_progress_strokes_view"
        android:width="match_parent"
        android:height="match_parent"
        />
    
    </FrameLayout>
    

  2. InProgressStrokesView 인스턴스화

    활동이나 프래그먼트의 [onCreate()][ink-draw-include6] 메서드 내에서 InProgressStrokesView 참조를 가져오고 사용자 입력을 관리하기 위한 터치 리스너를 설정합니다.

    class MyActivity : View.OnTouchListener {
        private lateinit var contentView: ScrollView
        private lateinit var inProgressStrokesView: InProgressStrokesView
        private lateinit var predictor: MotionEventPredictor
    
        // ... other variables
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
          predictor = MotionEventPredictor.newInstance(contentView)
            contentView = findViewById(R.id.my_content)
            contentView.setOnTouchListener(touchListener)
            inProgressStrokesView = findViewById(R.id.in_progress_strokes_view)
        }
    
        // ... (touchListener implementation)
    }
    

  3. 터치 이벤트 처리

    UI 구성요소를 설정했으므로 이제 터치 이벤트를 기반으로 그리기를 시작할 수 있습니다.

    MotionEvent 작업

    InProgressStrokesView 메서드

    설명

    ACTION_DOWN

    startStroke()

    획 렌더링 시작

    ACTION_MOVE

    addToStroke()

    획 렌더링 계속

    ACTION_UP

    finishStroke()

    획 렌더링 완료

    ACTION_CANCEL 또는 FLAG_CANCELED

    cancelStroke()

    손바닥 움직임 무시 기능을 구현하고 획을 취소합니다.

    class MyActivity : View.OnTouchListener {
      private lateinit var contentView: ScrollView
      private lateinit var inProgressStrokesView: InProgressStrokesView
    
      private var pointerId = -1
      private var strokeId: InProgressStrokeId? = null
      private lateinit var predictor: MotionEventPredictor
    
      override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        contentView = findViewById(R.id.my_content)
        predictor = MotionEventPredictor.create(contentView)
        contentView.setOnTouchListener(touchListener)
        inProgressStrokesView = findViewById(R.id.in_progress_strokes_view)
      }
    
      private val touchListener = { view: View, event: MotionEvent ->
        predictor.record(event)
          when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
              // First pointer - treat it as inking.
              view.requestUnbufferedDispatch(event)
              val pointerIndex = event.actionIndex
              pointerIdToStrokeId[event.getPointerId(pointerIndex)] =
                inProgressStrokesView.startStroke(event, pointerId)
              return true
            }
            MotionEvent.ACTION_POINTER_DOWN -> {
              val stroke = strokeId ?: return false
              inProgressStrokesView.cancelStroke(stroke, event)
              strokeId = null
              pointerId = -1
              return false
            }
            MotionEvent.ACTION_MOVE -> {
              val predictedEvent = predictor.predict()
              try
              {
                for (pointerIndex in 0 until pointerCount) {
                  val strokeId =
                  pointerIdToStrokeId[event.getPointerId(pointerIndex)] ?: continue
                  inProgressStrokesView.addToStroke(event, pointerId, strokeId, predictedEvent)
                } finally {
                  predictedEvent?.recycle()
                }
              }
            }
            MotionEvent.ACTION_UP -> {
              val pointerIndex = event.actionIndex
              val strokeId =
                pointerIdToStrokeId[event.getPointerId(pointerIndex)] ?: return false
              inProgressStrokesView.finishStroke(event, pointerId, strokeId)
              return true
            }
            MotionEvent.ACTION_CANCEL -> {
              val pointerIndex = event.actionIndex
              val strokeId =
                pointerIdToStrokeId[event.getPointerId(pointerIndex)] ?: return false
              inProgressStrokesView.cancelStroke(strokeId, event)
              return true
            }
          }
        return false
      }
    }
    

  4. 완료된 획 처리

    finishStroke()를 호출하면 획이 완료로 표시됩니다. 하지만 완료 프로세스는 즉각적으로 이루어지지 않습니다. 획은 완전히 처리되고 finishStroke()이 호출된 직후, 특히 진행 중인 다른 획이 없는 경우 애플리케이션에서 액세스할 수 있게 됩니다. 이렇게 하면 획이 완료됨으로 클라이언트에 전달되기 전에 모든 그리기 작업이 완료됩니다.

    완료된 획을 검색하는 방법에는 두 가지가 있습니다.

    class MyActivity : ComponentActivity(), InProgressStrokesFinishedListener {
      ...
    
      private val finishedStrokesState = mutableStateOf(emptySet<Stroke>())
    
      override fun onCreate(savedInstanceState: Bundle?) {
        ...
        inProgressStrokesView.addFinishedStrokesListener(this)
      }
    
      // ... (handle touch events)
    
      @UiThread
      override fun onStrokesFinished(strokes: Map<InProgressStrokeId, Stroke>) {
        finishedStrokesState.value += strokes.values
        inProgressStrokesView.removeFinishedStrokes(strokes.keys)
      }
    }
    

    완성된 획을 가져온 후에는 ViewStrokeRendererCanvasStrokeRenderer 위에 빌드된 상위 수준의 추상화로 사용할 수 있습니다. 이렇게 하면 뷰 계층 구조 내의 렌더링 프로세스를 더욱 단순화할 수 있습니다.

    class DrawingView(context: Context) : View(context) {
      private val viewStrokeRenderer = ViewStrokeRenderer(myCanvasStrokeRenderer, this)
    
      override fun onDraw(canvas: Canvas) {
        viewStrokeRenderer.drawWithStrokes(canvas) { scope ->
          canvas.scale(myZoomLevel)
          canvas.rotate(myRotation)
          canvas.translate(myPanX, myPanY)
          scope.drawStroke(myStroke)
          // Draw other objects including more strokes, apply more transformations, ...
        }
      }
    }