JavaFX / TornadoFX中的按钮边框-半径过渡

时间:2019-12-14 09:13:11

标签: 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))
            }
        }
    }
}

我的按钮

My button

我想要的

Discord button

有什么方法可以实现这种过渡吗?

谢谢。

1 个答案:

答案 0 :(得分:1)

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
            }
        }
    }
}