绘制笔触

为了获得最佳绘图性能,请使用 startStroke()addToStroke()finishStroke() InProgressStrokesView 类的方法,并将 MotionEvent 对象作为输入。

  1. 设置 UI 组件

    AndroidView 可组合项合并到您的绘图可组合函数中。

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

  2. 实例化 InProgressStrokesView

    在您的 activity 的 [onCreate()][ink-draw-include6] 方法中,获取对 InProgressStrokesView 的引用,并为管理用户输入建立触摸侦听器。

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

  3. 处理触摸事件

    建立 UI 组件后,您现在可以根据触摸事件启动绘图。

    MotionEvent 动作

    InProgressStrokesView 方法

    描述

    ACTION_DOWN

    startStroke()

    开始笔划渲染

    ACTION_MOVE

    addToStroke()

    继续渲染笔划

    ACTION_UP

    finishStroke()

    完成笔划渲染

    ACTION_CANCELFLAG_CANCELED

    cancelStroke()

    实现手掌拒绝;取消笔划

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

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

    检索到已完成的笔划后,您可以使用 CanvasStrokesRenderer 将它们提交到屏幕。

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