skill: add Android skills
This commit is contained in:
@@ -0,0 +1,394 @@
|
||||
When creating a Glimmer List component, refer to the following source code in
|
||||
`List.kt`:
|
||||
|
||||
|
||||
```kotlin
|
||||
/*
|
||||
* Copyright 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.xr.glimmer.list
|
||||
|
||||
import androidx.compose.foundation.OverscrollEffect
|
||||
import androidx.compose.foundation.gestures.FlingBehavior
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.lazy.layout.LazyLayout
|
||||
import androidx.compose.foundation.rememberOverscrollEffect
|
||||
import androidx.compose.foundation.scrollableArea
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.compose.ui.util.fastMap
|
||||
import androidx.xr.glimmer.GlimmerTheme
|
||||
import androidx.xr.glimmer.edgeScrim
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* This is a scrolling list component that only composes and lays out the currently visible items.
|
||||
* It is based on [androidx.compose.foundation.lazy.LazyColumn], but with extra functionality and
|
||||
* customized behavior required for Jetpack Compose Glimmer. For Jetpack Compose Glimmer
|
||||
* applications, it is recommended to use [VerticalList] instead of
|
||||
* [androidx.compose.foundation.lazy.LazyColumn], as it is specifically designed to provide seamless
|
||||
* focus-based navigation, visual scrim edge effects and support for focus-aware snap behavior.
|
||||
*
|
||||
* The [content] block defines a DSL which allows you to emit items of different types. For example,
|
||||
* you can use [ListScope.item] to add a single item and [ListScope.items] to add a list of items.
|
||||
*
|
||||
* See the other [VerticalList] overload for a variant with a title slot.
|
||||
*
|
||||
* @sample androidx.xr.glimmer.samples.VerticalListSample
|
||||
* @param modifier the modifier to apply to this layout.
|
||||
* @param state the state object to be used to control or observe the list's state.
|
||||
* @param contentPadding a padding around the whole content. This will add padding for the content
|
||||
* after it has been clipped, which is not possible via [modifier] param. You can use it to add a
|
||||
* padding before the first item or after the last one.
|
||||
* @param userScrollEnabled If user gestures are enabled.
|
||||
* @param overscrollEffect the [OverscrollEffect] that will be used to render overscroll for this
|
||||
* layout. Note that the [OverscrollEffect.node] will be applied internally as well - you do not
|
||||
* need to use Modifier.overscroll separately.
|
||||
* @param flingBehavior logic describing fling and snapping behavior when drag has finished.
|
||||
* @param reverseLayout reverses the direction of scrolling and layout.
|
||||
* @param horizontalAlignment aligns items horizontally.
|
||||
* @param verticalArrangement is arrangement for items. This only applies if the content is smaller
|
||||
* than the viewport.
|
||||
* @param content a block which describes the content. Inside this block you can use methods like
|
||||
* [ListScope.item] to add a single item or [ListScope.items] to add a list of items.
|
||||
*/
|
||||
@Composable
|
||||
public fun VerticalList(
|
||||
modifier: Modifier = Modifier,
|
||||
state: ListState = rememberListState(),
|
||||
contentPadding: PaddingValues = VerticalListDefaults.contentPadding,
|
||||
userScrollEnabled: Boolean = true,
|
||||
overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
|
||||
flingBehavior: FlingBehavior = VerticalListDefaults.flingBehavior(state),
|
||||
reverseLayout: Boolean = false,
|
||||
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
||||
verticalArrangement: Arrangement.Vertical = VerticalListDefaults.verticalArrangement,
|
||||
content: ListScope.() -> Unit,
|
||||
): Unit =
|
||||
List(
|
||||
orientation = Orientation.Vertical,
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
reverseLayout = reverseLayout,
|
||||
contentPadding = contentPadding,
|
||||
userScrollEnabled = userScrollEnabled,
|
||||
overscrollEffect = overscrollEffect,
|
||||
flingBehavior = flingBehavior,
|
||||
horizontalAlignment = horizontalAlignment,
|
||||
verticalArrangement = verticalArrangement,
|
||||
verticalAlignment = null,
|
||||
horizontalArrangement = null,
|
||||
content = content,
|
||||
)
|
||||
|
||||
/**
|
||||
* This is a scrolling list component that only composes and lays out the currently visible items.
|
||||
* It is based on [androidx.compose.foundation.lazy.LazyColumn], but with extra functionality and
|
||||
* customized behavior required for Jetpack Compose Glimmer. Jetpack Compose Glimmer applications
|
||||
* should always use VerticalList instead of LazyColumn to ensure correct behavior.
|
||||
*
|
||||
* The [content] block defines a DSL which allows you to emit items of different types. For example,
|
||||
* you can use [ListScope.item] to add a single item and [ListScope.items] to add a list of items.
|
||||
*
|
||||
* This overload of `VerticalList` contains a `title` slot. The title is expected to be a
|
||||
* [androidx.xr.glimmer.TitleChip]. It is positioned at the top center and visually overlaps the
|
||||
* list content. The list is vertically offset to start from the title's vertical center. When the
|
||||
* list is scrolled, the title remains static.
|
||||
*
|
||||
* See the other [VerticalList] overload for a variant with no title slot.
|
||||
*
|
||||
* @sample androidx.xr.glimmer.samples.VerticalListWithTitleChipSample
|
||||
* @param title a composable slot for the list title, expected to be a
|
||||
* [androidx.xr.glimmer.TitleChip]. It overlaps the list, positioned at the top-center, and
|
||||
* remains stuck to the top when the list is scrolled.
|
||||
* @param modifier applies to the layout that contains both list and title.
|
||||
* @param state the state object to be used to control or observe the list's state.
|
||||
* @param contentPadding a padding around the whole content. This will add padding for the content
|
||||
* after it has been clipped, which is not possible via [modifier] param. You can use it to add a
|
||||
* padding before the first item or after the last one. The list is vertically offset to start
|
||||
* from the title's vertical center, so custom content paddings must provide sufficient space to
|
||||
* avoid content being obscured.
|
||||
* @param userScrollEnabled If user gestures are enabled.
|
||||
* @param overscrollEffect the [OverscrollEffect] that will be used to render overscroll for this
|
||||
* layout. Note that the [OverscrollEffect.node] will be applied internally as well - you do not
|
||||
* need to use Modifier.overscroll separately.
|
||||
* @param flingBehavior logic describing fling and snapping behavior when drag has finished.
|
||||
* @param reverseLayout reverses the direction of scrolling and layout.
|
||||
* @param horizontalAlignment aligns items horizontally.
|
||||
* @param verticalArrangement is arrangement for items. This only applies if the content is smaller
|
||||
* than the viewport.
|
||||
* @param content a block which describes the content. Inside this block you can use methods like
|
||||
* [ListScope.item] to add a single item or [ListScope.items] to add a list of items.
|
||||
*/
|
||||
@Suppress(
|
||||
// The main trailing lambda is [content], but it's DSL.
|
||||
"ComposableLambdaParameterNaming",
|
||||
"ComposableLambdaParameterPosition",
|
||||
)
|
||||
@Composable
|
||||
public fun VerticalList(
|
||||
title: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
state: ListState = rememberListState(),
|
||||
contentPadding: PaddingValues = VerticalListDefaults.contentPaddingWithTitle,
|
||||
userScrollEnabled: Boolean = true,
|
||||
overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
|
||||
flingBehavior: FlingBehavior = VerticalListDefaults.flingBehavior(state),
|
||||
reverseLayout: Boolean = false,
|
||||
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
||||
verticalArrangement: Arrangement.Vertical = VerticalListDefaults.verticalArrangement,
|
||||
content: ListScope.() -> Unit,
|
||||
) {
|
||||
VerticalListWithTitleLayout(
|
||||
modifier = modifier,
|
||||
title = title,
|
||||
list = {
|
||||
VerticalList(
|
||||
state = state,
|
||||
contentPadding = contentPadding,
|
||||
userScrollEnabled = userScrollEnabled,
|
||||
overscrollEffect = overscrollEffect,
|
||||
flingBehavior = flingBehavior,
|
||||
reverseLayout = reverseLayout,
|
||||
horizontalAlignment = horizontalAlignment,
|
||||
verticalArrangement = verticalArrangement,
|
||||
content = content,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/** Contains the default values used by [VerticalList]. */
|
||||
public object VerticalListDefaults {
|
||||
/**
|
||||
* Recommended value for the distance between items.
|
||||
*
|
||||
* @see [verticalArrangement] for the default arrangement that uses this spacing.
|
||||
*/
|
||||
public val itemSpacing: Dp
|
||||
@Composable get() = GlimmerTheme.componentSpacingValues.extraLarge
|
||||
|
||||
/** The maximum height of the fade effects on the sides of the list. */
|
||||
public val ScrimMaxHeight: Dp = 46.dp
|
||||
|
||||
/** Recommended content padding values for lists without a title. */
|
||||
public val contentPadding: PaddingValues
|
||||
@Composable get() = PaddingValues(vertical = itemSpacing, horizontal = 0.dp)
|
||||
|
||||
/** Recommended content padding values for lists with a title. */
|
||||
public val contentPaddingWithTitle: PaddingValues
|
||||
@Composable get() = PaddingValues(top = ScrimMaxHeight, bottom = itemSpacing)
|
||||
|
||||
/** Recommended values for the vertical arrangement. */
|
||||
public val verticalArrangement: Arrangement.Vertical
|
||||
@Composable get() = Arrangement.spacedBy(itemSpacing)
|
||||
|
||||
/**
|
||||
* Creates and remembers the default fling behavior for a [VerticalList] that aligns the focus
|
||||
* position with list scroll.
|
||||
*
|
||||
* @param state The [ListState] to observe for layout and focus information.
|
||||
* @return A [FlingBehavior] instance that provides focus-aware snapping.
|
||||
*/
|
||||
@Composable
|
||||
public fun flingBehavior(state: ListState): FlingBehavior {
|
||||
val snapLayoutInfoProvider = remember(state) { SnapLayoutInfoProvider(state) }
|
||||
return rememberSnapFlingBehavior(snapLayoutInfoProvider)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun VerticalListWithTitleLayout(
|
||||
modifier: Modifier = Modifier,
|
||||
title: @Composable () -> Unit,
|
||||
list: @Composable () -> Unit,
|
||||
) {
|
||||
Layout(modifier = modifier, contents = listOf(list, title)) { measurables, constraints ->
|
||||
// The title parameter is provided by users, requiring iteration through all measurables.
|
||||
// The list parameter is provided by us, allowing to guarantee that it contains only
|
||||
// a single measurable.
|
||||
val listMeasurable = measurables[0][0]
|
||||
val titleMeasurables = measurables[1]
|
||||
// Measure title(s) first.
|
||||
var titleMaxHeight = 0
|
||||
var titleMaxWidth = 0
|
||||
val titleConstraints = constraints.copyMaxDimensions()
|
||||
val titlePlaceables =
|
||||
titleMeasurables.fastMap { measurable ->
|
||||
val placeable = measurable.measure(titleConstraints)
|
||||
titleMaxHeight = max(titleMaxHeight, placeable.height)
|
||||
titleMaxWidth = max(titleMaxWidth, placeable.width)
|
||||
placeable
|
||||
}
|
||||
|
||||
// List shouldn't use the space above the vertical center of the title.
|
||||
val titleYOffset = titleMaxHeight / 2
|
||||
val maxListHeight = constraints.maxHeight - titleYOffset
|
||||
val minListHeight = minOf(constraints.minHeight, maxListHeight)
|
||||
val listConstraints = constraints.copy(minHeight = minListHeight, maxHeight = maxListHeight)
|
||||
val listPlaceable = listMeasurable.measure(listConstraints)
|
||||
|
||||
val layoutWidth = maxOf(listPlaceable.width, titleMaxWidth)
|
||||
val layoutHeight = listPlaceable.height + titleYOffset
|
||||
layout(width = layoutWidth, height = layoutHeight) {
|
||||
// Place the list first.
|
||||
listPlaceable.placeRelative(
|
||||
x = (layoutWidth - listPlaceable.width) / 2,
|
||||
y = titleYOffset,
|
||||
)
|
||||
// Then place the rest of the titles on top of the list.
|
||||
titlePlaceables.fastForEach { titlePlaceable ->
|
||||
// Each title's center aligned with the top of the list.
|
||||
titlePlaceable.placeRelative(
|
||||
x = (layoutWidth - titlePlaceable.width) / 2,
|
||||
y = (titleMaxHeight - titlePlaceable.height) / 2,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The scrolling list that only composes and lays out the currently visible items. The [content]
|
||||
* block defines a DSL which allows you to emit items of different types. For example, you can use
|
||||
* [ListScope.item] to add a single item and [ListScope.items] to add a list of items.
|
||||
*
|
||||
* @param orientation The orientation in which to layout items in this list.
|
||||
* @param modifier the modifier to apply to this layout.
|
||||
* @param state the state object to be used to control or observe the list's state.
|
||||
* @param contentPadding a padding around the whole content. This will add padding for the content
|
||||
* after it has been clipped, which is not possible via [modifier] param. You can use it to add a
|
||||
* padding before the first item or after the last one.
|
||||
* @param userScrollEnabled If user gestures are enabled.
|
||||
* @param overscrollEffect the [OverscrollEffect] that will be used to render overscroll for this
|
||||
* layout. Note that the [OverscrollEffect.node] will be applied internally as well - you do not
|
||||
* need to use Modifier.overscroll separately.
|
||||
* @param flingBehavior logic describing fling and snapping behavior when drag has finished.
|
||||
* @param reverseLayout reverses the direction of scrolling and layout.
|
||||
* @param horizontalAlignment aligns items horizontally. It's required and used only if
|
||||
* [orientation] is [Orientation.Vertical].
|
||||
* @param verticalArrangement is arrangement for items. This only applies if the content is smaller
|
||||
* than the viewport. It's required and used only if [orientation] is [Orientation.Vertical].
|
||||
* @param verticalAlignment aligns items vertically. It's required and used only if [orientation] is
|
||||
* [Orientation.Horizontal].
|
||||
* @param horizontalArrangement is arrangement for items. This only applies if the content is
|
||||
* smaller than the viewport. It's required and used only if [orientation] is
|
||||
* [Orientation.Vertical].
|
||||
* @param content a block which describes the content. Inside this block you can use methods like
|
||||
* [ListScope.item] to add a single item or [ListScope.items] to add a list of items.
|
||||
*/
|
||||
@Composable
|
||||
internal fun List(
|
||||
orientation: Orientation,
|
||||
modifier: Modifier,
|
||||
state: ListState,
|
||||
contentPadding: PaddingValues,
|
||||
userScrollEnabled: Boolean,
|
||||
overscrollEffect: OverscrollEffect?,
|
||||
flingBehavior: FlingBehavior,
|
||||
reverseLayout: Boolean,
|
||||
horizontalAlignment: Alignment.Horizontal?,
|
||||
verticalArrangement: Arrangement.Vertical?,
|
||||
verticalAlignment: Alignment.Vertical?,
|
||||
horizontalArrangement: Arrangement.Horizontal?,
|
||||
content: ListScope.() -> Unit,
|
||||
) {
|
||||
val itemProvider = rememberGlimmerListItemProviderLambda(state, content)
|
||||
|
||||
val semanticState = rememberGlimmerListSemanticState(state, orientation)
|
||||
|
||||
val scrollEnabled = isScrollEnabled(userScrollEnabled, state)
|
||||
|
||||
val measurePolicy =
|
||||
rememberGlimmerListMeasurePolicy(
|
||||
itemProviderLambda = itemProvider,
|
||||
state = state,
|
||||
contentPadding = contentPadding,
|
||||
reverseLayout = reverseLayout,
|
||||
orientation = orientation,
|
||||
horizontalAlignment = horizontalAlignment,
|
||||
verticalArrangement = verticalArrangement,
|
||||
verticalAlignment = verticalAlignment,
|
||||
horizontalArrangement = horizontalArrangement,
|
||||
)
|
||||
|
||||
val beyondBoundsModifier =
|
||||
if (scrollEnabled) {
|
||||
Modifier.lazyLayoutBeyondBoundsModifier(
|
||||
state = rememberGlimmerListBeyondBoundsState(state),
|
||||
beyondBoundsInfo = state.beyondBoundsInfo,
|
||||
reverseLayout = reverseLayout,
|
||||
orientation = orientation,
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
|
||||
LazyLayout(
|
||||
modifier =
|
||||
modifier
|
||||
.then(state.remeasurementModifier)
|
||||
.then(state.awaitLayoutModifier)
|
||||
.autoFocus(state.autoFocusState)
|
||||
.lazyLayoutSemantics(
|
||||
itemProviderLambda = itemProvider,
|
||||
state = semanticState,
|
||||
orientation = orientation,
|
||||
userScrollEnabled = scrollEnabled,
|
||||
reverseScrolling = reverseLayout,
|
||||
)
|
||||
.then(beyondBoundsModifier)
|
||||
.edgeScrim(
|
||||
state = state.scrollIndicatorState,
|
||||
orientation = orientation,
|
||||
maxScrimSize = VerticalListDefaults.ScrimMaxHeight,
|
||||
)
|
||||
.scrollableArea(
|
||||
state = state,
|
||||
orientation = orientation,
|
||||
enabled = scrollEnabled,
|
||||
interactionSource = state.internalInteractionSource,
|
||||
overscrollEffect = overscrollEffect,
|
||||
flingBehavior = flingBehavior,
|
||||
),
|
||||
itemProvider = itemProvider,
|
||||
measurePolicy = measurePolicy,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun isScrollEnabled(userScrollEnabled: Boolean, state: ListState): Boolean {
|
||||
if (userScrollEnabled) {
|
||||
val derivedState =
|
||||
remember(state) { derivedStateOf { state.canScrollForward || state.canScrollBackward } }
|
||||
return derivedState.value
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
Reference in New Issue
Block a user