Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Modifier.Nodeに移行してパフォーマンスを比べてみた

usuiat
December 13, 2023

 Modifier.Nodeに移行してパフォーマンスを比べてみた

usuiat

December 13, 2023
Tweet

More Decks by usuiat

Other Decks in Programming

Transcript

  1. usuiat fun Modifier.zoomable(): Modifier = composed { Modifier .onSizeChanged {

    size -> zoomState.setLayoutSize(size.toSize()) } .nestedScroll(connection, dispatcher) .pointerInput(zoomState) { detectTransformGestures( onGesture = { centroid, pan, zoom -> scope.launch { zoomState.applyGesture( pan = pan, zoom = zoom, position = centroid, ) } }, ) } .graphicsLayer { scaleX = zoomState.scale scaleY = zoomState.scale translationX = zoomState.offsetX translationY = zoomState.offsetY } } Modifier.zoomable (composed版)の構造 1) pointerInput()でジェスチャーを検出し、ZoomStateを変更 2) graphicsLayer()でZoomStateの状態をレイアウトに反映 4) nestedScroll()で上位のPagerなどにスクロールイベントを伝達 3) onSizeChanged()でコンポーネントのサイズを取得 (ドラッグ可能な範囲の算出に使う) Modifier.composed()を使用
  2. usuiat ZoomableNodeの実装方針 1) pointerInput()でジェスチャーを検出し、ZoomStateを変更 2) graphicsLayer()でZoomStateの状態をレイアウトに反映 4) nestedScroll()で上位のPagerなどにスクロールイベントを伝達 3) onSizeChanged()でコンポーネントのサイズを取得

    (ドラッグ可能な範囲の算出に使う) PointerInputModifierNodeを実装 LayoutModifierNodeを実装 nestedScrollModifierNodeに委任 private class ZoomableNode(): PointerInputModifierNode, LayoutModifierNode, DelegatingNode() { ... }
  3. usuiat private class ZoomableNode(): PointerInputModifierNode, DelegatingNode() { override fun onPointerEvent(

    pointerEvent: PointerEvent, pass: PointerEventPass, bounds: IntSize ) { pointerInputNode.onPointerEvent(pointerEvent, pass, bounds) } override fun onCancelPointerInput() { pointerInputNode.onCancelPointerInput() } val pointerInputNode = delegate(SuspendingPointerInputModifierNode { detectTransformGestures( onGesture = { centroid, pan, zoom -> coroutineScope.launch { zoomState.applyGesture(pan, zoom, centroid) } }, ) }) } 1) ジェスチャーを検出し、ZoomStateを変更 PointerInputModifierNodeのonPointerEvent()と onCancelPointerInput()をoverride pointerInputNodeの処理を呼び出す SuspendingPointerInputModifierNodeにはPointerInputScopeの 処理を渡せるので、Modifier.pointerInputに書いていた処理を ほぼそのまま書ける。 pointerInputNodeはSuspendingPointerInputModifierNode 実際の処理はSuspendingPointerInputModifierNodeに 委任する PointerInputModifierNodeを実装
  4. usuiat private class ZoomableNode(): LayoutModifierNode, DelegatingNode() { var measuredSize =

    Size.Zero override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val placeable = measurable.measure(constraints) measuredSize = IntSize(placeable.measuredWidth, placeable.measuredHeight).toSize() zoomState.setLayoutSize(measuredSize) return layout(placeable.width, placeable.height) { placeable.placeWithLayer(x = 0, y = 0) { scaleX = zoomState.scale scaleY = zoomState.scale translationX = zoomState.offsetX translationY = zoomState.offsetY } } } } 2) ZoomStateの状態をレイアウトに反映 3) コンポーネントのサイズを取得 MeasureScope.measure()をoverride placeableからコンポーネントのサイズを取得できる placeWithLayer()に、Modifier.graphicsLayer()の処理を そのまま書ける LayoutModifierNodeを実装
  5. usuiat private class ZoomableNode(): DelegatingNode() { val connection = object

    : NestedScrollConnection{} val dispatcher = NestedScrollDispatcher() init { delegate(nestedScrollModifierNode(connection, dispatcher)) } } 4) スクロールイベントを伝達 initブロックでnestedScrollModifierNodeを作成し、 delegateで委任する DelegatingNodeを継承
  6. usuiat 再コンポーズの処理時間を計測 @Composable fun Sample() { var offset by remember

    { mutableStateOf(0.dp) } Image( painter = painter, contentDescription = null, contentScale = ContentScale.Fit, modifier = Modifier .fillMaxSize() .offset(y = offset) .zoomable( zoomState = rememberZoomState(), onTap = { offset += 10.dp } ), ) } Modifier.Nodeでは、再コンポーズ時のパフォーマンスが改善しているはず! onTapでoffsetを変更することによって再コンポーズを発生させて、 Image()の実行時間を測定する。
  7. usuiat Modifier.composed版 10回平均 8.2ms 最大 20.78ms 最小 4.16ms materializeModifierの処理時間が Image()全体の半分程度を占めて

    いる。 materializeがいかに大変かとい う話は、この動画で説明されて います。 https://www.youtube.com/watch? v=BjGX2RftXsU Image()の処理時間