Qt Quick里的图形效果:阴影(Drop Shadow)

时间:2024-12-02 08:36:44

Qt Quick提供了两种阴影效果:

  • DropShow,阴影。这个元素会根据源图像,产生一个彩色的、模糊的新图像,把这个新图像放在源图像后面,给人一种源图像从背景上凸出来的效果。
  • InnerShadow,内阴影。这个元素会根据源图像,产生一个彩色的、模糊的新图像,与 DropShadow不同的是,新图像会放在源图像里面。

效果

下面是我设计的示例效果。

首先是 DropShadow :

Qt Quick里的图形效果:阴影(Drop Shadow)

图1 阴影效果

然后是内阴影效果:

Qt Quick里的图形效果:阴影(Drop Shadow)

图2 内阴影效果

源码分析

如图1所示,界面被分为三部分。

最上面的是源图像。

源图像下面(即中间)是一个列表,你可以点击 DropShadow 和 InnerShadow 两个子项,切换不同的阴影效果。每种阴影效果都对应一个 qml 文档,当你点击这些子项时,对应的 qml 文档动态加载。

阴影示例界面

这个示例界面框架其实与“Qt Quick里的图形效果——颜色(Color)”是一致的,只是我把 ListView 从原来的竖向改为了横向。对应的 DropShadowExample.qml 内容如下:

  1. import QtQuick 2.2
  2. import QtQuick.Controls 1.2
  3. Rectangle {
  4. id: example;
  5. signal back();
  6. anchors.fill: parent;
  7. Text {
  8. id: origLabel;
  9. x: 10;
  10. y: 4;
  11. font.pointSize: 20;
  12. text: "Original Image";
  13. }
  14. Button {
  15. anchors.right: parent.right;
  16. anchors.top: parent.top;
  17. anchors.margins: 4;
  18. text: "Back";
  19. onClicked: example.back();
  20. }
  21. Image {
  22. id: origImage;
  23. width: 240;
  24. height: 240;
  25. anchors.left: parent.left;
  26. anchors.top: origLabel.bottom;
  27. anchors.margins: 4;
  28. source: "butterfly.png";
  29. sourceSize: Qt.size(240, 240);
  30. smooth: true;
  31. }
  32. Rectangle{
  33. anchors.left: parent.left;
  34. anchors.leftMargin: 4;
  35. anchors.right: parent.right;
  36. anchors.rightMargin: 4;
  37. anchors.top: origImage.bottom;
  38. height: 2;
  39. border.width: 1;
  40. border.color: "darkgray";
  41. }
  42. Text {
  43. id: effectsLabel;
  44. anchors.top: origImage.bottom;
  45. anchors.margins: 4;
  46. anchors.left: parent.left;
  47. font.pointSize: 20;
  48. font.bold: true;
  49. text: "Shadow Effects:";
  50. color: "blue";
  51. }
  52. Rectangle {
  53. id: shadowEffects;
  54. anchors.left: effectsLabel.right;
  55. anchors.leftMargin: 4;
  56. anchors.top: effectsLabel.top;
  57. anchors.right: parent.right;
  58. anchors.rightMargin: 4;
  59. height: 40;
  60. color: "gray";
  61. ListView {
  62. anchors.fill: parent;
  63. clip: true;
  64. focus: true;
  65. orientation: ListView.Horizontal;
  66. spacing: 20;
  67. delegate: Text {
  68. id: wrapper;
  69. height: 40;
  70. verticalAlignment: Text.AlignVCenter;
  71. text: name;
  72. font.pointSize: 18;
  73. Keys.onEnterPressed: {
  74. event.accepted = true;
  75. effectControl.source = example;
  76. }
  77. Keys.onReturnPressed: {
  78. event.accepted = true;
  79. effectControl.source = example;
  80. }
  81. MouseArea {
  82. anchors.fill: parent;
  83. onClicked: {
  84. wrapper.ListView.view.currentIndex = index;
  85. effectControl.source = example;
  86. }
  87. }
  88. }
  89. highlight: Rectangle {
  90. height: parent.height;
  91. color: "lightblue";
  92. }
  93. model: shadowsModel;
  94. }
  95. }
  96. Loader {
  97. id: effectControl;
  98. anchors.top: shadowEffects.bottom;
  99. anchors.left: parent.left;
  100. anchors.bottom: parent.bottom;
  101. anchors.right: parent.right;
  102. anchors.margins: 4;
  103. source: "DropShadowEx.qml";
  104. }
  105. ListModel {
  106. id: shadowsModel;
  107. ListElement {
  108. name: "DropShadow";
  109. example: "DropShadowEx.qml";
  110. }
  111. ListElement {
  112. name: "InnerShadow";
  113. example: "InnerShadowEx.qml";
  114. }
  115. }
  116. }

