减小Compose中可滚动选项卡之间的间距。

huangapple go评论69阅读模式
英文:

Reduce Spacing between Scrollable tabs in compose

问题

我正在尝试创建一个动画,其中有一个可滚动的组件,可以横向滚动。类似于下面的示例:

我考虑使用可滚动选项卡,并且在某种程度上它确实有效,只是我仍在努力找出如何减小上面gif中看到的裁剪项之间的间距。

我尝试过的内容:

@Composable
fun CropBar(onCropClicked: (Int) -> Unit) {
    // ... (代码太长,省略部分)
}

结果:这段代码只是一个粗略的示例。

期望结果:我应该能够控制选项卡项之间的间距。我不仅寻求使用可滚动选项卡的解决方案。事实上,任何可滚动组件,其中选定的项目具有背景并将背景过渡到新选定的项目都可以。我考虑使用类似于Row的东西,具有图像的drawBehind,位于偏移位置,然后获取点击项的位置并将背景移动到所选项目。还有其他解决方案或想法吗?

如果有帮助的话,可以查看这个链接:https://issuetracker.google.com/issues/234942462

注意:我使用uiautomaterviewer检查了plantix应用程序。他们使用了自定义的水平滚动视图和帧布局。曲线是使用三次贝塞尔曲线的自定义路径。我猜他们计算了点击的裁剪或边界的偏移量,然后将背景视图移动到某个偏移量。

英文:

I am trying to create a animation where there is scrollable component that scrolls horizontally. Something like

减小Compose中可滚动选项卡之间的间距。

I thought of using Scrollable tabs and it works to some extent except, I am still figuring out how to reduce space between the crop items that you see in the above gif

What I have tried?

@Composable
fun CropBar(onCropClicked: (Int) -> Unit) {
    var selectedIndex by remember { mutableStateOf(0) }
    val pages = listOf("kotlin", "java", "c#", "php", "golang","A","B","C")
    val colors = listOf(Color.Yellow, Color.Red, Color.White, Color.Blue, Color.Magenta)

    val indicator = @Composable { tabPositions: List<TabPosition> ->
        val color = when (selectedIndex) {
            0 -> colors[0]
            1 -> colors[1]
            2 -> colors[2]
            3 -> colors[3]
            else -> colors[4]
        }
        CustomIndicator(tabPositions = tabPositions, selectedIndex = selectedIndex, color)
    }
    ScrollableTabRow(
        modifier = Modifier
            .fillMaxWidth()
            .height(58.dp),
        selectedTabIndex = selectedIndex,
        containerColor = Color(0xFF03753C),
        indicator = indicator,
        edgePadding = 0.dp,
        divider = {
        },

        ) {
        pages.forEachIndexed { index, title ->

            Tab(
                modifier = Modifier
                    .height(58.dp)
                    .width(74.dp)
                    .zIndex(2f),
                selected = selectedIndex == index,
                onClick = {
                    selectedIndex = index
                    onCropClicked(index)
                },
                interactionSource = NoRippleInteractionSource()
            ) {

                SampleImage(selectedIndex)
            }
        }
    }

}

@Composable
private fun CustomIndicator(tabPositions: List<TabPosition>, selectedIndex: Int, color: Color) {

    val transition = updateTransition(selectedIndex, label = "transition")

    val indicatorStart by transition.animateDp(
        transitionSpec = {
            tween(
                durationMillis = 500,
                easing = LinearOutSlowInEasing
            )
        },
        label = ""
    ) {
        tabPositions[it].left
    }

    val indicatorEnd by transition.animateDp(
        transitionSpec = {
            tween(
                durationMillis = 500,
                easing = LinearOutSlowInEasing
            )
        },
        label = "",
    ) {
        tabPositions[it].right
    }
    Box(
        Modifier
            .padding(top = 8.dp)
            .offset(x = indicatorStart)
            .wrapContentSize(align = Alignment.BottomStart)
            .width(indicatorEnd - indicatorStart)
            .paint(
                // Replace with your image id
                painterResource(id = R.drawable.ic_test), // some background vector drawable image
                contentScale = ContentScale.FillWidth,
                colorFilter = ColorFilter.tint(color) // for tinting
            )
            .zIndex(1f)
    )
}

