@@ -26,8 +26,6 @@ import androidx.compose.foundation.lazy.LazyListScope
26
26
import androidx.compose.foundation.lazy.LazyListState
27
27
import androidx.compose.foundation.lazy.LazyRow
28
28
import androidx.compose.foundation.lazy.rememberLazyListState
29
- import androidx.compose.foundation.relocation.BringIntoViewResponder
30
- import androidx.compose.foundation.relocation.bringIntoViewResponder
31
29
import androidx.compose.foundation.shape.RoundedCornerShape
32
30
import androidx.compose.runtime.Composable
33
31
import androidx.compose.runtime.CompositionLocalProvider
@@ -42,7 +40,6 @@ import androidx.compose.runtime.mutableStateMapOf
42
40
import androidx.compose.runtime.mutableStateOf
43
41
import androidx.compose.runtime.remember
44
42
import androidx.compose.runtime.rememberCoroutineScope
45
- import androidx.compose.runtime.rememberUpdatedState
46
43
import androidx.compose.runtime.setValue
47
44
import androidx.compose.runtime.snapshotFlow
48
45
import androidx.compose.runtime.snapshots.Snapshot
@@ -60,11 +57,17 @@ import androidx.compose.ui.graphics.RectangleShape
60
57
import androidx.compose.ui.graphics.Shape
61
58
import androidx.compose.ui.graphics.SolidColor
62
59
import androidx.compose.ui.graphics.drawscope.Stroke
60
+ import androidx.compose.ui.layout.LayoutCoordinates
63
61
import androidx.compose.ui.layout.boundsInParent
64
62
import androidx.compose.ui.layout.onGloballyPositioned
65
63
import androidx.compose.ui.layout.onSizeChanged
64
+ import androidx.compose.ui.node.DelegatingNode
65
+ import androidx.compose.ui.node.ModifierNodeElement
66
+ import androidx.compose.ui.node.requireLayoutCoordinates
66
67
import androidx.compose.ui.platform.LocalDensity
67
68
import androidx.compose.ui.platform.LocalLayoutDirection
69
+ import androidx.compose.ui.relocation.BringIntoViewModifierNode
70
+ import androidx.compose.ui.relocation.bringIntoView
68
71
import androidx.compose.ui.unit.Density
69
72
import androidx.compose.ui.unit.Dp
70
73
import androidx.compose.ui.unit.DpSize
@@ -129,7 +132,14 @@ fun TabRow(
129
132
130
133
val selectedItem = remember {
131
134
derivedStateOf {
132
- state.layoutInfo.visibleItemsInfo.firstOrNull { it.key == selectedKey() }
135
+ val currentKey = selectedKey()
136
+ state.layoutInfo.visibleItemsInfo.firstOrNull { it.key == currentKey }
137
+ }
138
+ }
139
+ val selectedItemScrollOffset = remember(selectedItem, state) {
140
+ derivedStateOf {
141
+ val selectedItem = selectedItem.value ? : return @derivedStateOf 0
142
+ selectedItem.offset + state.firstVisibleItemScrollOffset - state.firstVisibleItemScrollOffset
133
143
}
134
144
}
135
145
Box (modifier = Modifier
@@ -140,7 +150,8 @@ fun TabRow(
140
150
borderColor = borderColor,
141
151
containerWidth = { containerWidth.value },
142
152
rowRect = { rowRect.value },
143
- selectedItem = { selectedItem.value }
153
+ selectedItem = { selectedItem.value },
154
+ selectedItemScrollOffset = { selectedItemScrollOffset.value }
144
155
)
145
156
)
146
157
Box (modifier = Modifier .widthIn(padding)) {
@@ -277,7 +288,6 @@ fun TabItem(
277
288
val direction = LocalLayoutDirection .current
278
289
val color = colors.schemeFor(targetInteractionSource.collectVisualState(false ))
279
290
val density = LocalDensity .current
280
- val selectedValue = rememberUpdatedState(selected)
281
291
val bottomRadius = FluentTheme .cornerRadius.control
282
292
val topRadius = FluentTheme .cornerRadius.overlay
283
293
Box (
@@ -299,11 +309,10 @@ fun TabItem(
299
309
Modifier
300
310
}
301
311
)
302
- .bringIntoViewResponder(
303
- remember(density, selectedValue, bottomRadius) {
304
- TabItemBringIntoViewResponder (density, bottomRadius) { selectedValue.value }
305
- }
306
- )
312
+ .then(TabItemBringIntoViewModifierNodeElement (
313
+ density = density,
314
+ bottomRadius = bottomRadius
315
+ ))
307
316
.clickable(
308
317
indication = null ,
309
318
interactionSource = targetInteractionSource
@@ -798,6 +807,7 @@ private fun Modifier.drawTabRowBorder(
798
807
containerWidth : () -> Int ,
799
808
rowRect : () -> Rect ,
800
809
selectedItem : () -> LazyListItemInfo ? ,
810
+ selectedItemScrollOffset : () -> Int ,
801
811
) = drawWithCache {
802
812
val path = Path ()
803
813
val strokeSizePx = StrokeSize .toPx()
@@ -809,7 +819,7 @@ private fun Modifier.drawTabRowBorder(
809
819
val itemPadding = bottomRadius.toPx()
810
820
if (currentItem != null ) {
811
821
val rowRectValue = rowRect()
812
- val currentItemOffset = (rowRectValue.left + currentItem.offset )
822
+ val currentItemOffset = (rowRectValue.left + selectedItemScrollOffset() )
813
823
lineTo(
814
824
(currentItemOffset).coerceIn(
815
825
rowRectValue.left,
@@ -885,27 +895,40 @@ private fun Modifier.drawTabViewItemBorder(
885
895
}
886
896
}
887
897
888
- @OptIn(ExperimentalFoundationApi ::class )
889
- @Stable
890
- private class TabItemBringIntoViewResponder (
891
- density : Density ,
892
- bottomRadius : Dp ,
893
- val selected : () -> Boolean ,
894
- ) : BringIntoViewResponder {
895
- val paddingSize = with (density) { bottomRadius.toPx() }
898
+ private data class TabItemBringIntoViewModifierNodeElement (
899
+ val density : Density ,
900
+ val bottomRadius : Dp ,
901
+ ): ModifierNodeElement<TabItemBringIntoViewModifierNode>() {
902
+ override fun create (): TabItemBringIntoViewModifierNode {
903
+ return TabItemBringIntoViewModifierNode (density, bottomRadius)
904
+ }
896
905
897
- override suspend fun bringChildIntoView (localRect : () -> Rect ? ) {}
906
+ override fun update (node : TabItemBringIntoViewModifierNode ) {
907
+ node.density = density
908
+ node.bottomRadius = bottomRadius
909
+ }
910
+ }
898
911
899
- override fun calculateRectForParent (localRect : Rect ): Rect {
900
- return Snapshot .withoutReadObservation {
901
- if (selected()) {
902
- localRect.copy(
912
+ @OptIn(ExperimentalFoundationApi ::class )
913
+ @Stable
914
+ private class TabItemBringIntoViewModifierNode (
915
+ var density : Density ,
916
+ var bottomRadius : Dp ,
917
+ ) : BringIntoViewModifierNode, DelegatingNode() {
918
+
919
+ override suspend fun bringIntoView (
920
+ childCoordinates : LayoutCoordinates ,
921
+ boundsProvider : () -> Rect ?
922
+ ) {
923
+ Snapshot .withoutReadObservation {
924
+ if (! childCoordinates.isAttached || ! isAttached) return @withoutReadObservation
925
+ val localRect = requireLayoutCoordinates().localBoundingBoxOf(childCoordinates)
926
+ val paddingSize = with (density) { bottomRadius.toPx() }
927
+ val targetRect = localRect.copy(
903
928
left = localRect.left - paddingSize,
904
929
right = localRect.right + paddingSize
905
930
)
906
- } else {
907
- localRect
908
- }
931
+ bringIntoView { targetRect }
909
932
}
910
933
}
911
934
}
0 commit comments