DropShawExample.qml 会被“Qt Quick里的图形效果(Graphical Effects)”里介绍过的 main.qml 动态加载。

阴影效果

阴影效果对应的 DropShadowEx.qml 内容如下:

  1. import QtQuick 2.2
  2. import QtGraphicalEffects 1.0
  3. import QtQuick.Controls 1.2
  4. Rectangle {
  5. anchors.fill: parent;
  6. Image {
  7. id: opImage;
  8. x: 4;
  9. y: 4;
  10. width: 250;
  11. height: 250;
  12. source: "butterfly.png";
  13. sourceSize: Qt.size(250, 250);
  14. smooth: true;
  15. visible: false;
  16. }
  17. DropShadow {
  18. id: dropshadow;
  19. anchors.fill: opImage;
  20. source: opImage;
  21. }
  22. Rectangle {
  23. anchors.left: opImage.right;
  24. anchors.top: opImage.top;
  25. anchors.right: parent.right;
  26. anchors.bottom: parent.bottom;
  27. anchors.margins: 2;
  28. color: "lightsteelblue";
  29. CheckBox {
  30. id: fast;
  31. anchors.top: parent.top;
  32. anchors.topMargin: 4;
  33. anchors.left: parent.left;
  34. anchors.leftMargin: 4;
  35. checked: false;
  36. text: "fast";
  37. }
  38. CheckBox {
  39. id: transparentBorder;
  40. anchors.left: fast.right;
  41. anchors.leftMargin: 8;
  42. anchors.top: fast.top;
  43. checked: false;
  44. text: "transparentBorder";
  45. }
  46. Text {
  47. id: colorLabel;
  48. anchors.left: fast.left;
  49. anchors.top: fast.bottom;
  50. anchors.topMargin: 8;
  51. text: "shadow color:";
  52. }
  53. ColorPicker {
  54. id: shadowColor;
  55. anchors.left: colorLabel.right;
  56. anchors.leftMargin: 4;
  57. anchors.top: colorLabel.top;
  58. width: 90;
  59. height: 28;
  60. color: "#ff000000";
  61. }
  62. Text {
  63. id: sampleLabel;
  64. anchors.left: fast.left;
  65. anchors.top: shadowColor.bottom;
  66. anchors.topMargin: 8;
  67. text: "samples:";
  68. }
  69. Slider {
  70. id: sampleSlider;
  71. anchors.left: sampleLabel.right;
  72. anchors.leftMargin: 4;
  73. anchors.top: sampleLabel.top;
  74. minimumValue: 0;
  75. maximumValue: 32;
  76. value: 0.0;
  77. width: 160;
  78. height: 30;
  79. stepSize: 1.0;
  80. }
  81. Text {
  82. id: spreadLabel;
  83. anchors.left: fast.left;
  84. anchors.top: sampleSlider.bottom;
  85. anchors.topMargin: 8;
  86. text: "spread:";
  87. }
  88. Slider {
  89. id: spreadSlider;
  90. anchors.left: spreadLabel.right;
  91. anchors.leftMargin: 4;
  92. anchors.top: spreadLabel.top;
  93. value: 0.5;
  94. width: 160;
  95. height: 30;
  96. }
  97. Text {
  98. id: radiusLabel;
  99. anchors.left: fast.left;
  100. anchors.top: spreadSlider.bottom;
  101. anchors.topMargin: 8;
  102. text: "radius:";
  103. }
  104. Rectangle {
  105. id: radiusArea;
  106. anchors.left: radiusLabel.right;
  107. anchors.leftMargin: 4;
  108. anchors.top: radiusLabel.top;
  109. height: 30;
  110. width: 160;
  111. color: "lightgray";
  112. border.width: 1;
  113. border.color: "darkgray";
  114. TextInput {
  115. anchors.fill: parent;
  116. anchors.margins: 2;
  117. id: radiusEdit;
  118. font.pointSize: 18;
  119. text: "0.0";
  120. validator: DoubleValidator{bottom: 0;}
  121. }
  122. }
  123. Text {
  124. id: voffLabel;
  125. anchors.left: fast.left;
  126. anchors.top: radiusArea.bottom;
  127. anchors.topMargin: 8;
  128. text: "verticalOffset:";
  129. }
  130. Rectangle {
  131. id: voffArea;
  132. anchors.left: voffLabel.right;
  133. anchors.leftMargin: 4;
  134. anchors.top: voffLabel.top;
  135. height: 30;
  136. width: 160;
  137. color: "lightgray";
  138. border.width: 1;
  139. border.color: "darkgray";
  140. TextInput {
  141. anchors.fill: parent;
  142. anchors.margins: 2;
  143. id: voffEdit;
  144. font.pointSize: 18;
  145. text: "0.0";
  146. validator: DoubleValidator{}
  147. }
  148. }
  149. Text {
  150. id: hoffLabel;
  151. anchors.left: fast.left;
  152. anchors.top: voffArea.bottom;
  153. anchors.topMargin: 8;
  154. text: "horizontalOffset:";
  155. }
  156. Rectangle {
  157. id: hoffArea;
  158. anchors.left: hoffLabel.right;
  159. anchors.leftMargin: 4;
  160. anchors.top: hoffLabel.top;
  161. height: 30;
  162. width: 160;
  163. color: "lightgray";
  164. border.width: 1;
  165. border.color: "darkgray";
  166. TextInput {
  167. anchors.fill: parent;
  168. anchors.margins: 2;
  169. id: hoffEdit;
  170. font.pointSize: 18;
  171. text: "0.0";
  172. validator: DoubleValidator{}
  173. }
  174. }
  175. Button {
  176. id: applyBtn;
  177. anchors.left: parent.left;
  178. anchors.leftMargin: 4;
  179. anchors.top: hoffArea.bottom;
  180. anchors.topMargin: 12;
  181. text: "Apply";
  182. onClicked: {
  183. dropshadow.color = shadowColor.color;
  184. dropshadow.fast = fast.checked;
  185. dropshadow.transparentBorder = transparentBorder.checked;
  186. dropshadow.samples = sampleSlider.value;
  187. dropshadow.radius = parseFloat(radiusEdit.text);
  188. dropshadow.verticalOffset = voffEdit.text;
  189. dropshadow.horizontalOffset = hoffEdit.text;
  190. dropshadow.spread = spreadSlider.value;
  191. }
  192. }
  193. }
  194. }