@Composable
fun SampleImage(selectedIndex: Int) {

    BoxWithConstraints(
        modifier = Modifier,
    ) {
        Image(
            modifier = Modifier
                .padding(top = 8.dp)
                .width(42.dp)
                .height(42.dp)
                .align(Alignment.BottomCenter),
            painter = painterResource(id = R.drawable.ic_img_round),
            contentDescription = "Image"
        )

        if(selectedIndex == 1) {
            Text(
                text = "180 Days",
                fontSize = 8.sp,
                modifier = Modifier
                    .align(Alignment.BottomCenter)
                    .padding(top = 18.dp)
                    .width(42.dp)
                    .clip(RoundedCornerShape(10.dp))
                    .background(Color.Gray)
                    .graphicsLayer {
                        translationX = 5f
                    }
            )
        }
    }
}

class NoRippleInteractionSource : MutableInteractionSource {
    override val interactions: Flow<Interaction> = emptyFlow()
    override suspend fun emit(interaction: Interaction) {}
    override fun tryEmit(interaction: Interaction) = true
}

Result : The code is just a rough sample.

减小Compose中可滚动选项卡之间的间距。

Desired Result : I should be able to control the spacing between tab items. I am not looking for solution using only scrollable tabs. In fact any scrollable component with selected item having a background and transitioning the background to new selected item is okay. I thought of using something like Row with a drawBehind of Image at a offset and then get the clicked item position and move the background to selected Items. Any other solution or ideas?

Just in case it helps : https://issuetracker.google.com/issues/234942462

Note: I check with uiautomaterviewer the plantix app. They use a a custom horizontall scrollview and they use a framelayout. The curves are custom path using cubic bezier curve. I guess the calculate offset of clicked crop or bounds and then move the background view to and from a certain offset.

答案1

得分: 3

以下是您要翻译的代码部分:

private val ScrollableTabRowMinimumTabWidth = 90.dp

但是可以通过复制粘贴ScrollableTabRow源代码并更改此值或不使用具有最小宽度的约束来更新此值

顶部的一个是默认宽度的底部的一个是我更改了最小宽度可以测量为0.dp这意味着它可以使用0到最大值之间的任何值来测量

结果

[![enter image description here][1]][1]

演示

@Preview
@Composable
private fun Test() {
    CropBar() {

    }
}

@Composable
fun CropBar(onCropClicked: (Int) -> Unit) {
    Column {

        Spacer(modifier = Modifier.height(20.dp))
        var selectedIndex by remember { mutableStateOf(0) }
        val pages = listOf("kotlin", "java", "c#", "php", "golang", "A", "B", "C")
        val colors = listOf(Color.Yellow, Color.Red, Color.White, Color.Blue, Color.Magenta)

        val indicator = @Composable { tabPositions: List<TabPosition> ->
            val color = when (selectedIndex) {
                0 -> colors[0]
                1 -> colors[1]
                2 -> colors[2]
                3 -> colors[3]
                else -> colors[4]
            }
            CustomIndicator(tabPositions = tabPositions, selectedIndex = selectedIndex, color)
        }
        MyScrollableTabRow(
            modifier = Modifier
                .fillMaxWidth()
                .height(58.dp),
            selectedTabIndex = selectedIndex,
            backgroundColor = Color(0xFF03753C),
            indicator = indicator,
            edgePadding = 0.dp,
            divider = {
            },

        ) {
            pages.forEachIndexed { index, title ->

                Tab(
                    modifier = Modifier
                        .height(58.dp)
                        .width(74.dp)
                        .zIndex(2f),
                    selected = selectedIndex == index,
                    onClick = {
                        selectedIndex = index
                        onCropClicked(index)
                    },
                    interactionSource = NoRippleInteractionSource()
                ) {

                    SampleImage(selectedIndex)
                }
            }
        }

        Spacer(modifier = Modifier.height(20.dp))

        MyScrollableTabRow(
            modifier = Modifier
                .fillMaxWidth()
                .height(58.dp),
            selectedTabIndex = selectedIndex,
            backgroundColor = Color(0xFF03753C),
            indicator = indicator,
            minItemWidth = 0.dp,
            edgePadding = 0.dp,
            divider = {
            },

        ) {
            pages.forEachIndexed { index, title ->

                Tab(
                    modifier = Modifier
                        .height(58.dp)
                        .width(74.dp)
                        .zIndex(2f),
                    selected = selectedIndex == index,
                    onClick = {
                        selectedIndex = index
                        onCropClicked(index)
                    },
                    interactionSource = NoRippleInteractionSource()
                ) {

                    SampleImage(selectedIndex)
                }
            }
        }
    }
}

