Çizgi çizme

Optimum çizim performansı elde etmek için InProgressStrokesView sınıfının startStroke(), addToStroke() ve finishStroke() yöntemlerini kullanın. Bu yöntemlerde, giriş olarak MotionEvent nesneleri iletin.

  1. Kullanıcı arayüzü bileşenini ayarlama

    Çiziminizin composable işlevine bir AndroidView composable (composable) ekleyin.

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

  2. InProgressStrokesView'ı başlatma

    Etkinliğinizin onCreate() yönteminde InProgressStrokesView referansı alın ve kullanıcı girişini yönetmek için bir dokunma dinleyicisi oluşturun.

    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. Dokunma etkinliklerini işleme

    Kullanıcı arayüzü bileşenlerini oluşturduktan sonra dokunma etkinliklerine göre çizim başlatabilirsiniz.

    MotionEvent işlem

    InProgressStrokesView yöntemi

    Açıklama

    ACTION_DOWN

    startStroke()

    Çizgi oluşturmaya başlama

    ACTION_MOVE

    addToStroke()

    Çizgiyi oluşturmaya devam et

    ACTION_UP

    finishStroke()

    Çizgi oluşturma işlemini tamamlama

    ACTION_CANCEL veya FLAG_CANCELED

    cancelStroke()

    Avuç içi reddetme özelliğini uygulayın; çizgiyi iptal edin

    @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. Tamamlanmış kulaçları işleme

    finishStroke() çağrıldıktan sonra çizgi tamamlandı olarak işaretlenir. Ancak, tamamlama işlemi anında gerçekleşmez. Çizgi tamamen işlenir ve özellikle devam eden başka darbe olmadığında finishStroke() çağrıldıktan kısa bir süre sonra uygulamanız için erişilebilir hale gelir. Bu sayede, çizgi bitti olarak istemciye aktarılmadan önce tüm çizim işlemleri tamamlanır.

    Bitmiş vuruşları almak için iki seçeneğiniz vardır:

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

    Tamamlanan çizgileri aldıktan sonra, CanvasStrokesRenderer ekrana kaydetmektir.

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