代码比较简单,不细说了。我们看看 DropShadow 元素的各个属性都什么含义吧。

  • source,variant类型,指向源Item
  • horizontalOffset 与verticalOffset,real类型,指定阴影相对于源Item的水平和垂直偏移量,默认为 0
  • radius,real类型,设置阴影的柔和程度,值越大,阴影的边缘就会显得越柔和
  • sample,int类型,指定生成阴影时阴影的每个像素由多少个采样点产生,采样点越多阴影效果越好,不过也越慢。一般可以把这个值设置为 radius的2倍。
  • spread,real类型,指定如何强化阴影接近源 Item 边缘的部分,取值范围为 0.0 -- 1.0 ,默认为 0.5

未提及的属性都比较简单,想 cached 、 fast 、 transparentBorder 等,之前的文章也提到过。

内阴影

内阴影效果对应的 InnerShadowEx.qml 内容如下:

  1. import QtQuick 2.2
  2. import QtGraphicalEffects 1.0
  3. import QtQuick.Controls 1.2
  4. Rectangle {
  5. anchors.fill: parent;
  6. Image {
  7. id: opImage;
  8. x: 4;
  9. y: 4;
  10. width: 250;
  11. height: 250;
  12. source: "butterfly.png";
  13. sourceSize: Qt.size(250, 250);
  14. smooth: true;
  15. visible: false;
  16. }
  17. InnerShadow {
  18. id: innershadow;
  19. anchors.fill: opImage;
  20. source: opImage;
  21. }
  22. Rectangle {
  23. anchors.left: opImage.right;
  24. anchors.top: opImage.top;
  25. anchors.right: parent.right;
  26. anchors.bottom: parent.bottom;
  27. anchors.margins: 2;
  28. color: "lightsteelblue";
  29. CheckBox {
  30. id: fast;
  31. anchors.top: parent.top;
  32. anchors.topMargin: 4;
  33. anchors.left: parent.left;
  34. anchors.leftMargin: 4;
  35. checked: false;
  36. text: "fast";
  37. }
  38. Text {
  39. id: colorLabel;
  40. anchors.left: fast.left;
  41. anchors.top: fast.bottom;
  42. anchors.topMargin: 8;
  43. text: "shadow color:";
  44. }
  45. ColorPicker {
  46. id: shadowColor;
  47. anchors.left: colorLabel.right;
  48. anchors.leftMargin: 4;
  49. anchors.top: colorLabel.top;
  50. width: 90;
  51. height: 28;
  52. color: "#ff000000";
  53. }
  54. Text {
  55. id: sampleLabel;
  56. anchors.left: fast.left;
  57. anchors.top: shadowColor.bottom;
  58. anchors.topMargin: 8;
  59. text: "samples:";
  60. }
  61. Slider {
  62. id: sampleSlider;
  63. anchors.left: sampleLabel.right;
  64. anchors.leftMargin: 4;
  65. anchors.top: sampleLabel.top;
  66. minimumValue: 0;
  67. maximumValue: 32;
  68. value: 0.0;
  69. width: 160;
  70. height: 30;
  71. stepSize: 1.0;
  72. }
  73. Text {
  74. id: spreadLabel;
  75. anchors.left: fast.left;
  76. anchors.top: sampleSlider.bottom;
  77. anchors.topMargin: 8;
  78. text: "spread:";
  79. }
  80. Slider {
  81. id: spreadSlider;
  82. anchors.left: spreadLabel.right;
  83. anchors.leftMargin: 4;
  84. anchors.top: spreadLabel.top;
  85. value: 0.5;
  86. width: 160;
  87. height: 30;
  88. }
  89. Text {
  90. id: radiusLabel;
  91. anchors.left: fast.left;
  92. anchors.top: spreadSlider.bottom;
  93. anchors.topMargin: 8;
  94. text: "radius:";
  95. }
  96. Rectangle {
  97. id: radiusArea;
  98. anchors.left: radiusLabel.right;
  99. anchors.leftMargin: 4;
  100. anchors.top: radiusLabel.top;
  101. height: 30;
  102. width: 160;
  103. color: "lightgray";
  104. border.width: 1;
  105. border.color: "darkgray";
  106. TextInput {
  107. anchors.fill: parent;
  108. anchors.margins: 2;
  109. id: radiusEdit;
  110. font.pointSize: 18;
  111. text: "0.0";
  112. validator: DoubleValidator{bottom: 0;}
  113. }
  114. }
  115. Text {
  116. id: voffLabel;
  117. anchors.left: fast.left;
  118. anchors.top: radiusArea.bottom;
  119. anchors.topMargin: 8;
  120. text: "verticalOffset:";
  121. }
  122. Rectangle {
  123. id: voffArea;
  124. anchors.left: voffLabel.right;
  125. anchors.leftMargin: 4;
  126. anchors.top: voffLabel.top;
  127. height: 30;
  128. width: 160;
  129. color: "lightgray";
  130. border.width: 1;
  131. border.color: "darkgray";
  132. TextInput {
  133. anchors.fill: parent;
  134. anchors.margins: 2;
  135. id: voffEdit;
  136. font.pointSize: 18;
  137. text: "0.0";
  138. validator: DoubleValidator{}
  139. }
  140. }
  141. Text {
  142. id: hoffLabel;
  143. anchors.left: fast.left;
  144. anchors.top: voffArea.bottom;
  145. anchors.topMargin: 8;
  146. text: "verticalOffset:";
  147. }
  148. Rectangle {
  149. id: hoffArea;
  150. anchors.left: hoffLabel.right;
  151. anchors.leftMargin: 4;
  152. anchors.top: hoffLabel.top;
  153. height: 30;
  154. width: 160;
  155. color: "lightgray";
  156. border.width: 1;
  157. border.color: "darkgray";
  158. TextInput {
  159. anchors.fill: parent;
  160. anchors.margins: 2;
  161. id: hoffEdit;
  162. font.pointSize: 18;
  163. text: "0.0";
  164. validator: DoubleValidator{}
  165. }
  166. }
  167. Button {
  168. id: applyBtn;
  169. anchors.left: parent.left;
  170. anchors.leftMargin: 4;
  171. anchors.top: hoffArea.bottom;
  172. anchors.topMargin: 12;
  173. text: "Apply";
  174. onClicked: {
  175. innershadow.color = shadowColor.color;
  176. innershadow.fast = fast.checked;
  177. innershadow.samples = sampleSlider.value;
  178. innershadow.radius = parseFloat(radiusEdit.text);
  179. innershadow.verticalOffset = voffEdit.text;
  180. innershadow.horizontalOffset = hoffEdit.text;
  181. innershadow.spread = spreadSlider.value;
  182. }
  183. }
  184. }
  185. }

源码比较简单,不说了。

InnerShadow 比 DropShadow 少了一个 transparentBorder 属性,其他基本一致,偷个懒,也不说了。

回顾一下: