Calqlatr
一款专为横屏和竖屏设备设计的计算器。其用户界面采用Qt Quick 自定义组件和响应式布局,应用逻辑则由JavaScript实现。

Calqlatr展示了各种 QML 和 Qt Quick 功能,例如显示自定义组件和使用响应式布局。应用程序逻辑采用 JavaScript 实现,用户界面则采用 QML 实现。
运行示例
您可以通过以下方式运行该示例:
- Qt Creator
打开Welcome 模式,并从Examples 中选择该示例。有关更多信息,请参阅Qt Creator :教程:构建与运行。
- Qt Extension for Visual Studio Code
在Command Palette 中运行Qt: Open Qt examples 命令,并从列表中选择该示例。有关更多信息,请参阅Qt Extension for Visual Studio Code :教程:构建和运行。
运行示例后,您应该能够像使用标准计算器一样使用该应用程序。将手机从纵向模式切换为横向模式——或者在桌面端调整主窗口大小——以激活自适应布局。
使用自定义组件
Calqlatr应用程序使用了自定义类型。其中大部分在各自独立的 .qml 文件中定义:
Main.qmlcontent/ApplicationState.qmlcontent/BackspaceButton.qmlcontent/CalculatorButton.qmlcontent/Display.qmlcontent/NumberPad.qml
Main.qml 包含顶级窗口和根项。它使用了content 目录中的其他自定义类型。例如,NumberPad 类型(由content/NumberPad.qml 定义)在Main.qml 中用于创建计算器的数字键盘:
NumberPad {
id: numberPad
Layout.margins: root.margin
isPortraitMode: root.isPortraitMode
applicationState: appState
}内联组件允许您在.qml 文件内声明多个组件。该示例在NumberPad.qml 中使用内联组件定义了两个组件:DigitButton 和OperatorButton :
component DigitButton: CalculatorButton {
onClicked: {
controller.applicationState.digitPressed(text);
controller.updateDimmed();
}
}
component OperatorButton: CalculatorButton {
dimmable: true
implicitWidth: 48
textColor: controller.qtGreenColor
onClicked: {
controller.applicationState.operatorPressed(text);
controller.updateDimmed();
}
}这两个组件的类型均为CalculatorButton (如CalculatorButton.qml 中所定义),但为 clicked 信号提供了自定义处理程序,并调整了一些属性的默认值。随后在NumberPad.qml 中实例化了DigitButton 和OperatorButton :
DigitButton {
text: "e"
dimmable: true
implicitWidth: 48
}
OperatorButton { text: "ln" }有关定义自定义 QML 组件的更多详细信息,请参阅《通过 QML 文档定义对象类型》。
响应式布局
在此示例中,响应式布局会根据纵向和横向模式调整不同 UI 组件的排列方式。可在Main.qml 中看到这一点,该文件定义了纵向模式下的ColumnLayout 以及横向模式下的RowLayout :
ColumnLayout {
id: portraitMode
anchors.fill: parent
visible: root.isPortraitMode
LayoutItemProxy {
target: calcDisplay
Layout.minimumHeight: calcDisplay.minHeight
}
LayoutItemProxy {
target: numberPad
Layout.alignment: Qt.AlignHCenter
}
}
RowLayout {
id: landscapeMode
anchors.fill: parent
visible: !root.isPortraitMode
LayoutItemProxy {
target: calcDisplay
}
LayoutItemProxy {
target: numberPad
Layout.alignment: Qt.AlignVCenter
}
}ColumnLayout 代表应用程序的纵向布局,RowLayout 代表横向布局。visible 属性负责在特定时刻决定使用哪种布局。NumberPad 和Display 组件的id属性用于设置LayoutItemProxy 类型的target 属性。这使得两种布局都能使用相同的内容项。此外,可以在LayoutItemProxy 项内将属性转发给target 本身。例如,当NumberPad 被实例化时,两种布局都需要不同的Layout.alignment 。
在NumberPad.qml 中,定义NumberPad 本身的纵向和横向布局时,也使用了响应式布局:
RowLayout {
spacing: controller.spacing
GridLayout {
id: scientificGrid
columns: 3
columnSpacing: controller.spacing
rowSpacing: controller.spacing
visible: !controller.isPortraitMode
OperatorButton {
text: "x²"
Accessible.name: "x squared"
}
OperatorButton {
text: "⅟x"
Accessible.name: "one over x"
}
OperatorButton { text: "√" }
OperatorButton {
text: "x³"
Accessible.name: "x cubed"
}
OperatorButton {
text: "sin"
Accessible.name: "sine"
}
OperatorButton {
text: "|x|"
Accessible.name: "absolute value"
}
OperatorButton { text: "log" }
OperatorButton {
text: "cos"
Accessible.name: "cosine"
}
DigitButton {
text: "e"
dimmable: true
implicitWidth: 48
}
OperatorButton { text: "ln" }
OperatorButton { text: "tan" }
DigitButton {
text: "π"
dimmable: true
implicitWidth: 48
}
}
GridLayout {
id: mainGrid
columns: 5
columnSpacing: controller.spacing
rowSpacing: controller.spacing
BackspaceButton {
onClicked: {
controller.applicationState.operatorPressed(this.text);
controller.updateDimmed();
}
}
DigitButton { text: "7" }
DigitButton { text: "8" }
DigitButton { text: "9" }
OperatorButton {
text: "÷"
implicitWidth: 38
}
OperatorButton {
text: "AC"
textColor: controller.backspaceRedColor
accentColor: controller.backspaceRedColor
}
DigitButton { text: "4" }
DigitButton { text: "5" }
DigitButton { text: "6" }
OperatorButton {
text: "×"
implicitWidth: 38
}
OperatorButton {
text: "="
implicitHeight: 81
Layout.rowSpan: 2
}
DigitButton { text: "1" }
DigitButton { text: "2" }
DigitButton { text: "3" }
OperatorButton {
text: "−"
implicitWidth: 38
}
OperatorButton {
text: "±"
implicitWidth: 38
}
DigitButton { text: "0" }
DigitButton {
text: "."
dimmable: true
}
OperatorButton {
text: "+"
implicitWidth: 38
}
}
} // RowLayout根据controller.isPortraitMode 的不同,数字键盘可能仅包含简单计算器(mainGrid )的按钮,也可能通过scientificGrid 提供一些更高级的功能。
按钮文字颜色的动画效果
在CalculatorButton.qml 文件中,数字小键盘按钮的文字颜色也会进行动画效果。
...
color: button.getTextColor()
Behavior on color {
ColorAnimation {
duration: 120
easing.type: Easing.OutElastic
}
}
}通过为color 属性定义Behavior 来实现颜色变化的动画效果。当按钮设置为dimmed = true 时,按钮会显示为较深的颜色;按下按钮时,按钮会亮起绿色。为了动态更改NumberPad 上所有按钮的dimmed 属性,buttonPressed 信号会调用NumberPad 的updateDimmed() 函数。
function updateDimmed() {
for (let i = 0; i < mainGrid.children.length; i++) {
mainGrid.children[i].dimmed = applicationState.isButtonDisabled(mainGrid.children[i].text);
}
for (let j = 0; j < scientificGrid.children.length; j++) {
scientificGrid.children[j].dimmed = applicationState.isButtonDisabled(
scientificGrid.children[j].text);
}
}执行计算
calculator.js 和ApplicationState.qml 文件定义了计算器的核心引擎。calculator.js 包含计算器的逻辑状态,以及用于更改状态的操作。ApplicationState.qml 通过一个QML类型暴露了该API。
让我们先来看看 `calculator.js`:
let accumulator = 0
let pendingOperator = ""
let lastButton = ""
let digits = ""accumulator,pendingOperator,lastButton,represent 计算器的逻辑状态。
function isOperationDisabled(op, display) {
...
}isOperationDisabled() 根据当前引擎和显示状态,如果应禁用某项运算(op ),则返回true ;否则,返回false 。
function digitPressed(op, display) {
...
}
function operatorPressed(op, display) {
...
}digitPressed() 而operatorPressed() 函数会根据按下的按钮更新引擎和显示状态。
ApplicationState.qml 通过更友好的 QML API 暴露了在calculator.js 中定义的 JavaScript 函数:
import QtQml
import "calculator.js" as CalcEnginecalculator.js 该功能以CalcEngine 为名称被导入。
QtObject {
required property Display display
function operatorPressed(operator) {
CalcEngine.operatorPressed(operator, display);
}
function digitPressed(digit) {
CalcEngine.digitPressed(digit, display);
}
function isButtonDisabled(op) {
return CalcEngine.isOperationDisabled(op, display);
}
}required property 确保在ApplicationState 中始终可以通过名称display 访问Display.qml 的实例。display 随后被用于operatorPressed() 、digitPressed() 和isButtonDisabled() 函数中。
源文件
Squish GUI 测试
该应用程序附带针对 Qt for Android 的Squish GUI 测试。这些测试使用Squish for Qt生成,采用 Python 编写,位于应用程序的 test 目录下。
在使用 Squish for Qt for Android 时,请确保使用Qt 内置的钩子,并通过 adb转发 Squish 端口。如果运行dlopen 时出现“无法加载库”的错误,请在 CMake 配置中禁用QT_USE_TARGET_ANDROID_BUILD_DIR ,或从Qt Creator 中的Projects->Build Settings->CMake->Current Configuration 文件中将其删除。
如果您没有 Squish 许可证,可以申请免费试用。
另请参阅 QML 应用程序。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.