JavaFX七巧板游戏:布局控件
用于布局的控件
JavaFX七巧板游戏:布局入门到放弃一文把用于布局的窗格(Pane
)拉了一遍,里面提到有一些控件,也有布局的作用。
明显,控件与窗格一样,是Region
的子类;与窗格不同的是,控件有自己的UI元素。
下图是控件的子类结构,在右边的框中的类,就是用于布局的控件:
ToolBar
ButtonBar
SplitPane
Pagination
Accordion
ScrollPane
(为啥打个 × \times ×呢,因为开头我觉得可以不用显式调用这个,后来发现自己太年轻了)TabPane
TitledPane
这里,我想用一个小界面演示程序,把这几个类展示一下。
代码在:gitcode库,可以直接git clone之后,用IDEA或者Gradle run运行。
程序的变态样子
因为这个程序仅作演示用,所有点变态……我自己都被自己吓坏了……
源代码
整个程序的源代码如下。我们从后至前(看程序就应该从后至前,从抽象到具体……从无聊到有趣,千万不要按顺序读……)简单讲讲,要搞清楚用户界面,还是应该运行起来,点一点,再次强调gitcode库。
package org.cardc
import javafx.application.Application
import javafx.geometry.Insets
import javafx.geometry.Side
import javafx.scene.Scene
import javafx.scene.control.*
import javafx.scene.layout.BorderPane
import javafx.scene.layout.Pane
import javafx.scene.layout.Priority
import javafx.scene.layout.VBox
import javafx.scene.paint.Color
import javafx.scene.shape.Circle
import javafx.stage.Stage
import kotlin.math.sqrt
import kotlin.reflect.KVisibility
fun getPane(color: String) = Pane().apply {
style = "-fx-background-color: $color;"
setPrefSize(200.0, 200.0)
children.addAll(
Circle(100.0, 50.0, 50.0, Color.RED),
Circle(50.0, 50 * (1 + sqrt(3.0)), 50.0, Color.YELLOW),
Circle(150.0, 50 * (1 + sqrt(3.0)), 50.0, Color.BLUE)
)
}
fun javafxColors() =
Color::class.members.filter { it.isFinal && it.visibility == KVisibility.PUBLIC && it.parameters.isEmpty() }
.map { it.name.lowercase() }
val colors = javafxColors().sorted().reversed().toTypedArray()
fun panes() = colors.map { getPane(it) }.toTypedArray()
fun buttonData() = ButtonBar.ButtonData.values().sorted().map {
Button(it.name).apply {
ButtonBar.setButtonData(this, it)
}
}.toTypedArray()
fun toolBarDemo() = ToolBar(
*buttonData()
)
fun buttonBarDemo() = ButtonBar().apply {
buttons.addAll(buttonData())
}
fun splitPaneDemo() = SplitPane().apply {
val panes = panes()
items.addAll(panes[0], panes[1], panes[2])
setDividerPositions(0.3, 0.8)
}
fun paginationDemo() = Pagination(panes().size, 0).apply {
setPageFactory {
panes()[it]
}
}
fun accordionDemo() = Accordion().apply {
panes.addAll(panes().mapIndexed { i, p ->
TitledPane("TitledPane: ${colors[i]}", p)
})
}
fun tabPaneDemo() = TabPane().apply {
side = Side.BOTTOM
tabClosingPolicy = TabPane.TabClosingPolicy.UNAVAILABLE
tabs.addAll(panes().mapIndexed { i, p ->
Tab("Tab: ${colors[i]}", p)
})
}
class ControlsLearningApplication : Application() {
override fun start(primaryStage: Stage) {
primaryStage.scene = Scene(
BorderPane().apply {
padding = Insets(2.0)
top = toolBarDemo()
center = VBox().apply {
padding = Insets(10.0)
children.addAll(splitPaneDemo().apply {
VBox.setVgrow(this, Priority.ALWAYS)
}, tabPaneDemo())
}
bottom = buttonBarDemo().apply { padding = Insets(10.0) }
left = paginationDemo().apply { padding= Insets(10.0) }
right = ScrollPane(accordionDemo()).apply { padding = Insets(10.0) }
}, 1440.0, 900.0
)
primaryStage.title = "Controls that layout children..."
primaryStage.show()
}
}
fun main() {
Application.launch(ControlsLearningApplication::class.java)
}
程序界面主体
程序主体,入口函数main
,调用ControlsLearningApplication
。这个Application
的子类重载了start
方法,构造一个Scene
,这个剧场的根节点是一个BorderPane
(参考:JavaFX七巧板游戏:布局入门到放弃)。
class ControlsLearningApplication : Application() {
override fun start(primaryStage: Stage) {
primaryStage.scene = Scene(
BorderPane().apply {
padding = Insets(2.0)
top = toolBarDemo()
center = VBox().apply {
padding = Insets(10.0)
children.addAll(splitPaneDemo().apply {
VBox.setVgrow(this, Priority.ALWAYS)
}, tabPaneDemo())
}
bottom = buttonBarDemo().apply { padding = Insets(10.0) }
left = paginationDemo().apply { padding= Insets(10.0) }
right = ScrollPane(accordionDemo()).apply { padding = Insets(10.0) }
}, 1440.0, 900.0
)
primaryStage.title = "Controls that layout children..."
primaryStage.show()
}
}
fun main() {
Application.launch(ControlsLearningApplication::class.java)
}
所以主界面的五个部分(我是讲真话的好程序员:只会布局三板斧:BorderPane
, VBox
, HBox
):
- top:
toolBarDemo
- bottom:
buttonBarDemo
- left:
paginationDemo
- right:
ScrollPane
包一个accordionDemo
- center:
VBox
包splitPaneDemo
和tabPaneDemo
包含了前面提到的,用于界面布局的所有控件。
ToolBar
和ButtonBar
这两货都是Bar,最终体现在UI上,就是一条。但是这两的目的和布局方式都不一样。
ToolBar
是一个水平或者垂直展示items
的控件。最常见的items
包括Button
,ToggleButton
和Separator
,但是也不仅仅包括这三类,只要是Node
都可以加入。
如果items
的数目过多,就会显示一个额外的按钮可以找到被隐藏的部分。
ButtonBar
本质上是一个HBox
,这个控件试图解决的是操作系统特定的按钮顺序问题。通过buttons
将Node
对象放到这个控件上,可以通过setButtonData(Node, ButtonData)
来设定标记,按钮之间的相对顺序就会被自动排列……这个功能我没看到有多大用处……
fun toolBarDemo() = ToolBar(
*buttonData()
)
fun buttonBarDemo() = ButtonBar().apply {
buttons.addAll(buttonData())
}
这两个函数非常简单,一个调用构造函数,把一个数组展开作为参数;另外一个调用buttons.addAll
来增加。这样就需要看看buttonData
函数:
fun buttonData() = ButtonBar.ButtonData.values().sorted().map {
Button(it.name).apply {
ButtonBar.setButtonData(this, it)
}
}.toTypedArray()
这个函数把,ButtonData
所有的值遍历排序,用名字作为按钮的标签,把标识设好,最后把按钮列表转换成按钮数组。这是Kotlin的典型操作方式,不要自己去用循环,直接用函数参数来做。
从界面上看,ToolBar
中的按钮大小不一,ButtonBar
的按钮默认尺寸相同,但是如果窗口宽度变小后,前者显示一个扩展按钮,后者的按钮直接被遮挡。
Pagination
和TabPane
fun paginationDemo() = Pagination(panes().size, 0).apply {
setPageFactory {
panes()[it]
}
}
fun tabPaneDemo() = TabPane().apply {
side = Side.BOTTOM
tabClosingPolicy = TabPane.TabClosingPolicy.UNAVAILABLE //是否可以关闭标签。
tabs.addAll(panes().mapIndexed { i, p ->
Tab("Tab: ${colors[i]}", p)
})
}
这两个东西很像,都是提供一个导航来访问一系列页面。前者页面的编号是数字,自动生成一个前后导航的UI元素;后者更好理解,就是标签,自己设置标签名称。
Pagination
的机制是采用工厂方法,对应每一个标签编号,选择对应的内容。
TabPane
采用tabs
属性管理,可以增加和删除标签,甚至可以绑定属性,自动更新标签,这个实际上是一个很重的控件,能够实现很复杂的功能。
上面是Pagination
,下面是TabPane
。
SplitPane
这个控件是一个分栏的布局形式。
fun splitPaneDemo() = SplitPane().apply {
val panes = panes()
items.addAll(panes[0], panes[1], panes[2])
setDividerPositions(0.3, 0.8)
}
增加items
,设置初始的分界位置,0.0到1.0之间。比较简单。
Accordion
这是一个折叠标签控件。
fun accordionDemo() = Accordion().apply {
panes.addAll(panes().mapIndexed { i, p ->
TitledPane("TitledPane: ${colors[i]}", p)
})
}
这里可以看到,panes
必须放TitledPane
,这个TitledPane
实际上一个Labeled
控件……标签的名字显示在折叠标签中,每次只能打开一个。
ScrollPane
这个控件多半是自动的……。我们这个程序的中就把Accordion
放在一个`ScrollPane``中。它会自动在需要的时候增加滚动条。
right = ScrollPane(accordionDemo()).apply { padding = Insets(10.0) }
panes
函数、colors
及其他
往上看程序,最后就剩下几个函数,功能都比较简单,但是使用了kotlin之后就特别美好……
// 设置背景颜色,绘制三原色的三个圆形
fun getPane(color: String) = Pane().apply {
style = "-fx-background-color: $color;"
setPrefSize(200.0, 200.0)
children.addAll(
Circle(100.0, 50.0, 50.0, Color.RED),
Circle(50.0, 50 * (1 + sqrt(3.0)), 50.0, Color.YELLOW),
Circle(150.0, 50 * (1 + sqrt(3.0)), 50.0, Color.BLUE)
)
}
// 把JavaFX定义的所有有名字的颜色找出来
fun javafxColors() =
Color::class.members.filter { it.isFinal && it.visibility == KVisibility.PUBLIC && it.parameters.isEmpty() }
.map { it.name.lowercase() }
// 排序,转换为数组
val colors = javafxColors().sorted().reversed().toTypedArray()
// 每一个颜色产生一个Pane
fun panes() = colors.map { getPane(it) }.toTypedArray()
尾声
基本上,用于布局的控件就这么几个。结合JavaFX七巧板游戏:布局入门到放弃中介绍的用于布局的窗格,JavaFX的七巧板游戏就是这么多内容,组合来组合去,就能构成任意复杂的界面。