// 其他代码部分已省略

请注意,代码中包含一些HTML实体,如&quot;,这些应该在使用代码时转义或修复。

英文:

Unfortunately, minumum width tabs are measured with is a fixed value

private val ScrollableTabRowMinimumTabWidth = 90.dp

but this can be updated by copy pasting ScrollableTabRow source code and changing this or not using a Constraints with minimum width.

The one on top is with default width and for the one at the bottom i changed minimum width a Measurable can be measured to 0.dp

which means it can be measured with any value between 0-and max

Result

减小Compose中可滚动选项卡之间的间距。

Demo

@Preview
@Composable
private fun Test() {
CropBar() {
}
}
@Composable
fun CropBar(onCropClicked: (Int) -&gt; Unit) {
Column {
Spacer(modifier = Modifier.height(20.dp))
var selectedIndex by remember { mutableStateOf(0) }
val pages = listOf(&quot;kotlin&quot;, &quot;java&quot;, &quot;c#&quot;, &quot;php&quot;, &quot;golang&quot;, &quot;A&quot;, &quot;B&quot;, &quot;C&quot;)
val colors = listOf(Color.Yellow, Color.Red, Color.White, Color.Blue, Color.Magenta)
val indicator = @Composable { tabPositions: List&lt;TabPosition&gt; -&gt;
val color = when (selectedIndex) {
0 -&gt; colors[0]
1 -&gt; colors[1]
2 -&gt; colors[2]
3 -&gt; colors[3]
else -&gt; colors[4]
}
CustomIndicator(tabPositions = tabPositions, selectedIndex = selectedIndex, color)
}
MyScrollableTabRow(
modifier = Modifier
.fillMaxWidth()
.height(58.dp),
selectedTabIndex = selectedIndex,
backgroundColor = Color(0xFF03753C),
indicator = indicator,
edgePadding = 0.dp,
divider = {
},
) {
pages.forEachIndexed { index, title -&gt;
Tab(
modifier = Modifier
.height(58.dp)
.width(74.dp)
.zIndex(2f),
selected = selectedIndex == index,
onClick = {
selectedIndex = index
onCropClicked(index)
},
interactionSource = NoRippleInteractionSource()
) {
SampleImage(selectedIndex)
}
}
}
Spacer(modifier = Modifier.height(20.dp))
MyScrollableTabRow(
modifier = Modifier
.fillMaxWidth()
.height(58.dp),
selectedTabIndex = selectedIndex,
backgroundColor = Color(0xFF03753C),
indicator = indicator,
minItemWidth = 0.dp,
edgePadding = 0.dp,
divider = {
},
) {
pages.forEachIndexed { index, title -&gt;
Tab(
modifier = Modifier
.height(58.dp)
.width(74.dp)
.zIndex(2f),
selected = selectedIndex == index,
onClick = {
selectedIndex = index
onCropClicked(index)
},
interactionSource = NoRippleInteractionSource()
) {
SampleImage(selectedIndex)
}
}
}
}
}

Implementation

@Composable
@UiComposable
fun MyScrollableTabRow(
selectedTabIndex: Int,
modifier: Modifier = Modifier,
minItemWidth:Dp =ScrollableTabRowMinimumTabWidth,
backgroundColor: Color = MaterialTheme.colors.primarySurface,
contentColor: Color = contentColorFor(backgroundColor),
edgePadding: Dp = TabRowDefaults.ScrollableTabRowPadding,
indicator: @Composable @UiComposable
(tabPositions: List&lt;TabPosition&gt;) -&gt; Unit = @Composable { tabPositions -&gt;
TabRowDefaults.Indicator(
Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
)
},
divider: @Composable @UiComposable () -&gt; Unit =
@Composable {
TabRowDefaults.Divider()
},
tabs: @Composable @UiComposable () -&gt; Unit
) {
Surface(
modifier = modifier,
color = backgroundColor,
contentColor = contentColor
) {
val scrollState = rememberScrollState()
val coroutineScope = rememberCoroutineScope()
val scrollableTabData = remember(scrollState, coroutineScope) {
ScrollableTabData(
scrollState = scrollState,
coroutineScope = coroutineScope
)
}
SubcomposeLayout(
Modifier.fillMaxWidth()
.wrapContentSize(align = Alignment.CenterStart)
.horizontalScroll(scrollState)
.selectableGroup()
.clipToBounds()
) { constraints -&gt;
// &#128293; Change this to 0 or
val minTabWidth = minItemWidth.roundToPx()
val padding = edgePadding.roundToPx()
// &#128293;or use constraints to measure each tab with its own width or
// a another value instead of them having at least 90.dp
val tabConstraints = constraints.copy(minWidth = minTabWidth)
val tabPlaceables = subcompose(com.smarttoolfactory.tutorial1_1basics.chapter6_graphics.TabSlots.Tabs, tabs)
.map { it.measure(tabConstraints) }
var layoutWidth = padding * 2
var layoutHeight = 0
tabPlaceables.forEach {
layoutWidth += it.width
layoutHeight = maxOf(layoutHeight, it.height)
}
// Position the children.
layout(layoutWidth, layoutHeight) {
// Place the tabs
val tabPositions = mutableListOf&lt;TabPosition&gt;()
var left = padding
tabPlaceables.forEach {
it.placeRelative(left, 0)
tabPositions.add(TabPosition(left = left.toDp(), width = it.width.toDp()))
left += it.width
}
// The divider is measured with its own height, and width equal to the total width
// of the tab row, and then placed on top of the tabs.
subcompose(com.smarttoolfactory.tutorial1_1basics.chapter6_graphics.TabSlots.Divider, divider).forEach {
val placeable = it.measure(
constraints.copy(
minHeight = 0,
minWidth = layoutWidth,
maxWidth = layoutWidth
)
)
placeable.placeRelative(0, layoutHeight - placeable.height)
}
// The indicator container is measured to fill the entire space occupied by the tab
// row, and then placed on top of the divider.
subcompose(com.smarttoolfactory.tutorial1_1basics.chapter6_graphics.TabSlots.Indicator) {
indicator(tabPositions)
}.forEach {
it.measure(Constraints.fixed(layoutWidth, layoutHeight)).placeRelative(0, 0)
}
scrollableTabData.onLaidOut(
density = this@SubcomposeLayout,
edgeOffset = padding,
tabPositions = tabPositions,
selectedTab = selectedTabIndex
)
}
}
}
}
@Immutable
class TabPosition internal constructor(val left: Dp, val width: Dp) {
val right: Dp get() = left + width
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is TabPosition) return false
if (left != other.left) return false
if (width != other.width) return false
return true
}
override fun hashCode(): Int {
var result = left.hashCode()
result = 31 * result + width.hashCode()
return result
}
override fun toString(): String {
return &quot;TabPosition(left=$left, right=$right, width=$width)&quot;
}
}
object TabRowDefaults {
/**
* Default [Divider], which will be positioned at the bottom of the [TabRow], underneath the
* indicator.
*
* @param modifier modifier for the divider&#39;s layout
* @param thickness thickness of the divider
* @param color color of the divider
*/
@Composable
fun Divider(
modifier: Modifier = Modifier,
thickness: Dp = DividerThickness,
color: Color = LocalContentColor.current.copy(alpha = DividerOpacity)
) {
androidx.compose.material.Divider(modifier = modifier, thickness = thickness, color = color)
}
/**
* Default indicator, which will be positioned at the bottom of the [TabRow], on top of the
* divider.
*
* @param modifier modifier for the indicator&#39;s layout
* @param height height of the indicator
* @param color color of the indicator
*/
@Composable
fun Indicator(
modifier: Modifier = Modifier,
height: Dp = IndicatorHeight,
color: Color = LocalContentColor.current
) {
Box(
modifier
.fillMaxWidth()
.height(height)
.background(color = color)
)
}
/**
* [Modifier] that takes up all the available width inside the [TabRow], and then animates
* the offset of the indicator it is applied to, depending on the [currentTabPosition].
*
* @param currentTabPosition [TabPosition] of the currently selected tab. This is used to
* calculate the offset of the indicator this modifier is applied to, as well as its width.
*/
fun Modifier.tabIndicatorOffset(
currentTabPosition: TabPosition
): Modifier = composed(
inspectorInfo = debugInspectorInfo {
name = &quot;tabIndicatorOffset&quot;
value = currentTabPosition
}
) {
val currentTabWidth by animateDpAsState(
targetValue = currentTabPosition.width,
animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing)
)
val indicatorOffset by animateDpAsState(
targetValue = currentTabPosition.left,
animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing)
)
fillMaxWidth()
.wrapContentSize(Alignment.BottomStart)
.offset(x = indicatorOffset)
.width(currentTabWidth)
}
/**
* Default opacity for the color of [Divider]
*/
const val DividerOpacity = 0.12f
/**
* Default thickness for [Divider]
*/
val DividerThickness = 1.dp
/**
* Default height for [Indicator]
*/
val IndicatorHeight = 2.dp
/**
* The default padding from the starting edge before a tab in a [ScrollableTabRow].
*/
val ScrollableTabRowPadding = 52.dp
}
private enum class TabSlots {
Tabs,
Divider,
Indicator
}
/**
* Class holding onto state needed for [ScrollableTabRow]
*/
private class ScrollableTabData(
private val scrollState: ScrollState,
private val coroutineScope: CoroutineScope
) {
private var selectedTab: Int? = null
fun onLaidOut(
density: Density,
edgeOffset: Int,
tabPositions: List&lt;TabPosition&gt;,
selectedTab: Int
) {
// Animate if the new tab is different from the old tab, or this is called for the first
// time (i.e selectedTab is `null`).
if (this.selectedTab != selectedTab) {
this.selectedTab = selectedTab
tabPositions.getOrNull(selectedTab)?.let {
// Scrolls to the tab with [tabPosition], trying to place it in the center of the
// screen or as close to the center as possible.
val calculatedOffset = it.calculateTabOffset(density, edgeOffset, tabPositions)
if (scrollState.value != calculatedOffset) {
coroutineScope.launch {
scrollState.animateScrollTo(
calculatedOffset,
animationSpec = ScrollableTabRowScrollSpec
)
}
}
}
}
}
/**
* @return the offset required to horizontally center the tab inside this TabRow.
* If the tab is at the start / end, and there is not enough space to fully centre the tab, this
* will just clamp to the min / max position given the max width.
*/
private fun TabPosition.calculateTabOffset(
density: Density,
edgeOffset: Int,
tabPositions: List&lt;TabPosition&gt;
): Int = with(density) {
val totalTabRowWidth = tabPositions.last().right.roundToPx() + edgeOffset
val visibleWidth = totalTabRowWidth - scrollState.maxValue
val tabOffset = left.roundToPx()
val scrollerCenter = visibleWidth / 2
val tabWidth = width.roundToPx()
val centeredTabOffset = tabOffset - (scrollerCenter - tabWidth / 2)
// How much space we have to scroll. If the visible width is &lt;= to the total width, then
// we have no space to scroll as everything is always visible.
val availableSpace = (totalTabRowWidth - visibleWidth).coerceAtLeast(0)
return centeredTabOffset.coerceIn(0, availableSpace)
}
}
private val ScrollableTabRowMinimumTabWidth = 90.dp
/**
* [AnimationSpec] used when scrolling to a tab that is not fully visible.
*/
private val ScrollableTabRowScrollSpec: AnimationSpec&lt;Float&gt; = tween(
durationMillis = 250,
easing = FastOutSlowInEasing
)

