Eines der Hauptsteuerelemente in einer Android -App ist der Navigation Drawer , aber QML hat keine fertige Komponente für dieses Element, aber die Jungs von Cutehacks hat ihre eigene Navigation Drawer erstellt, der Code dieser Komponente wird auf github veröffentlicht. Ich wollte diesen Code schon lange auf einem funktionierenden Android -Gerät ausführen und habe ihn endlich in die Hände bekommen.
Ich habe diesen Code im Detail studiert und ihn ein wenig optimiert, weil es in dieser Version einige Inkonsistenzen beim Materialdesign gab, da das Panel unabhängig von der Ausrichtung auf 78 Prozent der Bildschirmbreite erweitert wurde. Und Material Design empfiehlt, die Navigationsschublade im Hochformat zu öffnen, damit sie die gegenüberliegende Kante bei 56 Grad bei Smartphones und 64 Grad bei Tablets nicht erreicht, aber wir werden es tun zumindest für Smartphones und im Querformat nicht mehr als 320 Dip breit. Was ich korrigiert habe, indem ich auch einen kleinen Teil des derzeit unnötigen Codes herausgesägt und die Variablen für mich leicht umbenannt habe.
Was den Wert von dip angeht, also Pixel unabhängig von der Bildschirmdichte des Gerätes, so ist das schon eine Frage der richtigen Skalierung von Interface-Elementen .
Ich mache Sie auf ein Beispiel für die Verwendung dieses Navigationsfachs aufmerksam, um drei Fragmente in ein Loader -Objekt umzuwandeln, indem drei Menüelemente verwendet werden, die sich in diesem Navigationsfach befinden werden.
Projektstruktur für die Arbeit mit Navigation Drawer
Die Struktur des Projekts ähnelt der Struktur des Projekts aus dem Artikel zum Erlernen der Arbeit mit der Loader -Komponente.
- QmlNavigationDrawer.pro - Projektprofil;
- main.cpp - Quelldatei der Hauptanwendung;
- main.qml - Haupt-QML-Codedatei;
- Fragment1.qml - erstes Fragment, das im Loader ersetzt werden soll;
- Fragment2.qml - zweites Fragment;
- Fragment3.qml ist das dritte Fragment.
- NavigationDrawer.qml - das Objekt der Navigationsleiste selbst.
main.cpp wird standardmäßig erstellt und nicht geändert, daher wird es nicht aufgelistet, ebenso wenig wie das Projektprofil.
main.qml
In unserer Anwendung wird es eine Anwendungsleiste geben, die ein Objekt vom Typ Rechteck ist, und darin platzieren wir ein Hamburger-Symbol, durch Klicken darauf öffnen und schließen wir die Navigationsschublade Verwenden Sie den Toggle() , der die Anwendungsleiste überschreibt, wie von Material Design empfohlen. Und unter der Anwendungsleiste wird der gesamte verbleibende Platz von der Loader -Komponente belegt, in der wir die Fragmente ändern werden.
Wir platzieren auch ein Navigation Drawer -Objekt im Code, in dem wir eine ListView platzieren. Diese ListView enthält eine Liste von Menüelementen. Wenn er gedrückt wird, ruft der Menüpunkt die Komponentenwechselfunktion in Loader auf und übergibt seinen Index an diese Funktion. Und schon durch den Index Loader wird bestimmt, welche Komponente geladen werden muss.
import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Window 2.0 import QtQuick.Layouts 1.1 ApplicationWindow { visible: true width: 700 height: 480 title: qsTr("Hello World") // Пересчёт независимых от плотности пикселей в физические пиксели устройства readonly property int dpi: Screen.pixelDensity * 25.4 function dp(x){ return (dpi < 120) ? x : x*(dpi/160); } // Application Bar Rectangle { id: menuRect anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right height: dp(48) color: "#4cd964" // Иконка-Гамбургер Rectangle { anchors.top: parent.top anchors.bottom: parent.bottom anchors.left: parent.left width: dp(48) color: "#4cd964" Rectangle { anchors.top: parent.top anchors.topMargin: dp(16) anchors.left: parent.left anchors.leftMargin: dp(14) width: dp(20) height: dp(2) } Rectangle { anchors.top: parent.top anchors.topMargin: dp(23) anchors.left: parent.left anchors.leftMargin: dp(14) width: dp(20) height: dp(2) } Rectangle { anchors.top: parent.top anchors.topMargin: dp(30) anchors.left: parent.left anchors.leftMargin: dp(14) width: dp(20) height: dp(2) } MouseArea { anchors.fill: parent onClicked: { nav.toggle() } } } } // Loader для смены Фрагментов Loader { id: loader anchors.top: menuRect.bottom anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom source: "Fragment1.qml" // Функция для смены содержимого Loader function loadFragment(index){ switch(index){ case 0: loader.source = "Fragment1.qml" break; case 1: loader.source = "Fragment2.qml" break; case 2: loader.source = "Fragment3.qml" break; default: loader.source = "Fragment1.qml" break; } } } NavigationDrawer { id: nav Rectangle { anchors.fill: parent color: "white" // Список с пунктами меню ListView { anchors.fill: parent delegate: Item { height: dp(48) anchors.left: parent.left anchors.right: parent.right Rectangle { anchors.fill: parent anchors.margins: dp(5) color: "whitesmoke" Text { text: fragment anchors.fill: parent font.pixelSize: dp(20) renderType: Text.NativeRendering horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } MouseArea { anchors.fill: parent // По нажатию на пункт меню заменяем компонент в Loader onClicked: { loader.loadFragment(index) } } } } model: navModel } } } // Модель данных для списка с пунктами меню ListModel { id: navModel ListElement {fragment: "Fragment 1"} ListElement {fragment: "Fragment 2"} ListElement {fragment: "Fragment 3"} } }
Fragment1.qml und andere Fragmente
Der Code für Fragment1.qml und die restlichen Fragmente ist identisch, außer dass sie alle eine andere Hintergrundfarbe haben.
import QtQuick 2.5 Rectangle { anchors.fill: parent color: "green" Text { text: "Fragment 1" color: "white" anchors.top: parent.top anchors.right: parent.right anchors.margins: dp(50) font.pixelSize: dp(30) renderType: Text.NativeRendering } }
NavigationDrawer.qml
Und hier ist der Code der Navigationsschublade selbst , in der sich unsere Liste der Fragmente befindet. Dieser Code kann genommen und sofort verwendet oder gründlich darüber meditiert werden, um alle Nuancen seiner Implementierung gründlich zu verstehen und möglicherweise zu verbessern.
import QtQuick 2.5 import QtQuick.Window 2.0 Rectangle { id: panel // Пересчёт независимых от плотности пикселей в физические пиксели устройства readonly property int dpi: Screen.pixelDensity * 25.4 function dp(x){ return (dpi < 120) ? x : x*(dpi/160); } // Свойства Navigation Drawer property bool open: false // Состояние Navigation Drawer - Открыт/Закрыт property int position: Qt.LeftEdge // Положение Navigation Drawer - Слева/Справа // Функции открытия и закрытия Navigation Drawer function show() { open = true; } function hide() { open = false; } function toggle() { open = open ? false : true; } // Внутренние свойства Navigation Drawer readonly property bool _rightEdge: position === Qt.RightEdge readonly property int _closeX: _rightEdge ? _rootItem.width : - panel.width readonly property int _openX: _rightEdge ? _rootItem.width - width : 0 readonly property int _minimumX: _rightEdge ? _rootItem.width - panel.width : -panel.width readonly property int _maximumX: _rightEdge ? _rootItem.width : 0 readonly property int _pullThreshold: panel.width/2 readonly property int _slideDuration: 260 readonly property int _openMarginSize: dp(20) property real _velocity: 0 property real _oldMouseX: -1 property Item _rootItem: parent on_RightEdgeChanged: _setupAnchors() onOpenChanged: completeSlideDirection() width: (Screen.width > Screen.height) ? dp(320) : Screen.width - dp(56) height: parent.height x: _closeX z: 10 function _setupAnchors() { _rootItem = parent; shadow.anchors.right = undefined; shadow.anchors.left = undefined; mouse.anchors.left = undefined; mouse.anchors.right = undefined; if (_rightEdge) { mouse.anchors.right = mouse.parent.right; shadow.anchors.right = panel.left; } else { mouse.anchors.left = mouse.parent.left; shadow.anchors.left = panel.right; } slideAnimation.enabled = false; panel.x = _rightEdge ? _rootItem.width : - panel.width; slideAnimation.enabled = true; } function completeSlideDirection() { if (open) { panel.x = _openX; } else { panel.x = _closeX; Qt.inputMethod.hide(); } } function handleRelease() { var velocityThreshold = dp(5) if ((_rightEdge && _velocity > velocityThreshold) || (!_rightEdge && _velocity < -velocityThreshold)) { panel.open = false; completeSlideDirection() } else if ((_rightEdge && _velocity < -velocityThreshold) || (!_rightEdge && _velocity > velocityThreshold)) { panel.open = true; completeSlideDirection() } else if ((_rightEdge && panel.x < _openX + _pullThreshold) || (!_rightEdge && panel.x > _openX - _pullThreshold) ) { panel.open = true; panel.x = _openX; } else { panel.open = false; panel.x = _closeX; } } function handleClick(mouse) { if ((_rightEdge && mouse.x < panel.x ) || mouse.x > panel.width) { open = false; } } onPositionChanged: { if (!(position === Qt.RightEdge || position === Qt.LeftEdge )) { console.warn("SlidePanel: Unsupported position.") } } Behavior on x { id: slideAnimation enabled: !mouse.drag.active NumberAnimation { duration: _slideDuration easing.type: Easing.OutCubic } } NumberAnimation on x { id: holdAnimation to: _closeX + (_openMarginSize * (_rightEdge ? -1 : 1)) running : false easing.type: Easing.OutCubic duration: 200 } MouseArea { id: mouse parent: _rootItem y: _rootItem.y width: open ? _rootItem.width : _openMarginSize height: _rootItem.height onPressed: if (!open) holdAnimation.restart(); onClicked: handleClick(mouse) drag.target: panel drag.minimumX: _minimumX drag.maximumX: _maximumX drag.axis: Qt.Horizontal drag.onActiveChanged: if (active) holdAnimation.stop() onReleased: handleRelease() z: open ? 1 : 0 onMouseXChanged: { _velocity = (mouse.x - _oldMouseX); _oldMouseX = mouse.x; } } Connections { target: _rootItem onWidthChanged: { slideAnimation.enabled = false panel.completeSlideDirection() slideAnimation.enabled = true } } Rectangle { id: backgroundBlackout parent: _rootItem anchors.fill: parent opacity: 0.5 * Math.min(1, Math.abs(panel.x - _closeX) / _rootItem.width/2) color: "black" } Item { id: shadow anchors.left: panel.right anchors.leftMargin: _rightEdge ? 0 : dp(10) height: parent.height Rectangle { height: dp(10) width: panel.height rotation: 90 opacity: Math.min(1, Math.abs(panel.x - _closeX)/ _openMarginSize) transformOrigin: Item.TopLeft gradient: Gradient{ GradientStop { position: _rightEdge ? 1 : 0 ; color: "#00000000"} GradientStop { position: _rightEdge ? 0 : 1 ; color: "#2c000000"} } } } }
Insgesamt
Als Ergebnis sieht die schriftliche Bewerbung wie in den folgenden Abbildungen dargestellt aus. Das Video-Tutorial zeigt auch eine Demonstration der Anwendung auf Desktop - und Android -Geräten.
Link zum Herunterladen des Projekts in einem ZIP-Archiv: QML Navigation Drawer
Не работает, если запихать Navigation Drawer в элемент List View. Drawer срабатывает только для первого элемента в списке.
Уже разобрался, где ошибка. В 126-й строке должно быть не
, аКроме того, QML ругается на 135-ю строку. Думаю, там должно быть
Добрый день!
Я считаю, что это всё же ошибка, т. к. координаты Item'а всегда задаются относительно родителя. Соответственно, чтобы MouseArea занимала по высоте весь родительский элемент, её координата Y должна быть равна 0, а не координате Y родительского элемента.
Ясно, вопросы обратной совместимости.
Это как раз мой Use Case, потому-то я и задал тут вопрос.
Дело в том, что я использую Ваш Dawer не только для главного меню приложения, но и для контекстного меню элементов ListView.
Да, теперь представляю, как то работает. Согласен, ваша правка определённо к месту здесь.