英文:
Jetpack Compose - Modify scroll behavior to hold focus on a fix position on screen
问题
使用Jetpack Compose创建一个面向电视平台的应用程序,我一直在尝试在Jetpack Compose中创建一个在屏幕上固定位置并能滚动的行,以下是在Google TV启动器上观察到的目标行为:
在这里,我们可以看到视图固定在屏幕的左侧,而整个行在其周围移动。
而下面是我的lazyrow组合的当前行为:
正如我们在这里所看到的,焦点在列表开始围绕其移动之前会一直移动到右侧或左侧。
是否有帮助使lazyrow的滚动行为像第一个示例中的那样?
英文:
Using jetpack compose to create an AP for TV platforms and I've been trying to create a row in jetpack compose which scrolls about the fix position on screen, here is the target behaviour as observed on google TV launcher:
Here we can see that the view is fixed on the lefthand side of the screen while the entire row moves around it
And how here is the current behaviour with my lazyrow composable:
As we can see here, the focus moves all the way to the right or the left before the list starts moving around it.
Any help in making the lazyrow scroll behave like in the first example?
答案1
得分: 4
你可以使用Compose for TV库中的TvLazyRow
来解决这个问题。它是androidx.tv.foundation.lazy.list
包的一部分。
要使用它,你可以使用接受PivotOffsets
类实例的pivotOffsets
参数。你可以传递两个参数:
parentFraction
定义了子元素的起始边缘相对于父元素的起始边缘的偏移量childFraction
定义了子元素的起始边缘相对于由parentFraction
定义的枢轴的偏移量
以下是将项目放置在中心的示例用法:
import androidx.tv.foundation.lazy.list.TvLazyRow
TvLazyRow(
pivotOffsets = PivotOffsets(0.5f, 0.5f),
horizontalArrangement = Arrangement.spacedBy(20.dp)
) {
items(10) {
Card(backgroundColor = Color.Red)
}
}
如果你想要类似Google TV启动器的外观,你可以将PivotOffsets
更新为以下内容:
import androidx.tv.foundation.lazy.list.TvLazyRow
TvLazyRow(
pivotOffsets = PivotOffsets(0.1f, 0f),
horizontalArrangement = Arrangement.spacedBy(20.dp)
) {
items(10) {
Card(backgroundColor = Color.Red)
}
}
英文:
You can make use of TvLazyRow
from the Compose for TV library which solves exactly this problem. It is part of the androidx.tv.foundation.lazy.list
package.
To use it, you can make use of the pivotOffsets
argument which accepts a PivotOffsets
class instance. To that, you can pass 2 arguments:
parentFraction
which defines the offset of the starting edge of the child element from the starting edge of the parent elementchildFraction
defines the offset of the starting edge of the child from the pivot defined by parentFraction
Following is the sample usage which places the item exactly at the center:
import androidx.tv.foundation.lazy.list.TvLazyRow
TvLazyRow(
pivotOffsets = PivotOffsets(0.5f, 0.5f),
horizontalArrangement = Arrangement.spacedBy(20.dp)
) {
items(10) {
Card(backgroundColor = Color.Red)
}
}
If you want the Google TV launcher like look, you can update the PivotOffsets
to the following:
import androidx.tv.foundation.lazy.list.TvLazyRow
TvLazyRow(
pivotOffsets = PivotOffsets(0.1f, 0f),
horizontalArrangement = Arrangement.spacedBy(20.dp)
) {
items(10) {
Card(backgroundColor = Color.Red)
}
}
答案2
得分: 1
// 我实际上只是计算第一个可见项的偏移是否超出视口的边界,如果是的话,就将惰性列表滚动到该项。
英文:
We can use the snap fling behavior that would snap at the middle item but the video you provided has snapping enabled on the first visible item, there's no property of snap fling that we could change to snap at the first item.
So I have implemented something that calculates and stop at the very first item and highlights it.
I did not see that you were implementing it for tv and I created a sample for mobile :___( .
If there is any tv specific api available for this behavior you should surely go for that, if not then you can have a look at my implementation.
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MainScreen(
modifier: Modifier
) {
val lazyRowState = rememberLazyListState()
val snappingLayout = remember(lazyRowState) { SnapLayoutInfoProvider(lazyRowState) }
val flingBehavior = rememberSnapFlingBehavior(snappingLayout)
Box(
modifier = modifier
.fillMaxSize()
) {
val firstVisibleItem by remember {
derivedStateOf {
lazyRowState.layoutInfo.visibleItemsInfo.firstOrNull()
}
}
val viewPortStartOffset by remember {
derivedStateOf {
lazyRowState.layoutInfo.viewportStartOffset
}
}
LaunchedEffect(key1 = firstVisibleItem ) {
firstVisibleItem?.let {
if(it.offset < viewPortStartOffset){
lazyRowState.animateScrollToItem(it.index)
}
}
}
LazyRow(
modifier = Modifier
.align(Alignment.Center)
.wrapContentSize(),
state = lazyRowState,
flingBehavior = flingBehavior
) {
items(50) { index ->
val isFirstItem by remember { derivedStateOf { lazyRowState.firstVisibleItemIndex == index} }
val scale by animateFloatAsState(targetValue = if(isFirstItem) 1.2f else 1f,
label = "scale animation"
)
Card(
modifier = Modifier
.padding(start = 30.dp, end = 10.dp)
.size(80.dp)
.scale(scale = scale)
.border(
if (isFirstItem) 2.dp else 0.dp,
if (isFirstItem) Color.White else Color.Transparent,
RoundedCornerShape(15.dp)
)
,
colors = CardDefaults.cardColors(Color.Blue),
shape = RoundedCornerShape(15.dp),
) {
Text(text = "$index")
}
}
}
}
}
I am actually just calculating if the offset of very first visible item goes out of the bounds of viewport and if it does then scrolling the lazy list to that item.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论