答案2

得分: 1

我可以通过设置行的背景并动画偏移背景来实现这一点。

接受的答案适用于标签,但使用lazy row,我可以根据需要间隔项目。这正是问题的GIF中显示的Plantix应用程序的工作原理。

@Composable
fun EquiRow() {
    val selectedIndex = remember { mutableStateOf(0) }
    val colors = listOf(
        Color.Magenta,
        Color.Red,
        Color.Green,
        Color.Yellow,
        Color.Magenta,
        Color.Black,
        Color.Red
    )

    val first = remember {
        mutableStateOf(true)
    }

    val index = remember {
        mutableStateOf(4)
    }

    val scrollState = rememberScrollState()

    val radius = with(LocalDensity.current) { 40.dp.toPx() }
    val initialX = if (index.value == 0) {
        with(LocalDensity.current) { 27.dp.toPx() }
    } else {
        with(LocalDensity.current) { ((index.value * 54.dp.toPx()) + (14.dp.toPx() * index.value) + (27.dp.toPx())) }
    }

    val initialY = with(LocalDensity.current) { 75.dp.toPx() }

    var offsetX by remember { mutableStateOf(initialX) }
    var offsetY by remember { mutableStateOf(initialY) }
    val offsetAnim = remember { Animatable(0f) }

    val scrollToPosition by remember { mutableStateOf(initialX) }

    val mapRemem = remember { mutableMapOf<Int, Offset>() }

    LaunchedEffect(key1 = offsetX) {
        offsetAnim.animateTo(
            targetValue = offsetX, animationSpec = tween(
                durationMillis = 500,
                easing = LinearEasing
            )
        )
    }

    val animValue = if (first.value) {
        first.value = false
        offsetX
    } else {
        offsetAnim.value
    }

    Row(
        modifier = Modifier
            .horizontalScroll(scrollState)
            .fillMaxWidth()
            .height(150.dp)
            .padding(start = 16.dp, end = 16.dp)
            .drawBehind {

                drawCircle(
                    color = Color.LightGray,
                    radius = radius,
                    center =
                    Offset(animValue, offsetY)
                )
            },
        horizontalArrangement = Arrangement.spacedBy(14.dp)
    ) {

        // scroll row only first time initially to the selected index
        LaunchedEffect(key1 = scrollToPosition) {
            scrollState.animateScrollTo(scrollToPosition.roundToInt())
        }

        colors.forEachIndexed { index, color ->

            LogCompositions(tag = "For Loop", msg = "Running")
            Box(
                modifier = Modifier
                    .align(Alignment.CenterVertically)
                    .width(54.dp)
                    .height(54.dp)
                    .clip(CircleShape)
                    .background(colors[index])
                    .onGloballyPositioned { layoutCoordinates ->
                        println("⚡⚡ XXXXXXXX : ${layoutCoordinates.positionInParent().x}")
                        mapRemem[index] = Offset(
                            layoutCoordinates.boundsInParent().center.x,
                            layoutCoordinates.boundsInParent().center.y
                        )
                    }
                    .clickable {
                        offsetX = mapRemem[index]?.x!!
                        offsetY = mapRemem[index]?.y!!
                        selectedIndex.value = index
                        println("⚡⚡ POSITION : $offsetX")
                    }
            )

        }

    }
}

