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.

   <br />

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

           }
         }
       }

   <br />

2. **Instantiate InProgressStrokesView**

   <br />

   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 |

   <br />

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

   <br />

   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 />