JavaFX/TornadoFX 中的按钮边框半径过渡
Button border-radius transition in JavaFX/TornadoFX
我正在尝试使我的按钮在悬停时具有动画效果,例如 Discord 的按钮。
我可以更改背景颜色和边框半径,但不能流畅地设置动画。
我只能找到动画形状的示例,而不是 css 属性。
这是我的 css 按钮代码。
class NavigatorButtonViewCss: Stylesheet() {
companion object {
val face by cssclass()
val buttonBackgroundColor = c("#36393F")
val buttonHoverBackgroundColor = c("#7289DA")
val textColor = c("#C8C9CB")
}
init {
indicator {
prefWidth = 10.px
}
face {
prefWidth = 50.px
prefHeight = 50.px
backgroundColor += buttonBackgroundColor
backgroundRadius = multi(box(50.percent))
label {
textFill = textColor
}
and(hover) {
// I want this to be animated
backgroundColor += buttonHoverBackgroundColor
backgroundRadius = multi(box(35.percent))
}
}
}
}
现在我的按钮
我想要的
有什么方法可以实现这种转变吗?
谢谢。
JavaFX 不允许您通过 CSS 创建任何类型的动画,据我所知,即使使用 TornadoFX 也无法应用动画。 (请注意,我今天刚开始使用 TornadoFX,所以我可能是错的。)
通过样式表获取动画的唯一方法是将 skin
设置为实现角动画的自定义皮肤。虽然您可以让皮肤提供 CSS 属性 控制角的圆度。
通常你会扩展 Button
添加一个 属性,但在这种情况下我只是将它存储在 properties
映射中。
要分配给按钮的外观
package org.example
import com.sun.javafx.scene.control.skin.ButtonSkin
import javafx.beans.binding.Bindings
import javafx.beans.binding.DoubleBinding
import javafx.beans.property.DoubleProperty
import javafx.beans.property.SimpleDoubleProperty
import java.util.function.Function
import javafx.css.SimpleStyleableBooleanProperty
import javafx.css.CssMetaData
import javafx.css.StyleablePropertyFactory
import javafx.css.Styleable
import javafx.css.StyleableProperty
import javafx.scene.control.Button
import javafx.scene.shape.ArcTo
import javafx.scene.shape.ClosePath
import javafx.scene.shape.HLineTo
import javafx.scene.shape.MoveTo
import javafx.scene.shape.Path
import javafx.scene.shape.VLineTo
import tornadofx.*
class AnimatedButtonSkin(button: Button) : ButtonSkin(button) {
companion object {
@JvmField
val CSS_ROUNDED_KEY = "org.example.AnimatedButtonSkin.rounded"
@JvmField
val CSS_ROUNDED_METADATA: CssMetaData<Button, Boolean>
@JvmField
val FACTORY = StyleablePropertyFactory<Button>(javafx.scene.control.SkinBase.getClassCssMetaData())
init {
CSS_ROUNDED_METADATA = FACTORY.createBooleanCssMetaData(
"-fx-rounded",
object : Function<Button, StyleableProperty<kotlin.Boolean>> {
override fun apply(b: Button): StyleableProperty<Boolean> {
// property stored in properties to avoid extending button
val v = b.getProperties().get(CSS_ROUNDED_KEY)
return v as StyleableProperty<Boolean>
}
},
true
)
}
}
override fun dispose() {
// get rid of the property and the shape
val b = getSkinnable()
b.getProperties().remove(CSS_ROUNDED_KEY)
b.setShape(null)
super.dispose()
}
private fun createArc(
cornerSizeH: DoubleBinding,
cornerSizeV: DoubleBinding,
invertX: Boolean,
invertY: Boolean
): ArcTo {
return ArcTo().apply {
setAbsolute(false)
setSweepFlag(true)
radiusXProperty().bind(cornerSizeH)
radiusYProperty().bind(cornerSizeV)
xProperty().bind(if (invertX) cornerSizeH.negate() else cornerSizeH)
yProperty().bind(if (invertY) cornerSizeV.negate() else cornerSizeV)
}
}
override fun getCssMetaData(): List<CssMetaData<out Styleable, *>>? {
return FACTORY.getCssMetaData()
}
init {
val prop = SimpleStyleableBooleanProperty(CSS_ROUNDED_METADATA, true)
button.getProperties().put(CSS_ROUNDED_KEY, prop)
// relative part of width/height that is rounded
// size for single corner:
// 0 -> rectangular button
// 0.5 -> circular button
val cornerSize = SimpleDoubleProperty(.5)
val w = button.widthProperty()
val h = button.heightProperty()
// bindings for horizontal measures
val cornerHSize = w.multiply(cornerSize)
val doubleHCornerSize = cornerHSize.multiply(2.0);
// measures for vertical measures
val cornerVSize = h.multiply(cornerSize)
val doubleVCornerSize = cornerVSize.multiply(2.0);
// lower part of the top-left corner
val start = MoveTo().apply {
yProperty().bind(cornerSize);
}
// straight path of top
val top = HLineTo().apply {
setAbsolute(false)
xProperty().bind(w.subtract(doubleHCornerSize))
}
// straight part of the right
var right = VLineTo().apply {
setAbsolute(false)
yProperty().bind(h.subtract(doubleVCornerSize))
}
// straight part of the bottom
val bottom = HLineTo().apply {
setAbsolute(false)
xProperty().bind(top.xProperty().negate())
}
// assemble the parts
val shape = Path(
start,
createArc(cornerHSize, cornerVSize, false, true), top,
createArc(cornerHSize, cornerVSize, false, false), right,
createArc(cornerHSize, cornerVSize, true, false), bottom,
createArc(cornerHSize, cornerVSize, true, true), ClosePath()
)
button.shape = shape
// animate open/close on change of stylable property
prop.addListener({ _, _, new -> cornerSize.animate(endValue = if (new) .5 else .2, duration = .2.seconds) })
}
}
风格
class NavigatorButtonViewCss: Stylesheet() {
companion object {
val face by cssclass()
val rounded by cssproperty<Boolean>("-fx-rounded")
val buttonBackgroundColor = c("#36393F")
val buttonHoverBackgroundColor = c("#7289DA")
val textColor = c("#C8C9CB")
}
init {
indicator {
prefWidth = 10.px
}
face {
prefWidth = 50.px
prefHeight = 50.px
backgroundColor += buttonBackgroundColor
textFill = textColor
skin = AnimatedButtonSkin::class
and(hover) {
rounded.value = false // update corners
backgroundColor += buttonHoverBackgroundColor
}
}
}
}
我正在尝试使我的按钮在悬停时具有动画效果,例如 Discord 的按钮。
我可以更改背景颜色和边框半径,但不能流畅地设置动画。 我只能找到动画形状的示例,而不是 css 属性。
这是我的 css 按钮代码。
class NavigatorButtonViewCss: Stylesheet() {
companion object {
val face by cssclass()
val buttonBackgroundColor = c("#36393F")
val buttonHoverBackgroundColor = c("#7289DA")
val textColor = c("#C8C9CB")
}
init {
indicator {
prefWidth = 10.px
}
face {
prefWidth = 50.px
prefHeight = 50.px
backgroundColor += buttonBackgroundColor
backgroundRadius = multi(box(50.percent))
label {
textFill = textColor
}
and(hover) {
// I want this to be animated
backgroundColor += buttonHoverBackgroundColor
backgroundRadius = multi(box(35.percent))
}
}
}
}
现在我的按钮
我想要的
有什么方法可以实现这种转变吗?
谢谢。
JavaFX 不允许您通过 CSS 创建任何类型的动画,据我所知,即使使用 TornadoFX 也无法应用动画。 (请注意,我今天刚开始使用 TornadoFX,所以我可能是错的。)
通过样式表获取动画的唯一方法是将 skin
设置为实现角动画的自定义皮肤。虽然您可以让皮肤提供 CSS 属性 控制角的圆度。
通常你会扩展 Button
添加一个 属性,但在这种情况下我只是将它存储在 properties
映射中。
要分配给按钮的外观
package org.example
import com.sun.javafx.scene.control.skin.ButtonSkin
import javafx.beans.binding.Bindings
import javafx.beans.binding.DoubleBinding
import javafx.beans.property.DoubleProperty
import javafx.beans.property.SimpleDoubleProperty
import java.util.function.Function
import javafx.css.SimpleStyleableBooleanProperty
import javafx.css.CssMetaData
import javafx.css.StyleablePropertyFactory
import javafx.css.Styleable
import javafx.css.StyleableProperty
import javafx.scene.control.Button
import javafx.scene.shape.ArcTo
import javafx.scene.shape.ClosePath
import javafx.scene.shape.HLineTo
import javafx.scene.shape.MoveTo
import javafx.scene.shape.Path
import javafx.scene.shape.VLineTo
import tornadofx.*
class AnimatedButtonSkin(button: Button) : ButtonSkin(button) {
companion object {
@JvmField
val CSS_ROUNDED_KEY = "org.example.AnimatedButtonSkin.rounded"
@JvmField
val CSS_ROUNDED_METADATA: CssMetaData<Button, Boolean>
@JvmField
val FACTORY = StyleablePropertyFactory<Button>(javafx.scene.control.SkinBase.getClassCssMetaData())
init {
CSS_ROUNDED_METADATA = FACTORY.createBooleanCssMetaData(
"-fx-rounded",
object : Function<Button, StyleableProperty<kotlin.Boolean>> {
override fun apply(b: Button): StyleableProperty<Boolean> {
// property stored in properties to avoid extending button
val v = b.getProperties().get(CSS_ROUNDED_KEY)
return v as StyleableProperty<Boolean>
}
},
true
)
}
}
override fun dispose() {
// get rid of the property and the shape
val b = getSkinnable()
b.getProperties().remove(CSS_ROUNDED_KEY)
b.setShape(null)
super.dispose()
}
private fun createArc(
cornerSizeH: DoubleBinding,
cornerSizeV: DoubleBinding,
invertX: Boolean,
invertY: Boolean
): ArcTo {
return ArcTo().apply {
setAbsolute(false)
setSweepFlag(true)
radiusXProperty().bind(cornerSizeH)
radiusYProperty().bind(cornerSizeV)
xProperty().bind(if (invertX) cornerSizeH.negate() else cornerSizeH)
yProperty().bind(if (invertY) cornerSizeV.negate() else cornerSizeV)
}
}
override fun getCssMetaData(): List<CssMetaData<out Styleable, *>>? {
return FACTORY.getCssMetaData()
}
init {
val prop = SimpleStyleableBooleanProperty(CSS_ROUNDED_METADATA, true)
button.getProperties().put(CSS_ROUNDED_KEY, prop)
// relative part of width/height that is rounded
// size for single corner:
// 0 -> rectangular button
// 0.5 -> circular button
val cornerSize = SimpleDoubleProperty(.5)
val w = button.widthProperty()
val h = button.heightProperty()
// bindings for horizontal measures
val cornerHSize = w.multiply(cornerSize)
val doubleHCornerSize = cornerHSize.multiply(2.0);
// measures for vertical measures
val cornerVSize = h.multiply(cornerSize)
val doubleVCornerSize = cornerVSize.multiply(2.0);
// lower part of the top-left corner
val start = MoveTo().apply {
yProperty().bind(cornerSize);
}
// straight path of top
val top = HLineTo().apply {
setAbsolute(false)
xProperty().bind(w.subtract(doubleHCornerSize))
}
// straight part of the right
var right = VLineTo().apply {
setAbsolute(false)
yProperty().bind(h.subtract(doubleVCornerSize))
}
// straight part of the bottom
val bottom = HLineTo().apply {
setAbsolute(false)
xProperty().bind(top.xProperty().negate())
}
// assemble the parts
val shape = Path(
start,
createArc(cornerHSize, cornerVSize, false, true), top,
createArc(cornerHSize, cornerVSize, false, false), right,
createArc(cornerHSize, cornerVSize, true, false), bottom,
createArc(cornerHSize, cornerVSize, true, true), ClosePath()
)
button.shape = shape
// animate open/close on change of stylable property
prop.addListener({ _, _, new -> cornerSize.animate(endValue = if (new) .5 else .2, duration = .2.seconds) })
}
}
风格
class NavigatorButtonViewCss: Stylesheet() {
companion object {
val face by cssclass()
val rounded by cssproperty<Boolean>("-fx-rounded")
val buttonBackgroundColor = c("#36393F")
val buttonHoverBackgroundColor = c("#7289DA")
val textColor = c("#C8C9CB")
}
init {
indicator {
prefWidth = 10.px
}
face {
prefWidth = 50.px
prefHeight = 50.px
backgroundColor += buttonBackgroundColor
textFill = textColor
skin = AnimatedButtonSkin::class
and(hover) {
rounded.value = false // update corners
backgroundColor += buttonHoverBackgroundColor
}
}
}
}