为了实现最佳绘制性能,请使用 InProgressStrokesView
类的 startStroke()
、addToStroke()
和 finishStroke()
方法,并将 MotionEvent
对象作为输入传递。
设置界面组件
将
AndroidView
可组合项纳入到绘制可组合函数中。@Composable fun DrawingView() { Box(modifier = Modifier.fillMaxSize()) { AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> val rootView = FrameLayout(context) //... }, ) { } } }
实例化 InProgressStrokesView
在 activity 的
onCreate()
方法中,获取对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 }, ) {} } }
处理轻触事件
界面组件已建立完毕,现在您可以根据触摸事件发起绘制操作了。
MotionEvent
操作InProgressStrokesView
方法说明
开始绘制笔触
继续渲染笔画
最终渲染笔触
实现防手掌误触功能;取消笔触
@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 }, ) { } } }
处理完成的笔画
调用
finishStroke()
后,系统会将笔画标记为已完成。不过,最终确定流程并非瞬间完成。在调用finishStroke()
后,系统会立即处理笔触,并在没有其他笔触正在处理时,让应用能够访问该笔触。这样可确保在将笔触作为已完成的操作传递给客户端之前,所有绘制操作均已完成。如需检索已完成的笔画,您有以下两种方法可选:
- 在 activity 或 ViewModel 中实现
InProgressStrokesFinishedListener
接口,并使用addFinishedStrokesListener
将监听器注册到InProgressStrokesView
。 - 使用
InProgressStrokesView
的getFinishedStrokes()
方法直接获取所有已完成的笔画。
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
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) } } } }
- 在 activity 或 ViewModel 中实现