当前位置:网站首页>使用 Compose 实现可见 ScrollBar
使用 Compose 实现可见 ScrollBar
2022-07-02 06:25:00 【霏霏小雨】
前言
众所周知,如果在一个 View 内放一个更大的 View 需要滚动才能正常使用。
实现平台:Compose-Desktop (Compose-Android 等 Multiplatform 也是一样的)
实现效果图
开始吧
定义一个 UniversalScrollBox 方法,内一个 Box 用于包装所有子 Composable 内容。本例内容数据是 10 * 10 个 Text 组件。
滚动条宽度: 滚 动 条 宽 度 可 见 内 容 宽 度 = 可 见 内 容 宽 度 内 容 总 宽 度 \frac{滚动条宽度}{可见内容宽度} = \frac{可见内容宽度}{内容总宽度} 可见内容宽度滚动条宽度=内容总宽度可见内容宽度
滚动条高度:同宽度理。
private const val TAG = "UniversalScrollBox"
private fun TwoFloats(width: Float, height: Float) = androidx.compose.ui.geometry.Size(width = width, height = height)
inline val Int.ddp: Dp get() = Dp(value = this.toFloat())
inline val Float.ddp: Dp get() = Dp(value = this)
/** * 注意 content 中一定要有东西,没有做判空保护,会数组越界 */
@Composable
internal fun UniversalScrollBox(
modifier: Modifier = Modifier,
scrollBarStroke: Int = 16,
scrollBarColor: Color = MaterialTheme.colors.secondary.copy(alpha = 0.5F),
content: @Composable () -> Unit
) {
Box(
modifier = modifier
) {
var outerSize by remember {
mutableStateOf(IntSize(width = 0, height = 0)) } // 外层宽高,即内容可见的宽度
var sizeRatio by remember {
mutableStateOf(TwoFloats(width = 0F, height = 0F)) } // 内外大小比
var barSize by remember {
mutableStateOf(TwoFloats(width = 0F, height = 0F)) } // 水平滚动条宽度、垂直滚动条高度,$\frac{滚动条宽度}{可见内容宽度} = \frac{可见内容宽度}{内容总宽度}$
var dragOffset by remember {
mutableStateOf(TwoFloats(width = 0F, height = 0F)) } // 滚动条在水平、垂直方向拖动的距离
/** * 水平滚动条会遮挡垂直方向内容,因此,当水平滚动条存在时,需要设置垂直方向 padding,垂直滚动条同理。 * 当 content 变化,导致首次出现滚动条时,padding 随之发生变化,绘制时 outerSize 变化,从而可能使得另一个滚动条因此出现,继续导致 padding 变化,outerSize 变化,滚动条宽度变化。 * 但是这几次变化之后, padding 不会再改变,因而滚动条不会继续变化,变化终止。虽然第一次变化的时候,可以预测后续变化,但是懒得计算了。 */
Layout(
modifier = Modifier.fillMaxSize()
.padding(bottom = if (barSize.width > 0) 16.ddp else 0.ddp, end = if (barSize.height > 0) 16.ddp else 0.ddp)
.clipToBounds()
.align(Alignment.TopStart)
.wrapContentSize(unbounded = false),
content = content,
measurePolicy = object : MeasurePolicy {
override fun MeasureScope.measure(measurables: List<Measurable>, constraints: Constraints): MeasureResult {
// 外层 modifier 使用 unbounded false,可以通过 constraints.maxWidth maxHeight 算出外层的最大大小,注意这个 Size 是 padding 之后的
outerSize = IntSize(width = constraints.maxWidth, height = constraints.maxHeight)
Log.d(TAG, "outerSize $outerSize")
val placeables = measurables.map {
measurable ->
// 实际计算 子 Node 的时候,使用 maxWidth = Int.MAX_VALUE,可以得到 子 Node 的真实宽度
measurable.measure(constraints = constraints.copy(maxHeight = Int.MAX_VALUE, maxWidth = Int.MAX_VALUE))
}
val innerSize = IntSize(width = placeables[0].width, height = placeables[0].height)
// 计算是否需要水平、垂直滚动条
val needWidth = innerSize.width > outerSize.width
val needHeight = innerSize.height > outerSize.height
sizeRatio = TwoFloats(
width = if (needWidth) innerSize.width.toFloat() / outerSize.width.toFloat() else 0F,
height = if (needHeight) innerSize.height.toFloat() / outerSize.height.toFloat() else 0F,
)
barSize = TwoFloats(
width = if (needWidth) (outerSize.width * outerSize.width).toFloat() / innerSize.width else 0F,
height = if (needHeight) (outerSize.height * outerSize.height).toFloat() / innerSize.height else 0F
)
Log.d(TAG, "bar(width: ${
barSize.width}, height ${
barSize.height}) ratio(width: ${
sizeRatio.width}, height ${
sizeRatio.height})")
return layout(width = outerSize.width, height = outerSize.height) {
placeables.forEach {
it.place(x = (-dragOffset.width * sizeRatio.width).toInt(), y = (-dragOffset.height * sizeRatio.height).toInt(), zIndex = 0F) }
}
}
}
)
if (barSize.width > 0) {
Box(
modifier = Modifier.fillMaxWidth()
.height(scrollBarStroke.ddp)
.align(Alignment.BottomStart)
.background(Color.Transparent)
) {
Box(
modifier = Modifier.align(Alignment.BottomStart)
.fillMaxHeight()
.offset {
IntOffset(dragOffset.width.roundToInt(), 0) }
.width(barSize.width.ddp)
.clip(RoundedCornerShape(4.ddp)) // 注意先 clip 再 background
.background(scrollBarColor) // 注意先 offset 再 background
.draggable(state = rememberDraggableState {
var widthOffset = dragOffset.width
widthOffset += it
// 限制拖动不要超过两端
// 注意水平、垂直滚动条会互相影响,当两方同时存在时,应该控制大小,不要相交在 BottomEnd
if (widthOffset < 0) {
widthOffset = 0F
} else if (widthOffset > outerSize.width - barSize.width) {
widthOffset = (outerSize.width - barSize.width)
}
dragOffset = TwoFloats(
width = widthOffset,
height = dragOffset.height
)
}, orientation = Orientation.Horizontal)
)
}
}
if (barSize.height > 0) {
Box(
modifier = Modifier.fillMaxHeight()
.width(scrollBarStroke.ddp)
.align(Alignment.TopEnd)
.background(Color.Transparent)
) {
Box(
modifier = Modifier.align(Alignment.TopEnd)
.fillMaxWidth()
.offset {
IntOffset(0, dragOffset.height.roundToInt()) }
.height(barSize.height.ddp)
.clip(RoundedCornerShape(4.ddp))
.background(scrollBarColor)
.draggable(state = rememberDraggableState {
var heightOffset = dragOffset.height
heightOffset += it
if (heightOffset < 0) {
heightOffset = 0F
} else if (heightOffset > outerSize.height - barSize.height) {
heightOffset = (outerSize.height - barSize.height)
}
dragOffset = TwoFloats(
width = dragOffset.width,
height = heightOffset
)
}, orientation = Orientation.Vertical)
)
}
}
}
}
3… 使用
fun main(args: Array<String>) = application {
YCRWindow(
onCloseRequest = {
exitApplication()
},
title = "Test",
state = rememberWindowState(width = 150.ddp, height = 150.ddp, position = WindowPosition.Aligned(Alignment.Center)),
icon = loadSvgPainter("love.svg")
) {
Box(
modifier = Modifier.size(100.ddp, 100.ddp)
) {
UniversalScrollBox(
modifier = Modifier.fillMaxSize()
) {
Row(modifier = Modifier.wrapContentSize()) {
repeat(10) {
Column(modifier = Modifier.wrapContentSize()) {
repeat(10) {
Text(
text = " 123 "
)
}
}
}
}
}
}
}
}
边栏推荐
- 第一个快应用(quickapp)demo
- oracle EBS标准表的后缀解释说明
- PM2 simple use and daemon
- Oracle 11.2.0.3 handles the problem of continuous growth of sysaux table space without downtime
- Oracle 11g uses ords+pljson to implement JSON_ Table effect
- Module not found: Error: Can't resolve './$$_gendir/app/app.module.ngfactory'
- 2021-07-05C#/CAD二次开发创建圆弧(4)
- PHP Session原理简析
- MySQL无order by的排序规则因素
- Use of interrupt()
猜你喜欢
PHP Session原理简析
搭建frp进行内网穿透
ssm垃圾分类管理系统
SSM学生成绩信息管理系统
CAD secondary development object
@Transational踩坑
Only the background of famous universities and factories can programmers have a way out? Netizen: two, big factory background is OK
SQLI-LABS通关(less1)
2021-07-05c /cad secondary development create arc (4)
sqli-labs通关汇总-page4
随机推荐
CAD secondary development object
Oracle RMAN semi automatic recovery script restore phase
php中的二维数组去重
Basic knowledge of software testing
架构设计三原则
sparksql数据倾斜那些事儿
CAD二次开发 对象
JS judge whether the object is empty
oracle-外币记账时总账余额表gl_balance变化(上)
Oracle apex Ajax process + dy verification
mapreduce概念和案例(尚硅谷学习笔记)
数仓模型事实表模型设计
Oracle 11.2.0.3 handles the problem of continuous growth of sysaux table space without downtime
Oracle general ledger balance table GL for foreign currency bookkeeping_ Balance change (Part 1)
php中根据数字月份返回月份的英文缩写
类加载器及双亲委派机制
CRP implementation methodology
Flex Jiugongge layout
ORACLE EBS 和 APEX 集成登录及原理分析
How to call WebService in PHP development environment?