Evgenii Legotckoi
Nov. 28, 2015, 9:06 p.m.

QML - Lesson 019. Navigation Drawer in Qt Qml Android

One of the key controls in the Android app is the Navigation Drawer , but QML component is not ready for this item, but the guys from Cutehacks made your Navigation Drawer , the code of the component is laid out on githabe. I have long wanted to run the code on an Android device and live my hands finally to have reached it.

I studied in detail the code and slightly it corrected, because that version was some discrepancy Material Design in the sense that the socket was revealed 78 per cent of the width of the screen, regardless of orientation. A Material Design recommends portrait disclose Navigation Drawer so that it did not reach the opposite edge 56 dip in the case with smartphones and 64 dip in the case of tablets, but make at least for smartphones, and in landscape orientation was not more 320 dip width. What I corrected, and drank a small part of the unnecessary at the moment and a little bit of code rename the variable by itself.

With regard to the magnitude of the dip , that is independent of the pixel density of the display device, it is a question of the correct scaling of interface elements.

I bring to your attention an example of the use of the Navigation Drawer to change the three fragments in the Loader object using three menu items that will be located in the Navigation Drawer.

The structure of the project to work with the Navigation Drawer

The structure of the project will be similar to the structure of the draft articles on the working with the component Loader .

  • QmlNavigationDrawer.pro - the profile of the project;
  • main.cpp - the main file of the application source code;
  • main.qml - basic qml code file;
  • Fragment1.qml - the first fragment for replacement Loader;
  • Fragment2.qml - second fragment;
  • Fragment3.qml - a third fragment.
  • NavigationDrawer.qml - the object Navigation Drawer.

main.cpp is created by default and is not subject to change, so it will not be given a listing, as well as the project profile.

main.qml

In our application will Application Bar, which is the object type the Rectangle, and in Him we put-burger icon, by pressing on which will open and close the Navigation Drawer via toggle () function, in this case it would overlap the Application Bar in accordance with the recommendations of Material Design. A below Application Bar all the remaining space will occupy the Loader component, in which we are going to change the pieces.

