android Jetpack编写滚动条

kqlmhetl  于 2023-05-05  发布在  Android
关注(0)|答案(9)|浏览(264)

有没有办法添加滚动条来添加LazyColumnScrollableColumn已弃用)。Javadoc没有提到Jetpack Compose中的滚动条。
为了澄清,这是我想要实现的设计:

在Jetpack Compose中可以做到这一点吗?或者不支持滚动条?

5cnsuln7

5cnsuln71#

现在这实际上是可能的(他们已经在LazyListState中添加了更多的东西),而且很容易做到。这是一个非常原始的滚动条(总是可见/不能拖动/等),它使用项目索引来计算拇指位置,因此在只有几个项目的列表中滚动时可能看起来不太好:

@Composable
  fun Modifier.simpleVerticalScrollbar(
    state: LazyListState,
    width: Dp = 8.dp
  ): Modifier {
    val targetAlpha = if (state.isScrollInProgress) 1f else 0f
    val duration = if (state.isScrollInProgress) 150 else 500

    val alpha by animateFloatAsState(
      targetValue = targetAlpha,
      animationSpec = tween(durationMillis = duration)
    )

    return drawWithContent {
      drawContent()

      val firstVisibleElementIndex = state.layoutInfo.visibleItemsInfo.firstOrNull()?.index
      val needDrawScrollbar = state.isScrollInProgress || alpha > 0.0f

      // Draw scrollbar if scrolling or if the animation is still running and lazy column has content
      if (needDrawScrollbar && firstVisibleElementIndex != null) {
        val elementHeight = this.size.height / state.layoutInfo.totalItemsCount
        val scrollbarOffsetY = firstVisibleElementIndex * elementHeight
        val scrollbarHeight = state.layoutInfo.visibleItemsInfo.size * elementHeight

        drawRect(
          color = Color.Red,
          topLeft = Offset(this.size.width - width.toPx(), scrollbarOffsetY),
          size = Size(width.toPx(), scrollbarHeight),
          alpha = alpha
        )
      }
    }
  }

UPD:我已经更新了代码。我已经弄明白了如何显示/隐藏滚动条时,LazyColumn正在滚动或不+添加淡入淡出动画。我还将drawBehind()更改为drawWithContent(),因为前者在内容后面绘制,所以在某些情况下可能会在滚动条顶部绘制,这很可能是不可取的。

rlcwz9us

rlcwz9us2#

这在LazyColumn/LazyRow中还不可能实现。
它计划在某个时候添加,但目前还没有具体的计划版本。我会在可能的时候更新这个答案。

bvjxkvbb

bvjxkvbb3#

我使用了the answer by @Dmitry并在其之上构建:

  • 在“旋钮”之外增加了一个“轨道”
  • 对垂直和水平滚动条的解决方案进行了推广
  • 提取多个参数,以帮助自定义滚动条行为;包括Assert其有效性的代码
  • 修正了滚动时旋钮改变大小的问题,增加了在项目大小不一致的情况下传入fixedKnobRatio参数的功能
  • 添加了文档和注解
/**
 * Renders a scrollbar.
 *
 * <ul> <li> A scrollbar is composed of two components: a track and a knob. The knob moves across
 * the track <li> The scrollbar appears automatically when the user starts scrolling and disappears
 * after the scrolling is finished </ul>
 *
 * @param state The [LazyListState] that has been passed into the lazy list or lazy row
 * @param horizontal If `true`, this will be a horizontally-scrolling (left and right) scroll bar,
 * if `false`, it will be vertically-scrolling (up and down)
 * @param alignEnd If `true`, the scrollbar will appear at the "end" of the scrollable composable it
 * is decorating (at the right-hand side in left-to-right locales or left-hand side in right-to-left
 * locales, for the vertical scrollbars -or- the bottom for horizontal scrollbars). If `false`, the
 * scrollbar will appear at the "start" of the scrollable composable it is decorating (at the
 * left-hand side in left-to-right locales or right-hand side in right-to-left locales, for the
 * vertical scrollbars -or- the top for horizontal scrollbars)
 * @param thickness How thick/wide the track and knob should be
 * @param fixedKnobRatio If not `null`, the knob will always have this size, proportional to the
 * size of the track. You should consider doing this if the size of the items in the scrollable
 * composable is not uniform, to avoid the knob from oscillating in size as you scroll through the
 * list
 * @param knobCornerRadius The corner radius for the knob
 * @param trackCornerRadius The corner radius for the track
 * @param knobColor The color of the knob
 * @param trackColor The color of the track. Make it [Color.Transparent] to hide it
 * @param padding Edge padding to "squeeze" the scrollbar start/end in so it's not flush with the
 * contents of the scrollable composable it is decorating
 * @param visibleAlpha The alpha when the scrollbar is fully faded-in
 * @param hiddenAlpha The alpha when the scrollbar is fully faded-out. Use a non-`0` number to keep
 * the scrollbar from ever fading out completely
 * @param fadeInAnimationDurationMs The duration of the fade-in animation when the scrollbar appears
 * once the user starts scrolling
 * @param fadeOutAnimationDurationMs The duration of the fade-out animation when the scrollbar
 * disappears after the user is finished scrolling
 * @param fadeOutAnimationDelayMs Amount of time to wait after the user is finished scrolling before
 * the scrollbar begins its fade-out animation
 */
