JavaFX技巧31:遮罩/剪切/ Alpha通道

选择条

最近,我不得不实现一个自定义控件,该控件使用户可以从项目列表中选择一个项目。 此“ SelectionStrip”控件必须水平放置项目,并且在项目过多的情况下,允许用户左右水平滚动。 该控件将在空间受限的区域中使用,因此仅在需要时才显示滚动按钮。 显示时它们也不应浪费任何额外的空间。 因此,我决定将它们放置在控件左侧和右侧的顶部。 所有这些都很容易实现,只是现在很难区分滚动按钮和项目。 可以在下面的三个图像中看到。

阿尔法频道?

因此,我认为在靠近左侧或右侧边缘时以某种方式淡出项目会很好。 这种行为通常可以通过使用alpha通道来完成。 随着像素到边缘的距离减小,可能会降低像素的不透明度。 好的.....但是在JavaFX中如何完成呢? 在相当长的一段时间里,我一直在研究各种“混合模式”,这些模式可用于定义如何在彼此重叠的位置绘制两个重叠的节点。 但是,这是错误的方向。 事实证明,我已经知道该怎么做,因为我曾经写过一篇博客文章,内容涉及剪辑以及填充和未填充剪辑之间的区别 。 但是我想这太早了,我没有在“填充”和“不透明度小于1”之间建立联系。

复杂剪辑!

到目前为止,我用于自定义控件的大多数剪辑都是简单的矩形。 他们通常确保到达其父控件的布局范围之外的子节点不可见或仅部分可见。 但是此剪辑不同,它更加复杂。 它必须定义三个不同的区域。 左侧为“淡入”区域,中央为“完全不透明”区域,右侧为“淡入”区域。 为此,我定义了一个“组”,它由三个填充的“矩形”节点组成。 中心矩形的填充颜色为纯黑色,而其他两个矩形的填充颜色为从透明到黑色的线性渐变,反之亦然。 下图说明了这一点。

阿尔法频道

通过此设置,我们现在可以将任何节点作为子节点添加到堆栈窗格中,并且将在其侧面用淡入和淡出效果进行绘制。

结果

从一开始就将滚动箭头/按钮应用于“ SelectionStrip”控件后,它现在始终清晰可见,并且总体用户体验更加令人满意。 这些小细节使被认为是“学生项目”或“商业应用程序”的UI有所不同。 因此,有时值得在这些上投入时间。

源代码

我将屏蔽逻辑放入了一个称为“ MaskedView”的自定义控件中。 在本文的底部,您将看到包含此​​控件源代码的Gist(或Gist链接)。 可以将其视为给定内容节点周围的包装器。

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;

public class MaskedView extends Control {

    public MaskedView(Node content) {
        setContent(content);
    }

    @Override
    protected Skin<?> createDefaultSkin() {
        return new MaskedViewSkin(this);
    }

    private final SimpleObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content");

    public final Node getContent() {
        return content.get();
    }

    public final SimpleObjectProperty<Node> contentProperty() {
        return content;
    }

    public final void setContent(Node content) {
        this.content.set(content);
    }

    private final DoubleProperty fadingSize = new SimpleDoubleProperty(this, "fadingSize", 120);

    public final double getFadingSize() {
        return fadingSize.get();
    }

    public final DoubleProperty fadingSizeProperty() {
        return fadingSize;
    }

    public final void setFadingSize(double fadingSize) {
        this.fadingSize.set(fadingSize);
    }
}
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;

public class MaskedViewSkin extends SkinBase {

    private final Rectangle leftClip;
    private final Rectangle rightClip;
    private final Rectangle centerClip;

    private final Group group;

    private final StackPane stackPane;

    public MaskedViewSkin(MaskedView view) {
        super(view);

        leftClip = new Rectangle();
        rightClip = new Rectangle();
        centerClip = new Rectangle();

        centerClip.setFill(Color.BLACK);

        leftClip.setManaged(false);
        centerClip.setManaged(false);
        rightClip.setManaged(false);

        group = new Group(leftClip, centerClip, rightClip);

        stackPane = new StackPane();
        stackPane.setManaged(false);
        stackPane.setClip(group);

        getChildren().add(stackPane);

        view.contentProperty().addListener((observable, oldContent, newContent) -> buildView(oldContent, newContent));
        buildView(null, view.getContent());

        view.widthProperty().addListener(it -> updateClip());

        view.fadingSizeProperty().addListener(it -> updateClip());
    }

    private final InvalidationListener translateXListener = it -> updateClip();

    private final WeakInvalidationListener weakTranslateXListener = new WeakInvalidationListener(translateXListener);

    private void buildView(Node oldContent, Node newContent) {
        if (oldContent != null) {
            stackPane.getChildren().clear();
            oldContent.translateXProperty().removeListener(weakTranslateXListener);
        }

        if (newContent != null) {
            stackPane.getChildren().setAll(newContent);
            newContent.translateXProperty().addListener(weakTranslateXListener);
        }

        updateClip();
    }

    private void updateClip() {
        final MaskedView view = getSkinnable();

        Node content = view.getContent();
        if (content != null) {

            final double fadingSize = view.getFadingSize();

            if (content.getTranslateX() < 0) { leftClip.setFill(new LinearGradient(0, 0, fadingSize, 0, false, CycleMethod.NO_CYCLE, new Stop(0, Color.TRANSPARENT), new Stop(1, Color.BLACK))); } else { leftClip.setFill(Color.BLACK); } if (content.getTranslateX() + content.prefWidth(-1) > view.getWidth()) {
                rightClip.setFill(new LinearGradient(0, 0, fadingSize, 0, false, CycleMethod.NO_CYCLE, new Stop(0, Color.BLACK), new Stop(1, Color.TRANSPARENT)));
            } else {
                rightClip.setFill(Color.BLACK);
            }
        }

        view.requestLayout();
    }

    @Override
    protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {
        final double fadingSize = Math.min(contentWidth / 2, getSkinnable().getFadingSize());
        stackPane.resizeRelocate(snapPosition(contentX), snapPosition(contentY), snapSpace(contentWidth), snapSpace(contentHeight));
        resizeRelocate(leftClip, snapPosition(contentX), snapPosition(contentY), snapSpace(fadingSize), snapSpace(contentHeight));
        resizeRelocate(centerClip, snapPosition(contentX + fadingSize), snapPosition(contentY), snapSpace(contentWidth - 2 * fadingSize), snapSpace(contentHeight));
        resizeRelocate(rightClip, snapPosition(contentX + contentWidth - fadingSize), snapPosition(contentY), snapSpace(fadingSize), snapSpace(contentHeight));
    }

    private void resizeRelocate(Rectangle rect, double x, double y, double w, double h) {
        rect.setLayoutX(x);
        rect.setLayoutY(y);
        rect.setWidth(w);
        rect.setHeight(h);
    }
}

希望您能找到此控件的好用例。

祝大家编码愉快!

翻译自: https://www.javacodegeeks.com/2018/07/javafx-tip-31-masking-clipping-alpha-channel.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值