Also put in the code object Navigation Drawer, which put the ListView. This ListView is located a list of menu items. By clicking the menu item will cause a change in the function of the component Loader, passing in this function its index. And on Loader index will determine which component you want to download.

  1. import QtQuick 2.5
  2. import QtQuick.Controls 1.4
  3. import QtQuick.Window 2.0
  4. import QtQuick.Layouts 1.1
  5.  
  6. ApplicationWindow {
  7. visible: true
  8. width: 700
  9. height: 480
  10. title: qsTr("Hello World")
  11.  
  12. // Conversion independent of the density of pixels to physical pixels the device
  13. readonly property int dpi: Screen.pixelDensity * 25.4
  14. function dp(x){ return (dpi < 120) ? x : x*(dpi/160); }
  15.  
  16. // Application Bar
  17. Rectangle {
  18. id: menuRect
  19. anchors.top: parent.top
  20. anchors.left: parent.left
  21. anchors.right: parent.right
  22. height: dp(48)
  23. color: "#4cd964"
  24.  
  25. // Icon-Hamburger
  26. Rectangle {
  27. anchors.top: parent.top
  28. anchors.bottom: parent.bottom
  29. anchors.left: parent.left
  30.  
  31. width: dp(48)
  32. color: "#4cd964"
  33.  
  34. Rectangle {
  35. anchors.top: parent.top
  36. anchors.topMargin: dp(16)
  37. anchors.left: parent.left
  38. anchors.leftMargin: dp(14)
  39. width: dp(20)
  40. height: dp(2)
  41. }
  42.  
  43. Rectangle {
  44. anchors.top: parent.top
  45. anchors.topMargin: dp(23)
  46. anchors.left: parent.left
  47. anchors.leftMargin: dp(14)
  48. width: dp(20)
  49. height: dp(2)
  50. }
  51.  
  52. Rectangle {
  53. anchors.top: parent.top
  54. anchors.topMargin: dp(30)
  55. anchors.left: parent.left
  56. anchors.leftMargin: dp(14)
  57. width: dp(20)
  58. height: dp(2)
  59. }
  60.  
  61. MouseArea {
  62. anchors.fill: parent
  63.  
  64. onClicked: {
  65. nav.toggle()
  66. }
  67. }
  68. }
  69.  
  70. }
  71.  
  72. Loader {
  73. id: loader
  74. anchors.top: menuRect.bottom
  75. anchors.left: parent.left
  76. anchors.right: parent.right
  77. anchors.bottom: parent.bottom
  78. source: "Fragment1.qml"
  79.  
  80. function loadFragment(index){
  81.  
  82. switch(index){
  83. case 0:
  84. loader.source = "Fragment1.qml"
  85. break;
  86. case 1:
  87. loader.source = "Fragment2.qml"
  88. break;
  89. case 2:
  90. loader.source = "Fragment3.qml"
  91. break;
  92. default:
  93. loader.source = "Fragment1.qml"
  94. break;
  95. }
  96. }
  97. }
  98.  
  99. NavigationDrawer {
  100. id: nav
  101. Rectangle {
  102. anchors.fill: parent
  103. color: "white"
  104.  
  105. ListView {
  106. anchors.fill: parent
  107.  
  108. delegate: Item {
  109. height: dp(48)
  110. anchors.left: parent.left
  111. anchors.right: parent.right
  112.  
  113. Rectangle {
  114. anchors.fill: parent
  115. anchors.margins: dp(5)
  116. color: "whitesmoke"
  117.  
  118. Text {
  119. text: fragment
  120. anchors.fill: parent
  121. font.pixelSize: dp(20)
  122.  
  123. renderType: Text.NativeRendering
  124. horizontalAlignment: Text.AlignHCenter
  125. verticalAlignment: Text.AlignVCenter
  126. }
  127.  
  128. MouseArea {
  129. anchors.fill: parent
  130.  
  131. // By clicking on the menu item is replaced component in the Loader
  132. onClicked: {
  133. loader.loadFragment(index)
  134. }
  135. }
  136. }
  137. }
  138.  
  139. model: navModel
  140. }
  141. }
  142. }
  143.  
  144. ListModel {
  145. id: navModel
  146.  
  147. ListElement {fragment: "Fragment 1"}
  148. ListElement {fragment: "Fragment 2"}
  149. ListElement {fragment: "Fragment 3"}
  150. }
  151. }

Fragment1.qml and other fragments

And other code fragments Fragment1.qml identical except that they all have a different background color.

  1. import QtQuick 2.5
  2.  
  3. Rectangle {
  4. anchors.fill: parent
  5. color: "green"
  6.  
  7. Text {
  8. text: "Fragment 1"
  9. color: "white"
  10. anchors.top: parent.top
  11. anchors.right: parent.right
  12. anchors.margins: dp(50)
  13. font.pixelSize: dp(30)
  14.  
  15. renderType: Text.NativeRendering
  16. }
  17.  
  18. }

NavigationDrawer.qml