@Composable
fun Modifier.scrollbar(
    state: LazyListState,
    horizontal: Boolean,
    alignEnd: Boolean = true,
    thickness: Dp = 4.dp,
    fixedKnobRatio: Float? = null,
    knobCornerRadius: Dp = 4.dp,
    trackCornerRadius: Dp = 2.dp,
    knobColor: Color = Color.Black,
    trackColor: Color = Color.White,
    padding: Dp = 0.dp,
    visibleAlpha: Float = 1f,
    hiddenAlpha: Float = 0f,
    fadeInAnimationDurationMs: Int = 150,
    fadeOutAnimationDurationMs: Int = 500,
    fadeOutAnimationDelayMs: Int = 1000,
): Modifier {
  check(thickness > 0.dp) { "Thickness must be a positive integer." }
  check(fixedKnobRatio == null || fixedKnobRatio < 1f) {
    "A fixed knob ratio must be smaller than 1."
  }
  check(knobCornerRadius >= 0.dp) { "Knob corner radius must be greater than or equal to 0." }
  check(trackCornerRadius >= 0.dp) { "Track corner radius must be greater than or equal to 0." }
  check(hiddenAlpha <= visibleAlpha) { "Hidden alpha cannot be greater than visible alpha." }
  check(fadeInAnimationDurationMs >= 0) {
    "Fade in animation duration must be greater than or equal to 0."
  }
  check(fadeOutAnimationDurationMs >= 0) {
    "Fade out animation duration must be greater than or equal to 0."
  }
  check(fadeOutAnimationDelayMs >= 0) {
    "Fade out animation delay must be greater than or equal to 0."
  }

  val targetAlpha =
      if (state.isScrollInProgress) {
        visibleAlpha
      } else {
        hiddenAlpha
      }
  val animationDurationMs =
      if (state.isScrollInProgress) {
        fadeInAnimationDurationMs
      } else {
        fadeOutAnimationDurationMs
      }
  val animationDelayMs =
      if (state.isScrollInProgress) {
        0
      } else {
        fadeOutAnimationDelayMs
      }

  val alpha by
      animateFloatAsState(
          targetValue = targetAlpha,
          animationSpec =
              tween(delayMillis = animationDelayMs, durationMillis = animationDurationMs))

  return drawWithContent {
    drawContent()

    state.layoutInfo.visibleItemsInfo.firstOrNull()?.let { firstVisibleItem ->
      if (state.isScrollInProgress || alpha > 0f) {
        // Size of the viewport, the entire size of the scrollable composable we are decorating with
        // this scrollbar.
        val viewportSize =
            if (horizontal) {
              size.width
            } else {
              size.height
            } - padding.toPx() * 2

        // The size of the first visible item. We use this to estimate how many items can fit in the
        // viewport. Of course, this works perfectly when all items have the same size. When they
        // don't, the scrollbar knob size will grow and shrink as we scroll.
        val firstItemSize = firstVisibleItem.size

        // The *estimated* size of the entire scrollable composable, as if it's all on screen at
        // once. It is estimated because it's possible that the size of the first visible item does
        // not represent the size of other items. This will cause the scrollbar knob size to grow
        // and shrink as we scroll, if the item sizes are not uniform.
        val estimatedFullListSize = firstItemSize * state.layoutInfo.totalItemsCount

        // The difference in position between the first pixels visible in our viewport as we scroll
        // and the top of the fully-populated scrollable composable, if it were to show all the
        // items at once. At first, the value is 0 since we start all the way to the top (or start
        // edge). As we scroll down (or towards the end), this number will grow.
        val viewportOffsetInFullListSpace =
            state.firstVisibleItemIndex * firstItemSize + state.firstVisibleItemScrollOffset

        // Where we should render the knob in our composable.
        val knobPosition =
            (viewportSize / estimatedFullListSize) * viewportOffsetInFullListSpace + padding.toPx()
        // How large should the knob be.
        val knobSize =
            fixedKnobRatio?.let { it * viewportSize }
                ?: (viewportSize * viewportSize) / estimatedFullListSize

        // Draw the track
        drawRoundRect(
            color = trackColor,
            topLeft =
                when {
                  // When the scrollbar is horizontal and aligned to the bottom:
                  horizontal && alignEnd -> Offset(padding.toPx(), size.height - thickness.toPx())
                  // When the scrollbar is horizontal and aligned to the top:
                  horizontal && !alignEnd -> Offset(padding.toPx(), 0f)
                  // When the scrollbar is vertical and aligned to the end:
                  alignEnd -> Offset(size.width - thickness.toPx(), padding.toPx())
                  // When the scrollbar is vertical and aligned to the start:
                  else -> Offset(0f, padding.toPx())
                },
            size =
                if (horizontal) {
                  Size(size.width - padding.toPx() * 2, thickness.toPx())
                } else {
                  Size(thickness.toPx(), size.height - padding.toPx() * 2)
                },
            alpha = alpha,
            cornerRadius = CornerRadius(x = trackCornerRadius.toPx(), y = trackCornerRadius.toPx()),
        )

        // Draw the knob
        drawRoundRect(
            color = knobColor,
            topLeft =
                when {
                  // When the scrollbar is horizontal and aligned to the bottom:
                  horizontal && alignEnd -> Offset(knobPosition, size.height - thickness.toPx())
                  // When the scrollbar is horizontal and aligned to the top:
                  horizontal && !alignEnd -> Offset(knobPosition, 0f)
                  // When the scrollbar is vertical and aligned to the end:
                  alignEnd -> Offset(size.width - thickness.toPx(), knobPosition)
                  // When the scrollbar is vertical and aligned to the start:
                  else -> Offset(0f, knobPosition)
                },
            size =
                if (horizontal) {
                  Size(knobSize, thickness.toPx())
                } else {
                  Size(thickness.toPx(), knobSize)
                },
            alpha = alpha,
            cornerRadius = CornerRadius(x = knobCornerRadius.toPx(), y = knobCornerRadius.toPx()),
        )
      }
    }
  }
}
xsuvu9jc

