当前位置:网站首页>使用 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 "
)
}
}
}
}
}
}
}
}
边栏推荐
- How to call WebService in PHP development environment?
- Cve-2015-1635 (ms15-034) Remote Code Execution Vulnerability recurrence
- 数仓模型事实表模型设计
- sparksql数据倾斜那些事儿
- Oracle APEX 21.2 installation et déploiement en une seule touche
- Principle analysis of spark
- php中计算树状结构数据中的合计
- UEditor . Net version arbitrary file upload vulnerability recurrence
- CRP实施方法论
- Go common compilation fails
猜你喜欢

Basic knowledge of software testing

Sqli labs customs clearance summary-page2

UEditor .Net版本任意文件上传漏洞复现

JSP intelligent community property management system

ORACLE EBS 和 APEX 集成登录及原理分析

Flex Jiugongge layout

Analysis of MapReduce and yarn principles

Proteus -- RS-232 dual computer communication

Network security -- intrusion detection of emergency response

SQLI-LABS通關(less6-less14)
随机推荐
ORACLE EBS中消息队列fnd_msg_pub、fnd_message在PL/SQL中的应用
Basic knowledge of software testing
Network security -- intrusion detection of emergency response
php中在二维数组中根据值返回对应的键值
ssm人事管理系统
oracle EBS标准表的后缀解释说明
sparksql数据倾斜那些事儿
php中树形结构转数组(拉平树结构,保留上下级排序)
ORACLE APEX 21.2安裝及一鍵部署
ORACLE EBS接口开发-json格式数据快捷生成
ssm+mysql实现进销存系统
Explanation and application of annotation and reflection
Sqli labs customs clearance summary-page2
Build FRP for intranet penetration
Oracle EBS数据库监控-Zabbix+zabbix-agent2+orabbix
Cve-2015-1635 (ms15-034) Remote Code Execution Vulnerability recurrence
ssm垃圾分类管理系统
Cloud picture says | distributed transaction management DTM: the little helper behind "buy buy buy"
JS delete the last character of the string
DNS攻击详解