And here is the code of the Navigation Drawer, which is located in our list of fragments. This code can be taken and used immediately or thoroughly meditate on it, to thoroughly understand all the nuances of its implementation and it is possible to improve it.

  1. import QtQuick 2.5
  2. import QtQuick.Window 2.0
  3.  
  4. Rectangle {
  5. id: panel
  6.  
  7. // Conversion independent of the density of pixels to physical pixels the device
  8. readonly property int dpi: Screen.pixelDensity * 25.4
  9. function dp(x){ return (dpi < 120) ? x : x*(dpi/160); }
  10.  
  11. property bool open: false // State Navigation Drawer - Open / Closed
  12. property int position: Qt.LeftEdge // Location Navigation Drawer - Left / Right
  13.  
  14. // Functions of the opening and closing Navigation Drawer
  15. function show() { open = true; }
  16. function hide() { open = false; }
  17. function toggle() { open = open ? false : true; }
  18.  
  19. // internal properties Navigation Drawer
  20. readonly property bool _rightEdge: position === Qt.RightEdge
  21. readonly property int _closeX: _rightEdge ? _rootItem.width : - panel.width
  22. readonly property int _openX: _rightEdge ? _rootItem.width - width : 0
  23. readonly property int _minimumX: _rightEdge ? _rootItem.width - panel.width : -panel.width
  24. readonly property int _maximumX: _rightEdge ? _rootItem.width : 0
  25. readonly property int _pullThreshold: panel.width/2
  26. readonly property int _slideDuration: 260
  27. readonly property int _openMarginSize: dp(20)
  28.  
  29. property real _velocity: 0
  30. property real _oldMouseX: -1
  31.  
  32. property Item _rootItem: parent
  33.  
  34. on_RightEdgeChanged: _setupAnchors()
  35. onOpenChanged: completeSlideDirection()
  36.  
  37. width: (Screen.width > Screen.height) ? dp(320) : Screen.width - dp(56)
  38. height: parent.height
  39. x: _closeX
  40. z: 10
  41.  
  42. function _setupAnchors() {
  43. _rootItem = parent;
  44.  
  45. shadow.anchors.right = undefined;
  46. shadow.anchors.left = undefined;
  47.  
  48. mouse.anchors.left = undefined;
  49. mouse.anchors.right = undefined;
  50.  
  51. if (_rightEdge) {
  52. mouse.anchors.right = mouse.parent.right;
  53. shadow.anchors.right = panel.left;
  54. } else {
  55. mouse.anchors.left = mouse.parent.left;
  56. shadow.anchors.left = panel.right;
  57. }
  58.  
  59. slideAnimation.enabled = false;
  60. panel.x = _rightEdge ? _rootItem.width : - panel.width;
  61. slideAnimation.enabled = true;
  62. }
  63.  
  64. function completeSlideDirection() {
  65. if (open) {
  66. panel.x = _openX;
  67. } else {
  68. panel.x = _closeX;
  69. Qt.inputMethod.hide();
  70. }
  71. }
  72.  
  73. function handleRelease() {
  74. var velocityThreshold = dp(5)
  75. if ((_rightEdge && _velocity > velocityThreshold) ||
  76. (!_rightEdge && _velocity < -velocityThreshold)) {
  77. panel.open = false;
  78. completeSlideDirection()
  79. } else if ((_rightEdge && _velocity < -velocityThreshold) ||
  80. (!_rightEdge && _velocity > velocityThreshold)) {
  81. panel.open = true;
  82. completeSlideDirection()
  83. } else if ((_rightEdge && panel.x < _openX + _pullThreshold) ||
  84. (!_rightEdge && panel.x > _openX - _pullThreshold) ) {
  85. panel.open = true;
  86. panel.x = _openX;
  87. } else {
  88. panel.open = false;
  89. panel.x = _closeX;
  90. }
  91. }
  92.  
  93. function handleClick(mouse) {
  94. if ((_rightEdge && mouse.x < panel.x ) || mouse.x > panel.width) {
  95. open = false;
  96. }
  97. }
  98.  
  99. onPositionChanged: {
  100. if (!(position === Qt.RightEdge || position === Qt.LeftEdge )) {
  101. console.warn("SlidePanel: Unsupported position.")
  102. }
  103. }
  104.  
  105. Behavior on x {
  106. id: slideAnimation
  107. enabled: !mouse.drag.active
  108. NumberAnimation {
  109. duration: _slideDuration
  110. easing.type: Easing.OutCubic
  111. }
  112. }
  113.  
  114. NumberAnimation on x {
  115. id: holdAnimation
  116. to: _closeX + (_openMarginSize * (_rightEdge ? -1 : 1))
  117. running : false
  118. easing.type: Easing.OutCubic
  119. duration: 200
  120. }
  121.  
  122. MouseArea {
  123. id: mouse
  124. parent: _rootItem
  125.  
  126. y: _rootItem.y
  127. width: open ? _rootItem.width : _openMarginSize
  128. height: _rootItem.height
  129. onPressed: if (!open) holdAnimation.restart();
  130. onClicked: handleClick(mouse)
  131. drag.target: panel
  132. drag.minimumX: _minimumX
  133. drag.maximumX: _maximumX
  134. drag.axis: Qt.Horizontal
  135. drag.onActiveChanged: if (active) holdAnimation.stop()
  136. onReleased: handleRelease()
  137. z: open ? 1 : 0
  138. onMouseXChanged: {
  139. _velocity = (mouse.x - _oldMouseX);
  140. _oldMouseX = mouse.x;
  141. }
  142. }
  143.  
  144. Connections {
  145. target: _rootItem
  146. onWidthChanged: {
  147. slideAnimation.enabled = false
  148. panel.completeSlideDirection()
  149. slideAnimation.enabled = true
  150. }
  151. }
  152.  
  153. Rectangle {
  154. id: backgroundBlackout
  155. parent: _rootItem
  156. anchors.fill: parent
  157. opacity: 0.5 * Math.min(1, Math.abs(panel.x - _closeX) / _rootItem.width/2)
  158. color: "black"
  159. }
  160.  
  161. Item {
  162. id: shadow
  163. anchors.left: panel.right
  164. anchors.leftMargin: _rightEdge ? 0 : dp(10)
  165. height: parent.height
  166.  
  167. Rectangle {
  168. height: dp(10)
  169. width: panel.height
  170. rotation: 90
  171. opacity: Math.min(1, Math.abs(panel.x - _closeX)/ _openMarginSize)
  172. transformOrigin: Item.TopLeft
  173. gradient: Gradient{
  174. GradientStop { position: _rightEdge ? 1 : 0 ; color: "#00000000"}
  175. GradientStop { position: _rightEdge ? 0 : 1 ; color: "#2c000000"}
  176. }
  177. }
  178. }
  179. }