xsuvu9jc4#

这可能会有所帮助:https://github.com/sahruday/Carousel是一种类似的方法,使用Compose作为Composable函数。
同时适用于CarouselScrollState(在ScrollState上添加的参数)和LazyList
如果高度是变化的或混合的项目,我不建议添加滚动指示器。

svdrlsy4

svdrlsy45#

如果您来自库的JetBrains Compose Multiplatform方面(无论如何,滚动条在桌面上更有意义),请查看https://github.com/JetBrains/compose-multiplatform/tree/master/tutorials/Desktop_Components
它们提供了VerticalScrollbar/HorizontalScrollbar可组合组件,可以与惰性可滚动组件一起使用。

import androidx.compose.foundation.VerticalScrollbar
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState

fun main() = application {
    Window(
        onCloseRequest = ::exitApplication,
        title = "Scrollbars",
        state = rememberWindowState(width = 250.dp, height = 400.dp)
    ) {
        LazyScrollable()
    }
}

@Composable
fun LazyScrollable() {
    Box(
        modifier = Modifier.fillMaxSize()
            .background(color = Color(180, 180, 180))
            .padding(10.dp)
    ) {

        val state = rememberLazyListState()

        LazyColumn(Modifier.fillMaxSize().padding(end = 12.dp), state) {
            items(1000) { x ->
                TextBox("Item #$x")
                Spacer(modifier = Modifier.height(5.dp))
            }
        }
        VerticalScrollbar(
            modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(),
            adapter = rememberScrollbarAdapter(
                scrollState = state
            )
        )
    }
}