结果:

点击这里查看gif

英文:

I was able to achieve this with setting a background to the row and animating the offset of the background.

The accepted answer works with tabs but using lazy row i can space the item with whatever padding i need. This is exactly how the plantix app works as shown in the gif of the question.

@Composable
fun EquiRow() {
val selectedIndex = remember { mutableStateOf(0) }
val colors = listOf(
Color.Magenta,
Color.Red,
Color.Green,
Color.Yellow,
Color.Magenta,
Color.Black,
Color.Red
)
val first = remember {
mutableStateOf(true)
}
val index = remember {
mutableStateOf(4)
}
val scrollState = rememberScrollState()
val radius = with(LocalDensity.current) { 40.dp.toPx() }
val initialX = if (index.value == 0) {
with(LocalDensity.current) { 27.dp.toPx() }
} else {
with(LocalDensity.current) { ((index.value * 54.dp.toPx()) + (14.dp.toPx() * index.value) + (27.dp.toPx())) }
}
val initialY = with(LocalDensity.current) { 75.dp.toPx() }
var offsetX by remember { mutableStateOf(initialX) }
var offsetY by remember { mutableStateOf(initialY) }
val offsetAnim = remember { Animatable(0f) }
val scrollToPosition by remember { mutableStateOf(initialX) }
val mapRemem = remember { mutableMapOf&lt;Int, Offset&gt;() }
LaunchedEffect(key1 = offsetX) {
offsetAnim.animateTo(
targetValue = offsetX, animationSpec = tween(
durationMillis = 500,
easing = LinearEasing
)
)
}
val animValue = if (first.value) {
first.value = false
offsetX
} else {
offsetAnim.value
}
Row(
modifier = Modifier
.horizontalScroll(scrollState)
.fillMaxWidth()
.height(150.dp)
.padding(start = 16.dp, end = 16.dp)
.drawBehind {
drawCircle(
color = Color.LightGray,
radius = radius,
center =
Offset(animValue, offsetY)
)
},
horizontalArrangement = Arrangement.spacedBy(14.dp)
) {
// scroll row only first time initially to the selected index
LaunchedEffect(key1 = scrollToPosition) {
scrollState.animateScrollTo(scrollToPosition.roundToInt())
}
colors.forEachIndexed { index, color -&gt;
LogCompositions(tag = &quot;For Loop&quot;, msg = &quot;Running&quot;)
Box(
modifier = Modifier
.align(Alignment.CenterVertically)
.width(54.dp)
.height(54.dp)
.clip(CircleShape)
.background(colors[index])
.onGloballyPositioned { layoutCoordinates -&gt;
println(&quot;&#128293;&#128293; XXXXXXXX : ${layoutCoordinates.positionInParent().x}&quot;)
mapRemem[index] = Offset(
layoutCoordinates.boundsInParent().center.x,
layoutCoordinates.boundsInParent().center.y
)
}
.clickable {
offsetX = mapRemem[index]?.x!!
offsetY = mapRemem[index]?.y!!
selectedIndex.value = index
println(&quot;&#128293;&#128293; POSITION : $offsetX&quot;)
}
)
}
}
}

Result

Clcik here to view the gif

huangapple
  • 本文由 发表于 2023年6月19日 12:23:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/76503595.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定