I am trying to create a animation where there is scrollable component that scrolls horizontally. Something like
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.
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.