@Composable
fun TextBox(text: String = "Item") {
    Box(
        modifier = Modifier.height(32.dp)
            .fillMaxWidth()
            .background(color = Color(0, 0, 0, 20))
            .padding(start = 10.dp),
        contentAlignment = Alignment.CenterStart
    ) {
        Text(text = text)
    }
}
tez616oj

tez616oj6#

将下面的代码复制粘贴到单个Kotlin文件中。

import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.consumeAllChanges
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

@ExperimentalAnimationApi
@ExperimentalMaterialApi
@ExperimentalComposeUiApi
@ExperimentalFoundationApi
@RequiresApi(Build.VERSION_CODES.N)
@Composable
fun <T> LazyColumnWithScrollbar(
    data: List<T>,
    modifier: Modifier = Modifier,
    state: LazyListState = rememberLazyListState(),
    contentPadding: PaddingValues = PaddingValues(0.dp),
//                            reverseLayout: Boolean = false,
//                            verticalArrangement: Arrangement.Vertical =
//                                if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
    content: LazyListScope.() -> Unit
) {
    val coroutineContext = rememberCoroutineScope()
    val animationCoroutineContext = rememberCoroutineScope()

    val offsetY = remember { mutableStateOf(0f) }
    val isUserScrollingLazyColumn = remember {
        mutableStateOf(true)
    }
    val heightInPixels = remember {
        mutableStateOf(0F)
    }
    val firstVisibleItem = remember {
        mutableStateOf(0)
    }
    val isScrollbarVisible = remember {
        mutableStateOf(false)
    }

    BoxWithConstraints(modifier = modifier) {
        LazyColumn(state = state,
            contentPadding = contentPadding,
//            reverseLayout = reverseLayout,
//        verticalArrangement = verticalArrangement,
            horizontalAlignment = horizontalAlignment,
            flingBehavior = flingBehavior,
            modifier = Modifier.pointerInput(Unit) {
                detectTapGestures(onPress = {
                    isUserScrollingLazyColumn.value = true
                    heightInPixels.value = maxHeight.toPx()
                },
                    onTap = {
                        isUserScrollingLazyColumn.value = true
                        heightInPixels.value = maxHeight.toPx()
                    })
            }
        ) {
            if (!state.isScrollInProgress) {
                isUserScrollingLazyColumn.value = true
                hideScrollbar(animationCoroutineContext, isScrollbarVisible)

                if (state.layoutInfo.visibleItemsInfo.isNotEmpty()) {
                    firstVisibleItem.value = state.layoutInfo.visibleItemsInfo.first().index
                }
            } else if (state.isScrollInProgress && isUserScrollingLazyColumn.value) {
                showScrollbar(animationCoroutineContext, isScrollbarVisible)

                if (heightInPixels.value != 0F) {

                    if (firstVisibleItem.value > state.layoutInfo.visibleItemsInfo.first().index || // Scroll to upper start of list
                        state.layoutInfo.visibleItemsInfo.first().index == 0 // Reached the upper start of list
                    ) {
                        if (state.layoutInfo.visibleItemsInfo.first().index == 0) {
                            offsetY.value = 0F
                        } else {
                            offsetY.value = calculateScrollbarOffsetY(state, data.size, heightInPixels)
                        }
                    } else { // scroll to bottom end of list or reach the bottom end of the list
                        if (state.layoutInfo.visibleItemsInfo.last().index == data.lastIndex) {
                            offsetY.value = heightInPixels.value - heightInPixels.value / 3F
                        } else {
                            offsetY.value = calculateScrollbarOffsetY(state, data.size, heightInPixels)
                        }
                    }

                }
            }
            content()
        }
        if (state.layoutInfo.visibleItemsInfo.size < data.size) {
            AnimatedVisibility(
                visible = isScrollbarVisible.value,
                enter = fadeIn(
                    animationSpec = tween(
                        durationMillis = 200,
                        easing = LinearEasing
                    )
                ),
                exit = fadeOut(
                    animationSpec = tween(
                        delayMillis = 1000,
                        durationMillis = 1000,
                        easing = LinearEasing
                    )
                ),
                modifier = Modifier.align(Alignment.CenterEnd)
            ) {
                Canvas(modifier = Modifier
                    .width(15.dp)
                    .height(maxHeight)
                    .align(Alignment.CenterEnd)
                    .background(Color.Transparent)
                    .pointerInput(Unit) {
                        heightInPixels.value = maxHeight.toPx()
                        detectDragGestures { change, dragAmount ->
                            change.consumeAllChanges()

                            showScrollbar(animationCoroutineContext, isScrollbarVisible)

                            isUserScrollingLazyColumn.value = false
                            if (dragAmount.y > 0) { // drag slider down
                                if (offsetY.value >= (maxHeight.toPx() - maxHeight.toPx() / 3F)) { // Bottom End
                                    offsetY.value = maxHeight.toPx() - maxHeight.toPx() / 3F
                                    coroutineContext.launch {
                                        state.scrollToItem(data.lastIndex)
                                    }
                                } else {
                                    offsetY.value = offsetY.value + dragAmount.y
                                }
                            } else { // drag slider up
                                if (offsetY.value <= 0f) { // Top Start
                                    offsetY.value = 0F
                                    coroutineContext.launch {
                                        state.scrollToItem(0)
                                    }
                                } else {
                                    offsetY.value = offsetY.value + dragAmount.y
                                }
                            }
                            val yMaxValue = maxHeight.toPx() - maxHeight.toPx() / 3F
                            val yPercentage = (100 * offsetY.value) / yMaxValue

                            /* The items which could be rendered should not be taken under account
                            otherwise you are going to show the last rendered items before
                            the scrollbar reaches the bottom.
                            Change the renderedItemsNumberPerScroll = 0 and scroll to the bottom
                            and you will understand.
                             */
                            val renderedItemsNumberPerScroll =
                                state.layoutInfo.visibleItemsInfo.size - 2
                            val index =
                                (((data.lastIndex - renderedItemsNumberPerScroll) * yPercentage) / 100).toInt()

                            coroutineContext.launch {
                                if (index > 0) {
                                    state.scrollToItem(index)
                                }
                            }
                        }
                    }
                ) {
                    drawRoundRect(
                        topLeft = Offset(0f, offsetY.value),
                        color = Color.DarkGray,
                        size = Size(size.width / 2F, maxHeight.toPx() / 3F),
                        cornerRadius = CornerRadius(20F, 20F)
                    )
                }
            }
        }
    }
}

