To achieve optimal drawing performance, use the
[`startStroke()`](https://developer.android.com/reference/kotlin/androidx/ink/authoring/InProgressStrokesView#startStroke(android.view.MotionEvent,kotlin.Int,androidx.ink.brush.Brush,android.graphics.Matrix,android.graphics.Matrix))(),
[`addToStroke()`](https://developer.android.com/reference/kotlin/androidx/ink/authoring/InProgressStrokesView#addToStroke(androidx.ink.strokes.StrokeInputBatch,androidx.ink.authoring.InProgressStrokeId,androidx.ink.strokes.StrokeInputBatch))(),
and
[`finishStroke()`](https://developer.android.com/reference/kotlin/androidx/ink/authoring/InProgressStrokesView#finishStroke(android.view.MotionEvent,kotlin.Int,androidx.ink.authoring.InProgressStrokeId))
methods of the
[`InProgressStrokesView`](https://developer.android.com/reference/kotlin/androidx/ink/authoring/InProgressStrokesView) class, passing
[`MotionEvent`](https://developer.android.com/reference/android/view/MotionEvent) objects as input.

1. **Set up UI component**


   Incorporate an [`AndroidView`](https://developer.android.com/reference/kotlin/androidx/compose/ui/viewinterop/package-summary#AndroidView(kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Function1))
   composable into your drawing composable function.


       @Composable
       fun DrawingView()
       {
         Box(modifier = Modifier.fillMaxSize()) {
           AndroidView(
             modifier = Modifier.fillMaxSize(),
             factory = { context ->
               val rootView = FrameLayout(context)
               //...
             },
           ) {

           }
         }
       }

   <br />

2. **Instantiate InProgressStrokesView**


   Within your activity's `onCreate()` method, obtain a reference to the
   [`InProgressStrokesView`](https://developer.android.com/reference/kotlin/androidx/ink/authoring/InProgressStrokesView) and establish a
   touch listener for managing user input.  

       class MainActivity : ComponentActivity(){
         private lateinit var inProgressStrokesView: InProgressStrokesView

         // ... other variables

         override fun onCreate(savedInstanceState: Bundle?) {
           super.onCreate(savedInstanceState)
           inProgressStrokesView = InProgressStrokesView(this)

           setContent {
             // ...
             DrawingView(inProgressStrokesView = inProgressStrokesView)
           }
         }
       }

       @Composable
       fun DrawingView(
         inProgressStrokesView: InProgressStrokesView,
       ) {

         Box(modifier = Modifier.fillMaxSize()) {
           AndroidView(
             modifier = Modifier.fillMaxSize(),
             factory = { context ->
               val rootView = FrameLayout(context)
               inProgressStrokesView.apply {
                 layoutParams =
                 FrameLayout.LayoutParams(
                   FrameLayout.LayoutParams.MATCH_PARENT,
                   FrameLayout.LayoutParams.MATCH_PARENT,
                 )
               }
               val predictor = MotionEventPredictor.newInstance(rootView)
               val touchListener =
               View.OnTouchListener { view, event ->
                 // ... (handle touch events)
               }

               rootView.setOnTouchListener(touchListener)
               rootView.addView(inProgressStrokesView)
               rootView
             },
           ) {}
         }
       }

   <br />

   | **Note:** Employing the [`MotionEventPredictor`](https://developer.android.com/reference/kotlin/androidx/input/motionprediction/MotionEventPredictor) interface improves stroke latency by predicting upcoming touch events. Use `MotionEventPredictor` for optimal performance.
3. **Handle touch events**

   Having established the UI components, you can now initiate drawing based on
   touch events.

   |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------|
   | **`MotionEvent` action**                                                                                                                                                                               | **`InProgressStrokesView` method**                                                                                                                                                                                                                | **Description**                             |
   | [`ACTION_DOWN`](https://developer.android.com/reference/android/view/MotionEvent#ACTION_DOWN)                                                                                                          | [`startStroke()`](https://developer.android.com/reference/kotlin/androidx/ink/authoring/InProgressStrokesView#startStroke(android.view.MotionEvent,kotlin.Int,androidx.ink.brush.Brush,android.graphics.Matrix,android.graphics.Matrix))          | Begin stroke rendering                      |
   | [`ACTION_MOVE`](https://developer.android.com/reference/android/view/MotionEvent#ACTION_MOVE)                                                                                                          | [`addToStroke()`](https://developer.android.com/reference/kotlin/androidx/ink/authoring/InProgressStrokesView#addToStroke(androidx.ink.strokes.StrokeInputBatch,androidx.ink.authoring.InProgressStrokeId,androidx.ink.strokes.StrokeInputBatch)) | Continue rendering the stroke               |
   | [`ACTION_UP`](https://developer.android.com/reference/android/view/MotionEvent#ACTION_UP)                                                                                                              | [`finishStroke()`](https://developer.android.com/reference/kotlin/androidx/ink/authoring/InProgressStrokesView#finishStroke(android.view.MotionEvent,kotlin.Int,androidx.ink.authoring.InProgressStrokeId))                                       | Finalize the stroke rendering               |
   | [`ACTION_CANCEL`](https://developer.android.com/reference/android/view/MotionEvent#ACTION_CANCEL) or [`FLAG_CANCELED`](https://developer.android.com/reference/android/view/MotionEvent#FLAG_CANCELED) | [`cancelStroke()`](https://developer.android.com/reference/kotlin/androidx/ink/authoring/InProgressStrokesView#cancelStroke(androidx.ink.authoring.InProgressStrokeId,android.view.MotionEvent))                                                  | Implement palm rejection; cancel the stroke |


       @SuppressLint("ClickableViewAccessibility")
       @Composable
       fun DrawingSurface(
           inProgressStrokesView: InProgressStrokesView
       ) {
           val currentPointerId = remember { mutableStateOf<Int?>(null) }
           val currentStrokeId = remember { mutableStateOf<InProgressStrokeId?>(null) }
           val defaultBrush = Brush.createWithColorIntArgb(
               family = StockBrushes.pressurePenLatest,
               colorIntArgb = Color.Black.toArgb(),
               size = 5F,
               epsilon = 0.1F
           )

           Box(modifier = Modifier.fillMaxSize()) {
               AndroidView(
                   modifier = Modifier.fillMaxSize(),
                   factory = { context ->
                       val rootView = FrameLayout(context)
                       inProgressStrokesView.apply {
                           layoutParams =
                               FrameLayout.LayoutParams(
                                   FrameLayout.LayoutParams.MATCH_PARENT,
                                   FrameLayout.LayoutParams.MATCH_PARENT,
                               )
                       }
                       val predictor = MotionEventPredictor.newInstance(rootView)
                       val touchListener =
                           View.OnTouchListener { view, event ->
                               predictor.record(event)
                               val predictedEvent = predictor.predict()

                               try {
                                   when (event.actionMasked) {
                                       MotionEvent.ACTION_DOWN -> {
                                           // First pointer - treat it as inking.
                                           view.requestUnbufferedDispatch(event)
                                           val pointerIndex = event.actionIndex
                                           val pointerId = event.getPointerId(pointerIndex)
                                           currentPointerId.value = pointerId
                                           currentStrokeId.value =
                                               inProgressStrokesView.startStroke(
                                                   event = event,
                                                   pointerId = pointerId,
                                                   brush = defaultBrush
                                               )
                                           true
                                       }

                                       MotionEvent.ACTION_MOVE -> {
                                           val pointerId = checkNotNull(currentPointerId.value)
                                           val strokeId = checkNotNull(currentStrokeId.value)

                                           for (pointerIndex in 0 until event.pointerCount) {
                                               if (event.getPointerId(pointerIndex) != pointerId) continue
                                               inProgressStrokesView.addToStroke(
                                                   event,
                                                   pointerId,
                                                   strokeId,
                                                   predictedEvent
                                               )
                                           }
                                           true
                                       }

                                       MotionEvent.ACTION_UP -> {
                                           val pointerIndex = event.actionIndex
                                           val pointerId = event.getPointerId(pointerIndex)
                                           check(pointerId == currentPointerId.value)
                                           val currentStrokeId = checkNotNull(currentStrokeId.value)
                                           inProgressStrokesView.finishStroke(
                                               event,
                                               pointerId,
                                               currentStrokeId
                                           )
                                           view.performClick()
                                           true
                                       }

                                       MotionEvent.ACTION_CANCEL -> {
                                           val pointerIndex = event.actionIndex
                                           val pointerId = event.getPointerId(pointerIndex)
                                           check(pointerId == currentPointerId.value)

                                           val currentStrokeId = checkNotNull(currentStrokeId.value)
                                           inProgressStrokesView.cancelStroke(currentStrokeId, event)
                                           true
                                       }

                                       else -> false
                                   }
                               } finally {
                                   predictedEvent?.recycle()
                               }

                           }
                       rootView.setOnTouchListener(touchListener)
                       rootView.addView(inProgressStrokesView)
                       rootView
                   },
               ) {

               }
           }
       }

   <br />

4. **Handle finished strokes**

   Upon calling
   [`finishStroke()`](https://developer.android.com/reference/kotlin/androidx/ink/authoring/InProgressStrokesView#finishStroke(android.view.MotionEvent,kotlin.Int,androidx.ink.authoring.InProgressStrokeId)),
   the stroke is marked for completion. However, the finalization process isn't
   instantaneous. The stroke is fully processed and becomes accessible
   to your application shortly after
   [`finishStroke()`](https://developer.android.com/reference/kotlin/androidx/ink/authoring/InProgressStrokesView#finishStroke(android.view.MotionEvent,kotlin.Int,androidx.ink.authoring.InProgressStrokeId))
   is called, specifically when there are no other strokes in progress. This
   ensures that all drawing operations are concluded before the stroke is handed
   off to the client as *finished*.

   To retrieve finished strokes, you have two options:
   - Implement the [`InProgressStrokesFinishedListener`](https://developer.android.com/reference/kotlin/androidx/ink/authoring/InProgressStrokesFinishedListener) interface within your activity or [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel), and register the listener with the `InProgressStrokesView` with [`addFinishedStrokesListener`](https://developer.android.com/reference/androidx/ink/authoring/InProgressStrokesView#addFinishedStrokesListener(androidx.ink.authoring.InProgressStrokesFinishedListener)).
   - Use the [`getFinishedStrokes()`](https://developer.android.com/reference/kotlin/androidx/ink/authoring/InProgressStrokesView#getFinishedStrokes()) method of [`InProgressStrokesView`](https://developer.android.com/reference/kotlin/androidx/ink/authoring/InProgressStrokesView) to obtain all finished strokes directly.

       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)
         }
       }


   Once you have retrieved the finished strokes, you can use
   [`CanvasStrokesRenderer`](https://developer.android.com/reference/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRenderer)
   to commit them to the screen.  

       class MainActivity : ComponentActivity(), InProgressStrokesFinishedListener {
         private lateinit var inProgressStrokesView: InProgressStrokesView
         private val finishedStrokesState = MutableState<emptySet<Stroke>()>

         override fun onCreate(savedInstanceState: Bundle?) {
           inProgressStrokesView = InProgressStrokesView(this)
           inProgressStrokesView.addFinishedStrokesListener(this)
           canvasStrokeRenderer = CanvasStrokeRenderer.create()
           //...
           DrawingSurface(
             inProgressStrokesView = inProgressStrokesView,
             canvasStrokeRenderer = canvasStrokeRenderer,
             finishedStrokesState = finishedStrokesState
           )
           //...
           }
         //...
       }

       @SuppressLint("ClickableViewAccessibility")
       @Composable
       fun DrawingSurface(
           inProgressStrokesView: InProgressStrokesView,
           finishedStrokesState: Set<Stroke>
       ) {
           val canvasStrokeRenderer = CanvasStrokeRenderer.create()
           //...

           Box(modifier = Modifier.fillMaxSize()) {
               AndroidView(
                   //...
               )

               Canvas(modifier = Modifier) {
                   val canvasTransform = Matrix()
                   drawContext.canvas.nativeCanvas.concat(canvasTransform)
                   val canvas = drawContext.canvas.nativeCanvas

                   finishedStrokesState.value.forEach { stroke ->
                       canvasStrokeRenderer.draw(stroke = stroke, canvas = canvas, strokeToScreenTransform = canvasTransform)
                   }
               }
           }
       }

   <br />