前言
最近在用kotlin多平臺
搓一些小工具,但這玩意的UI基于Jetpack Compose元媚,很多都是安卓風格的組件轧叽,雖然能支持Windows平臺,但用起來很難受刊棕,效率不高炭晒。于是想要什么效果只能自己搓。
實現(xiàn)效果
先看一眼效果
compose的實現(xiàn)思路比較繞甥角,沒有安卓傳統(tǒng)XML來得直接腰埂,也沒有Windows自帶的控件那么省心很多功能都是自帶的。所以這里稍微啰嗦一點講講思路蜈膨。不想了解原理只想抄作業(yè)請直接跳到文末屿笼。
設計思路
compose是聲明式UI,隨著外部聲明的改變來刷新內容翁巍。我們要改變寬度驴一,所以要先定義一個描述寬度的量:
var directoryTreeWidth by remember { mutableStateOf(250.dp) }
使用remember
修飾保持這個值持久化,加上mutableStateOf
之后灶壶,每當這個數值變化肝断,用到它的UI元素就會自動刷新。
然后我們需要一個函數來刷新這個變量的值,由于拖動發(fā)生在控件的內部胸懈,修改值也應該發(fā)生在控件內部担扑,所以這個函數需要在控件里面定義,外部只需要一個回調來接收這個值趣钱。
于是這個控件就需要兩個參數涌献,寬度width
和寬度回調onWidthChange(width:Int)
@Composable
fun DirectoryTree(
items: List<DirectoryItem>, //這個參數是我左側這個目錄樹的內容,不用管
width: Dp,
onWidthChange: (Dp) -> Unit
) {
接下來我們需要監(jiān)聽拖拽
這個事件首有,然后調用回調即可燕垃,使用Modifier
的pointerInput
可以輕松達成:
@Composable
fun DirectoryTree(
items: List<DirectoryItem>,
width: Dp,
onWidthChange: (Dp) -> Unit
) {
var dragOffset by remember { mutableStateOf(0f) } // 緩存拖動偏移量
Box(
Modifier
.width(width)
.fillMaxHeight()
.background(Color.Gray)
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consume() //消費掉事件,避免和點擊事件沖突
dragOffset += dragAmount.x //拖動距離井联,多次拖動要累加
val newWidth = (width + dragOffset.dp).coerceAtLeast(50.dp) //新的寬度卜壕,不能小于50
onWidthChange(newWidth) // 更新寬度
}
}
) {
// 下面是界面里填的假數據,不用管
Column(modifier = Modifier.padding(16.dp)) {
items.forEach { item ->
DirectoryTreeItem(item)
}
}
然后在外部調用它
@Composable
fun FileManagerUI() {
var directoryTreeWidth by remember { mutableStateOf(250.dp) } //這是可拖拽控件的寬度
var previewWidth by remember { mutableStateOf(250.dp) }
// 模擬的目錄樹數據
val directoryTree = listOf(
DirectoryItem("Documents", listOf(DirectoryItem("Text Files"), DirectoryItem("PDFs"))),
DirectoryItem("Images", listOf(DirectoryItem("JPG"), DirectoryItem("PNG"))),
DirectoryItem("Videos", listOf(DirectoryItem("Movies"), DirectoryItem("Clips"))),
)
Column(Modifier.fillMaxSize()) {
// 頂部菜單欄
TopMenuBar()
Row(Modifier.fillMaxSize()) {
// 左側目錄樹
DirectoryTree(
items = directoryTree,
width = directoryTreeWidth,
onWidthChange = { newWidth -> directoryTreeWidth = newWidth } //拿到回調的數據刷新寬度
)
// 中間文件列表
FileList()
// 右側預覽窗口
PreviewWindow(
width = previewWidth,
onWidthChange = { newWidth -> previewWidth = newWidth }
)
}
}
}
效果如圖
但這個效果還是不符合Windows的使用習慣烙常。Windows的習慣是在邊緣放一個柄
轴捎,鼠標放上去以后變成拉伸圖標,然后長按才能拖拽改變寬度蚕脏。所以接下來讓我們加一個柄
:
@Composable
fun DirectoryTree(
items: List<DirectoryItem>,
width: Dp,
onWidthChange: (Dp) -> Unit
) {
var dragOffset by remember { mutableStateOf(0f) } // 緩存拖動偏移量
Box(
Modifier
.width(width)
.fillMaxHeight()
.background(Color.Gray)
) {
Column(modifier = Modifier.padding(16.dp)) {
items.forEach { item ->
DirectoryTreeItem(item)
}
}
// 右側拖動柄轮蜕,只有在這個區(qū)域可以拖動
Box(
modifier = Modifier
.align(Alignment.CenterEnd) // 右對齊
.width(4.dp) // 拖動柄的寬度
.fillMaxHeight() // 高度占滿父容器
.pointerInput(Unit) { //把拖動事件搬到柄上
detectDragGestures { change, dragAmount ->
change.consume() //消費掉事件,避免和點擊事件沖突
dragOffset += dragAmount.x //拖動距離蝗锥,多次拖動要累加
val newWidth = (width + dragOffset.dp).coerceAtLeast(50.dp) //新的寬度跃洛,不能小于50
onWidthChange(newWidth) // 更新寬度
}
}
.pointerHoverIcon(PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR))) // 設置鼠標樣式為拉伸圖標
.background(Color.LightGray) // 給拖動柄加個背景色
)
}
}
大功告成!現(xiàn)在效果就是文章開頭的樣子了终议,一個符合Windows操作習慣的可拖動組件做好了汇竭。