private fun hideScrollbar(coroutineScope: CoroutineScope, state: MutableState<Boolean>) {
    coroutineScope.launch {
        state.value = false
    }
}

private fun showScrollbar(coroutineScope: CoroutineScope, state: MutableState<Boolean>) {
    coroutineScope.launch {
        state.value = true
    }
}

/* The items which are already shown on screen should not be taken
for calculations because they are already on screen!
You have to calculate the items remaining off screen as the 100%
of the data and match this percentage with the distance travelled
by the scrollbar.
*/
private fun calculateScrollbarOffsetY(
    state: LazyListState, dataSize: Int,
    height: MutableState<Float>
): Float {
    val renderedItemsNumberPerScroll =
        state.layoutInfo.visibleItemsInfo.size - 2
    val itemsToScroll = dataSize - renderedItemsNumberPerScroll
    val index = state.layoutInfo.visibleItemsInfo.first().index
    val indexPercentage = ((100 * index) / itemsToScroll)

    val yMaxValue = height.value - height.value / 3F

    return ((yMaxValue * indexPercentage) / 100)
}

然后调用可组合函数LazyColumnWithScrollbar。该函数的参数与LazyColumn类似。

4bbkushb

4bbkushb7#

您可以按照下面的代码库滚动
用于垂直滚动

Composable
fun ScrollableColumn(
    modifier: Modifier = Modifier,
    scrollState: ScrollState = rememberScrollState(0f),
    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
    horizontalGravity: Alignment.Horizontal = Alignment.Start,
    reverseScrollDirection: Boolean = false,
    isScrollEnabled: Boolean = true,
    contentPadding: InnerPadding = InnerPadding(0.dp),
    children: @Composable ColumnScope.() -> Unit
)

用于水平滚动

Composable
fun ScrollableRow(
    modifier: Modifier = Modifier,
    scrollState: ScrollState = rememberScrollState(0f),
    horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
    verticalGravity: Alignment.Vertical = Alignment.Top,
    reverseScrollDirection: Boolean = false,
    isScrollEnabled: Boolean = true,
    contentPadding: InnerPadding = InnerPadding(0.dp),
    children: @Composable RowScope.() -> Unit
)
8nuwlpux

8nuwlpux8#

使用Jetpack Compose构建带有滚动条的LazyColumn