Conclusion

As a result of the written application will appear as shown in the following figure.

Link to the project download in zip-archive: QML Navigation Drawer

Video

Do you like it? Share on social networks!

Константин Козлов
  • Feb. 17, 2018, 1:50 p.m.

Не работает, если запихать Navigation Drawer в элемент List View. Drawer срабатывает только для первого элемента в списке.

Константин Козлов
  • Feb. 17, 2018, 7:11 p.m.

Уже разобрался, где ошибка. В 126-й строке должно быть не

y: _rootItem.y
, а
y: 0
Кроме того, QML ругается на 135-ю строку. Думаю, там должно быть
drag.onActiveChanged: if (drag.active) holdAnimation.stop()

Добрый день!

Вполне возможно, что есть некоторые несостыковки из-за версий компонентов QML.
А вообще, попробуйте Navigation Drawer из последних компонентов Qt. Дело в том, что этот урок несколько устарел, а Qt Company выпустила свои собственные компоненты, в составе которых есть и Navigation Drawer. Вы можете ознакомиться с этими компонентами в примере Qt Gallery, который присутствует в составе примеров в Qt Creator. Думаю, что это будет для ваших задач и целей лучше.

Я считаю, что это всё же ошибка, т. к. координаты Item'а всегда задаются относительно родителя. Соответственно, чтобы MouseArea занимала по высоте весь родительский элемент, её координата Y должна быть равна 0, а не координате Y родительского элемента.


А по поводу Drawer'а, появившегося в Qt 5.6, я не хочу его использовать, т. к. желаю сохранить совместимость с Qt 5.5.

Ясно, вопросы обратной совместимости.

Да, проверил, как-то криво работает, если не поправить 126-ю строку, в том случае, если Navigation Drawer требуется поместить в какой-то внутренний объект, хотя для меня сам по себе User Case странный. Получается, что вы допускаете случай, в котором Drawer может быть развёрнут лишь на часть окна приложения, иначе я не вижу смысла помещать его в какое-либо иное место, кроме самого окна приложения.
Константин Козлов
  • Feb. 18, 2018, 8:29 p.m.

Это как раз мой Use Case, потому-то я и задал тут вопрос.
Дело в том, что я использую Ваш Dawer не только для главного меню приложения, но и для контекстного меню элементов ListView.

Да, теперь представляю, как то работает. Согласен, ваша правка определённо к месту здесь.

Comments

Only authorized users can post comments.
Please, Log in or Sign up
  • Last comments
  • AK
    April 1, 2025, 11:41 a.m.
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    March 9, 2025, 9:02 p.m.
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    March 9, 2025, 4:14 p.m.
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…
  • ИМ
    Nov. 22, 2024, 9:51 p.m.
    Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
  • Evgenii Legotckoi
    Oct. 31, 2024, 11:37 p.m.
    Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup