如何在QML应用中创建类似ContextMenu的控件

120 阅读3分钟

在我们先前的文章"如何在QML应用中创建一个Context Menu",我们使用了一个popup的方法来显示一个我们自己需要的context menu.那个方法虽好,但是显示起来和背景的颜色很相近,不容易看出来.当然我们可以通过一些方法来改进我的设计.在今天的例程中,我们通过另外一种方法来做一个同样的context menu.这个方法的好处是,我们可以随意来设计我们所需要的效果.我们从另外一个角度来实现同样的东西.我们设计的最终效果为:

\

    \

\

我们可以通点击上图中的图标来弹出我们所需要的选项,在选项中,我们可以做出我们的选择.当选择完后,我们可以点击图标收回选项.基于这个设计我们还可以更进一步做更多的改进.比如点击区域以外的地方自动让选项列表消失.当然,我们也可以加入我们的动画效果.

\

上面图中的一个图标及一个文字,我们是通过一个AbstractButton控件来实现的.当然,我们也可以采用一个ListView来实现.

\

OptionValueButton.qml

\

import QtQuick 2.4
import Ubuntu.Components 1.3

AbstractButton {
    id: optionValueButton

    implicitHeight: units.gu(5)

    property alias label: label.text
    property alias iconName: icon.name
    property bool selected
    property bool isLast
    property int columnWidth
    property int marginSize: units.gu(1)

    width: marginSize + iconLabelGroup.width + marginSize

    Item {
        id: iconLabelGroup
        width: childrenRect.width
        height: icon.height

        anchors {
            left: (iconName) ? undefined : parent.left
            leftMargin: (iconName) ? undefined : marginSize
            horizontalCenter: (iconName) ? parent.horizontalCenter : undefined
            verticalCenter: parent.verticalCenter
            topMargin: marginSize
            bottomMargin: marginSize
        }

        Icon {
            id: icon
            anchors {
                verticalCenter: parent.verticalCenter
                left: parent.left
            }
            width: optionValueButton.height - optionValueButton.marginSize * 2
            color: "white"
            opacity: optionValueButton.selected ? 1.0 : 0.5
            visible: name !== ""
        }

        Label {
            id: label
            anchors {
                left: icon.name != "" ? icon.right : parent.left
                verticalCenter: parent.verticalCenter
                leftMargin: units.gu(0.5)
            }

            color: "white"
            opacity: optionValueButton.selected ? 1.0 : 0.5
            width: paintedWidth
        }
    }

    Rectangle {
        anchors {
            left: parent.left
            bottom: parent.bottom
        }
        width: parent.columnWidth
        height: units.dp(1)
        color: "red"
        opacity: 0.5
        visible: true
    }
}


我们的Main.qml设计也比较简单.只是在这里面,我们使用了Item中 mapFromItem来得到我们点击的控件的位置信息.利用这个位置信息,我们来定位我们的选项的optionValueSelector位置.整个设计如下:

\

Main.qml

\

import QtQuick 2.4
import Ubuntu.Components 1.3

/*!
    \brief MainView with a Label and Button elements.
*/

MainView {
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "mapfromitem.liu-xiao-guo"

    width: units.gu(60)
    height: units.gu(85)
    theme.name :"Ubuntu.Components.Themes.SuruDark"
    property bool optionValueSelectorVisible: false

    Page {
        header: PageHeader {
            id: pageHeader
            title: i18n.tr("mapfromitem")
        }

        ListModel {
            id: model
            property int selectedIndex: 0

            ListElement {
                icon: "account"
                label: "On"
                value: "flash-on"
            }
            ListElement {
                icon: "active-call"
                label: "Auto"
                value: "Auto"
            }
            ListElement {
                icon: "call-end"
                label: "Off"
                value: "flash-off"
            }
        }

        Column {
            id: optionValueSelector
            objectName: "optionValueSelector"
            width: childrenRect.width
            spacing: units.gu(1)

            property Item caller

            function toggle(model, callerButton) {
                if (optionValueSelectorVisible && optionsRepeater.model === model) {
                    hide();
                } else {
                    show(model, callerButton);
                }
            }

            function show(model, callerButton) {
                optionValueSelector.caller = callerButton;
                optionsRepeater.model = model;
                alignWith(callerButton);
                optionValueSelectorVisible = true;
            }

            function hide() {
                optionValueSelectorVisible = false;
                optionValueSelector.caller = null;
            }

            function alignWith(item) {
                // horizontally center optionValueSelector with the center of item
                // if there is enough space to do so, that is as long as optionValueSelector
                // does not get cropped by the edge of the screen
                var itemX = parent.mapFromItem(item, 0, 0).x;
                var centeredX = itemX + item.width / 2.0 - width / 2.0;
                var margin = units.gu(1);

                if (centeredX < margin) {
                    x = itemX;
                } else if (centeredX + width > item.parent.width - margin) {
                    x = itemX + item.width - width;
                } else {
                    x = centeredX;
                }

                // vertically position the options above the caller button
                y = Qt.binding(function() { return item.y - height - units.gu(2) });

                console.log("x: " + x + " y: " + y)
            }

            visible: opacity !== 0.0
            onVisibleChanged: if (!visible) optionsRepeater.model = null;
            opacity: optionValueSelectorVisible ? 1.0 : 0.0
            Behavior on opacity {UbuntuNumberAnimation {duration: UbuntuAnimation.FastDuration}}

            Repeater {
                id: optionsRepeater

                delegate: OptionValueButton {
                    anchors.left: optionValueSelector.left
                    columnWidth: optionValueSelector.childrenRect.width
                    label: model.label
                    iconName: model.icon
                    selected: optionsRepeater.model.selectedIndex == index
                    isLast: index === optionsRepeater.count - 1
                    onClicked: {
                        optionsRepeater.model.selectedIndex = index
                        console.log(optionsRepeater.model.get(index).value)
                    }
                }
            }
        }

        Icon {
            id: optionButton
            width: units.gu(3)
            height: width
            anchors.centerIn: parent
            name: model.get(model.selectedIndex).icon

            MouseArea {
                anchors.fill: parent
                onClicked: {
                    console.log("optionValueSelectorVisible: " + optionValueSelectorVisible)
                    optionValueSelector.toggle(model, optionButton)
                }
            }
        }

        Component.onCompleted: {
            console.log("width: " + width + " height: " +height)
        }
    }

}

\

在我们的实际应用中,我们可以把它设计为Component供我们其它的应用直接使用.我们可以通过 InverseMouseArea来更进一步完成整个Component的可用性.

\

整个项目的源码在: github.com/liu-xiao-gu…

\

\

\