Để đạt được hiệu suất vẽ tối ưu, hãy sử dụng phương thức startStroke(), addToStroke() và finishStroke() của lớp InProgressStrokesView, truyền đối tượng MotionEvent làm dữ liệu đầu vào.
Thiết lập thành phần giao diện người dùng
Tích hợp thành phần kết hợp
AndroidViewvào hàm có khả năng kết hợp bản vẽ.@Composable fun DrawingView() { Box(modifier = Modifier.fillMaxSize()) { AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> val rootView = FrameLayout(context) //... }, ) { } } }Tạo thực thể InProgressStrokesView
Trong phương thức
onCreate()của hoạt động, hãy lấy thông tin tham chiếu đếnInProgressStrokesViewvà thiết lập trình nghe thao tác chạm để quản lý dữ liệu đầu vào của người dùng.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 }, ) {} } }Xử lý sự kiện chạm
Sau khi thiết lập các thành phần giao diện người dùng, giờ đây, bạn có thể bắt đầu vẽ dựa trên các sự kiện chạm.
Hành động
MotionEventPhương thức
InProgressStrokesViewMô tả
Bắt đầu kết xuất nét vẽ
Tiếp tục kết xuất nét vẽ
Hoàn tất quá trình kết xuất nét vẽ
Triển khai tính năng chống tì tay; huỷ thao tác vẽ
@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 }, ) { } } }Xử lý nét vẽ đã hoàn tất
Khi gọi
finishStroke(), nét vẽ được đánh dấu là hoàn tất. Tuy nhiên, quá trình hoàn tất không diễn ra ngay lập tức. Nét vẽ được xử lý hoàn toàn và ứng dụng của bạn có thể truy cập được ngay sau khi gọifinishStroke(), đặc biệt là khi không có nét vẽ nào khác đang diễn ra. Điều này đảm bảo rằng tất cả các thao tác vẽ đều được kết thúc trước khi nét vẽ được chuyển cho ứng dụng dưới dạng hoàn tất.Để truy xuất các nét vẽ đã hoàn tất, bạn có hai lựa chọn:
- Triển khai giao diện
InProgressStrokesFinishedListenertrong hoạt động hoặc ViewModel và đăng ký trình nghe vớiInProgressStrokesViewbằngaddFinishedStrokesListener. - Sử dụng phương thức
getFinishedStrokes()củaInProgressStrokesViewđể trực tiếp lấy tất cả các nét vẽ đã hoàn tất.
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) } }Sau khi truy xuất nét vẽ xong, bạn có thể sử dụng
CanvasStrokesRendererđưa chúng vào màn hình.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) } } } }- Triển khai giao diện