Evgenii Legotckoi
26 ноября 2019 г. 2:35

QML - Урок 038. Использование свойства clip для обрезки child объектов внутри Item или Rectangle

Довольно часто на форуме возникает один и тот же вопрос, касающийся проблемы отображения контента, встроенного в какие-либо QML-объекты.

Наиболее частым проявлением этой проблемы является прокрутка содержимого ListView за пределы его области. В любом случае, это один из тех частых случаев, когда с этой проблемой сталкиваются новички, приступая к изучению QML. Ну или еще вариант, когда столкнулся с такой проблемой при использовании объектов Rectangle.

На следующем изображении ниже вы можете увидеть два окна приложения, которые иллюстрируют эту проблему.

  • В левом окне показано, как изначально выглядит макет
  • Правое окно показывает, что если вы начнете прокручивать содержимое ListView, оно уйдет под текст заголовка, хотя должно было быть обрезано.


Пример исходного кода

Вот так выглядит исходный код программы.

  1. import QtQuick 2.12
  2. import QtQuick.Window 2.12
  3.  
  4. Window {
  5. visible: true
  6. width: 640
  7. height: 480
  8. title: qsTr("Hello World")
  9.  
  10. Text {
  11. id: topText
  12. text: qsTr("Title")
  13.  
  14. anchors.horizontalCenter: parent.horizontalCenter
  15. anchors.top: parent.top
  16. }
  17.  
  18. ListView {
  19. width: 180; height: 200
  20.  
  21. model: contactModel
  22. delegate: Text {
  23. text: name + ": " + number
  24. }
  25.  
  26. anchors.horizontalCenter: parent.horizontalCenter
  27. anchors.top: topText.bottom
  28. }
  29.  
  30. ListModel {
  31. id: contactModel
  32. ListElement {
  33. name: "Bill Smith"
  34. number: "555 3264"
  35. }
  36. ListElement {
  37. name: "John Brown"
  38. number: "555 8426"
  39. }
  40. ListElement {
  41. name: "Sam Wise"
  42. number: "555 0473"
  43. }
  44. }
  45. }
  46.  

Чтобы решить эту проблему, просто установите свойство clip: true в ListView .

  1. ListView {
  2. clip: true
  3.  
  4. /* код */
  5. }

И теперь содержимое ListView не будет выходить за пределы ListView

Использование прямоугольных объектов

Точно так же clip работает для объектов Rectangle. Вот код программы, а также пример до и после включения клипа.

  1. import QtQuick 2.12
  2. import QtQuick.Window 2.12
  3.  
  4. Window {
  5. visible: true
  6. width: 640
  7. height: 480
  8. title: qsTr("Hello World")
  9.  
  10. Rectangle {
  11. x: 27
  12. y: 31
  13. width: 100
  14. height: 100
  15. color: "blue"
  16. clip: true
  17.  
  18. Rectangle {
  19. x: -20
  20. y: -20
  21. width: 100
  22. height: 100
  23.  
  24. color: "green"
  25. }
  26. }
  27. }

Округление

Но что не работает при использовании клипа, так это скругление, даже если вы настраиваете радиус скругления у родительского объекта Rectangle.

Например вот у нас такой код не даст ожидаемого результата

  1. import QtQuick 2.12
  2. import QtQuick.Window 2.12
  3.  
  4. Window {
  5. visible: true
  6. width: 640
  7. height: 480
  8. title: qsTr("Hello World")
  9.  
  10. Rectangle {
  11. x: 27
  12. y: 31
  13. width: 100
  14. height: 100
  15. color: "blue"
  16. radius: 30 // set rounding
  17. clip: true // enable cropping
  18.  
  19. Rectangle {
  20. x: -20
  21. y: -20
  22. width: 100
  23. height: 100
  24.  
  25. color: "green"
  26. }
  27. }
  28. }
  29.  

The result

Такое поведение клипа является стандартным и было сделано из соображений производительности, поскольку почти все графические элементы QML наследуются от типа Item. Это означает, что дополнительная обработка масок во всем приложении избыточна.

Но ожидаемого результата можно добиться следующим программным кодом с использованием эффекта прививки OpacityMask

  1. import QtQuick 2.12
  2. import QtQuick.Window 2.12
  3. import QtGraphicalEffects 1.13
  4.  
  5. Window {
  6. visible: true
  7. width: 640
  8. height: 480
  9. title: qsTr("Hello World")
  10.  
  11. Rectangle {
  12. id: rect
  13. x: 27
  14. y: 31
  15. width: 100
  16. height: 100
  17. color: "blue"
  18.  
  19. layer.enabled: true
  20. layer.effect: OpacityMask {
  21. maskSource: Item {
  22. width: rect.width
  23. height: rect.height
  24. Rectangle {
  25. anchors.centerIn: parent
  26. width: rect.adapt ? rect.width : Math.min(rect.width, rect.height)
  27. height: rect.adapt ? rect.height : width
  28. radius: 30
  29. }
  30. }
  31. }
  32.  
  33. Rectangle {
  34. x: -20
  35. y: -20
  36. width: 100
  37. height: 100
  38.  
  39. color: "green"
  40. }
  41. }
  42. }
  43.  

И результат будет следующим

Вывод

В заключение скажу, что clip — очень полезное свойство, которое из соображений производительности по умолчанию установлено в false. При этом это свойство выполняет только простую обрезку в виде прямоугольника, а что-то более сложное нужно обрезать по маске.

Вам это нравится? Поделитесь в социальных сетях!

f
  • 21 октября 2021 г. 15:24

Зачем внутри OpacityMask Item, почему сразу Rectangle не вставить?
И что за rect.adapt?

Мое предложение:

  1. layer.enabled: true
  2. layer.effect: OpacityMask {
  3. maskSource: Rectangle {
  4. width: rect.width
  5. height: rect.height
  6. radius: 30
  7. }
  8. }

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь