在我们先前的文章"如何在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…
\
\
\