@Composable
fun ExampleLazyColumnWithScrollbar(data: List<Int>) {
    val scrollbarSettings = remember {
        mutableStateOf(LazyColumnScrollbarSettings())
    }

    Column(modifier = Modifier.fillMaxSize()) {
        LazyColumnWithScrollbar(
            data = data,
            settings = scrollbarSettings.value,
            modifier = Modifier.height(500.dp)
        ) {
            items(data) {
                Card(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(5.dp)
                        .clickable { },
                    elevation = 10.dp
                ) {
                    Column {
                        Text(
                            text = it.toString(),
                            fontSize = 17.sp,
                            fontWeight = FontWeight.Bold,
                            fontStyle = FontStyle.Italic,
                            modifier = Modifier.padding(start = 10.dp)
                        )
                    }
                }
            }
        }

        Row() {
            Button(modifier = Modifier.fillMaxWidth(0.5F).padding(4.dp),
                contentPadding = PaddingValues(4.dp),
                onClick = {
                    scrollbarSettings.value = scrollbarSettings.value.copy(
                        thumbColor = Color.Green,
                        trailColor = Color.Transparent,
                        thumbWidth = LazyColumnScrollbarSettings.ThumbWidth.X_LARGE,
                        thumbHeight = LazyColumnScrollbarSettings.ThumbHeight.SMALL
                    )
                }
            ) {
                Text(text = "Green + Small + Thick")
            }

            Button(modifier = Modifier.fillMaxWidth(1F).padding(4.dp),
                contentPadding = PaddingValues(4.dp),
                onClick = {
                    scrollbarSettings.value = scrollbarSettings.value.copy(
                        thumbColor = Color.Red,
                        trailColor = Color.Yellow,
                        thumbWidth = LazyColumnScrollbarSettings.ThumbWidth.SMALL,
                        thumbHeight = LazyColumnScrollbarSettings.ThumbHeight.X_LARGE
                    )
                }
            ) {
                Text("Red + Yellow + XL + Thin")
            }
        }
        Button(modifier = Modifier.padding(4.dp).fillMaxWidth(),
            contentPadding = PaddingValues(4.dp),
            onClick = {
                scrollbarSettings.value = LazyColumnScrollbarSettings()
            }
        ) {
            Text("Default")
        }
    }
}
1sbrub3j

1sbrub3j9#

这里是一个简单的Kotlin代码片段,用于使用 Android Jetpack Compose创建Horizontal ScrollBar

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun DemoLazyRowWithHorizontalScrollbar() {
    val lazyListState = rememberLazyListState()
    LazyRow(
        state = lazyListState, modifier = Modifier
            .fillMaxSize()
            .simpleHorizontalScrollbar(lazyListState)
    ) {
        items(20) {
            Column {
                Spacer(Modifier.height(4.dp))
                Text(text = "Item A -$it")
                Spacer(Modifier.height(4.dp))
                Text(text = "Item B -$it")
                Spacer(Modifier.height(4.dp))
                Text(text = "Item C -$it")
                Spacer(Modifier.height(4.dp))
                Text(text = "Item D -$it")
                Spacer(Modifier.height(8.dp))
            }
        }
    }
}

@Composable
fun Modifier.simpleHorizontalScrollbar(
    state: LazyListState,
    height: Float = 12f,
    backgroundColor: Color = Color.DarkGray,
    color: Color = Color.LightGray
): Modifier {

    return drawWithContent {
        drawContent()

        val firstVisibleElementIndex = state.layoutInfo.visibleItemsInfo.firstOrNull()?.index

        if (firstVisibleElementIndex != null) {

            val scrollableItems = state.layoutInfo.totalItemsCount - state.layoutInfo.visibleItemsInfo.size
            val scrollBarWidth = this.size.width / scrollableItems
            var offsetX = ((this.size.width - scrollBarWidth) * firstVisibleElementIndex) / scrollableItems

            drawRect(
                color = backgroundColor,
                topLeft = Offset(x = 0f, y = this.size.height),
                size = Size(this.size.width, height),
                alpha = 1f
            )

            drawRect(
                color = color,
                topLeft = Offset(x = offsetX, y = this.size.height),
                size = Size(scrollBarWidth, height),
                alpha = 1f
            )
        }
    }
}

注意:LazyRow/LazyColumn中还没有直接的滚动条。但也有好消息。Compose提供了以多种方式自定义组件的规定。

  • 希望有帮助!

快乐编码:)*

相关问题