最適な描画パフォーマンスを実現するには、InProgressStrokesView
クラスの startStroke()
、addToStroke()
、finishStroke()
メソッドを使用し、MotionEvent
オブジェクトを入力として渡します。
UI コンポーネントをセットアップする
AndroidView
コンポーザブルを描画のコンポーズ可能な関数に組み込みます。@Composable fun DrawingView() { Box(modifier = Modifier.fillMaxSize()) { AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> val rootView = FrameLayout(context) //... }, ) { } } }
InProgressStrokesView をインスタンス化する
アクティビティの
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 }, ) {} } }
タッチイベントを処理する
UI コンポーネントが確立されたので、タッチイベントに基づいて描画を開始できるようになりました。
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()
が呼び出された直後に完全に処理され、アプリがストロークにアクセスできるようになります(特に、他のストロークが進行していない場合)。これにより、ストロークが「finished」としてクライアントに引き渡される前に、すべての描画オペレーションが終了するようにできます。完了したストロークを取得するには、次の 2 つの方法があります。
- アクティビティまたは 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) } } } }
- アクティビティまたは ViewModel 内に