概述

在应用的开发过程中,经常需要设计复杂界面,此时涉及到多个相同或不同组件之间的嵌套。如果布局组件嵌套深度过深,或者嵌套组件数过多,会带来额外的开销。如果在布局的方式上进行优化,就可以有效的提升性能,减少时间开销。

RelativeContainer是一种采用相对布局的容器,支持容器内部的子元素设置相对位置关系,适用于处理界面复杂的场景,对多个子元素进行对齐和排列。子元素可以指定兄弟元素或父容器作为锚点,基于锚点进行相对位置布局。在使用锚点时,需注意子元素的相对位置关系,以避免出现错位或遮挡的情况。下图展示了一个 RelativeContainer的概念图,图中的虚线表示位置的依赖关系。

图1 相对布局示意图

子元素并不完全是上图中的依赖关系。比如,Item4可以以Item2为依赖锚点,也可以以RelativeContainer父容器为依赖锚点。

基本概念

  • 参考边界:设置当前组件的哪个边界对齐到锚点。

  • 锚点:通过锚点设置当前元素基于哪个元素确定位置。

  • 对齐方式:通过对齐方式,设置当前元素是基于锚点的上中下对齐,还是基于锚点的左中右对齐。

设置依赖关系

设置参考边界

设置当前组件的哪个边界对齐到锚点。容器内子组件的参考边界区分水平方向和垂直方向。

  • 在水平方向上,可以按照起始(left)、居中(middle)或尾端(right)的组件边界与锚点对齐。当设置三个边界时,仅起始(left)和居中(middle)的边界设置生效。

  • 在垂直方向上,可以设置组件边界与锚点对齐,具体包括顶部(top)、居中(center)和底部(bottom)。当设置三个边界时,仅顶部(top)和居中(center)生效。

设置锚点

锚点设置涉及子元素相对于其父元素或兄弟元素的位置依赖关系。具体而言,子元素可以将其位置锚定到相对布局容器(RelativeContainer)、辅助线(guideline)、屏障(barrier)或其他子元素上。

为了准确定义锚点,RelativeContainer的子元素必须拥有唯一的组件标识(id),用于指定锚点信息。父元素RelativeContainer的标识默认为“__container__”,其他子元素的组件标识(id)则通过id属性设置。

说明

  • 未设置组件标识(id)的组件虽可显示,但无法被其他组件引用为锚点。相对布局容器会为其拼接组件标识,但组件标识(id)的规律无法被应用感知。辅助线(guideline)与屏障(barrier)的组件标识(id)需确保唯一,避免与任何组件冲突。若有重复,遵循组件 > guideline > barrier 的优先级。
  • 组件间设置锚点时应避免形成依赖循环(组件之间设置链除外),依赖循环将导致子组件缺乏定位基准,最终无法绘制。
  • RelativeContainer父组件为锚点,__container__代表父容器的组件标识(id)。

    
      
    1. let AlignRus: Record<string, Record<string, string | VerticalAlign | HorizontalAlign>> = {
    2. 'top': { 'anchor': '__container__', 'align': VerticalAlign.Top },
    3. 'left': { 'anchor': '__container__', 'align': HorizontalAlign.Start }
    4. }
    5. let AlignRue: Record<string, Record<string, string | VerticalAlign | HorizontalAlign>> = {
    6. 'top': { 'anchor': '__container__', 'align': VerticalAlign.Top },
    7. 'right': { 'anchor': '__container__', 'align': HorizontalAlign.End }
    8. }
    9. let Mleft: Record<string, number> = { 'left': 20 }
    10. let BWC: Record<string, number | string> = { 'width': 2, 'color': '#6699FF' }
    11. @Entry
    12. @Component
    13. struct Index {
    14. build() {
    15. RelativeContainer() {
    16. Row() {
    17. Text('row1')
    18. }
    19. .justifyContent(FlexAlign.Center)
    20. .width(100)
    21. .height(100)
    22. .backgroundColor('#a3cf62')
    23. .alignRules(AlignRus)
    24. .id("row1")
    25. Row() {
    26. Text('row2')
    27. }
    28. .justifyContent(FlexAlign.Center)
    29. .width(100)
    30. .height(100)
    31. .backgroundColor('#00ae9d')
    32. .alignRules(AlignRue)
    33. .id("row2")
    34. }.width(300).height(300)
    35. .margin(Mleft)
    36. .border(BWC)
    37. }
    38. }

  • 以兄弟元素为锚点。

    
      
    1. let AlignRus: Record<string, Record<string, string | VerticalAlign | HorizontalAlign>> = {
    2. 'top': { 'anchor': '__container__', 'align': VerticalAlign.Top },
    3. 'left': { 'anchor': '__container__', 'align': HorizontalAlign.Start }
    4. }
    5. let RelConB: Record<string, Record<string, string | VerticalAlign | HorizontalAlign>> = {
    6. 'top': { 'anchor': 'row1', 'align': VerticalAlign.Bottom },
    7. 'left': { 'anchor': 'row1', 'align': HorizontalAlign.Start }
    8. }
    9. let Mleft: Record<string, number> = { 'left': 20 }
    10. let BWC: Record<string, number | string> = { 'width': 2, 'color': '#6699FF' }
    11. @Entry
    12. @Component
    13. struct Index {
    14. build() {
    15. RelativeContainer() {
    16. Row() {
    17. Text('row1')
    18. }
    19. .justifyContent(FlexAlign.Center)
    20. .width(100)
    21. .height(100)
    22. .backgroundColor('#00ae9d')
    23. .alignRules(AlignRus)
    24. .id("row1")
    25. Row() {
    26. Text('row2')
    27. }
    28. .justifyContent(FlexAlign.Center)
    29. .width(100)
    30. .height(100)
    31. .backgroundColor('#a3cf62')
    32. .alignRules(RelConB)
    33. .id("row2")
    34. }.width(300).height(300)
    35. .margin(Mleft)
    36. .border(BWC)
    37. }
    38. }

  • 子组件锚点可以任意选择,但需注意不要相互依赖。

    
      
    1. @Entry
    2. @Component
    3. struct Index {
    4. build() {
    5. Row() {
    6. RelativeContainer() {
    7. Row(){Text('row1')}.justifyContent(FlexAlign.Center).width(100).height(100)
    8. .backgroundColor('#a3cf62')
    9. .alignRules({
    10. top: {anchor: "__container__", align: VerticalAlign.Top},
    11. left: {anchor: "__container__", align: HorizontalAlign.Start}
    12. })
    13. .id("row1")
    14. Row(){Text('row2')}.justifyContent(FlexAlign.Center).width(100)
    15. .backgroundColor('#00ae9d')
    16. .alignRules({
    17. top: {anchor: "__container__", align: VerticalAlign.Top},
    18. right: {anchor: "__container__", align: HorizontalAlign.End},
    19. bottom: {anchor: "row1", align: VerticalAlign.Center},
    20. })
    21. .id("row2")
    22. Row(){Text('row3')}.justifyContent(FlexAlign.Center).height(100)
    23. .backgroundColor('#0a59f7')
    24. .alignRules({
    25. top: {anchor: "row1", align: VerticalAlign.Bottom},
    26. left: {anchor: "row1", align: HorizontalAlign.Start},
    27. right: {anchor: "row2", align: HorizontalAlign.Start}
    28. })
    29. .id("row3")
    30. Row(){Text('row4')}.justifyContent(FlexAlign.Center)
    31. .backgroundColor('#2ca9e0')
    32. .alignRules({
    33. top: {anchor: "row3", align: VerticalAlign.Bottom},
    34. left: {anchor: "row1", align: HorizontalAlign.Center},
    35. right: {anchor: "row2", align: HorizontalAlign.End},
    36. bottom: {anchor: "__container__", align: VerticalAlign.Bottom}
    37. })
    38. .id("row4")
    39. }
    40. .width(300).height(300)
    41. .margin({left: 50})
    42. .border({width:2, color: "#6699FF"})
    43. }
    44. .height('100%')
    45. }
    46. }

设置相对于锚点的对齐位置

设置了锚点之后,可以通过alignRules属性的align设置相对于锚点的对齐位置。

在水平方向上,对齐位置可以设置为HorizontalAlign.Start、HorizontalAlign.Center、HorizontalAlign.End。

在竖直方向上,对齐位置可以设置为VerticalAlign.Top、VerticalAlign.Center、VerticalAlign.Bottom。

子组件位置偏移

子组件经过相对位置对齐后,可能尚未达到目标位置。开发者可根据需要设置额外偏移(offset)。当使用offset调整位置的组件作为锚点时,对齐位置为设置offset之前的位置。从API Version 11开始,新增了bias对象,建议API Version 11及以后的版本使用bias来设置额外偏移。


  1. @Entry
  2. @Component
  3. struct Index {
  4. build() {
  5. Row() {
  6. RelativeContainer() {
  7. Row() {
  8. Text('row1')
  9. }
  10. .justifyContent(FlexAlign.Center)
  11. .width(100)
  12. .height(100)
  13. .backgroundColor('#a3cf62')
  14. .alignRules({
  15. top: { anchor: "__container__", align: VerticalAlign.Top },
  16. left: { anchor: "__container__", align: HorizontalAlign.Start }
  17. })
  18. .id("row1")
  19. Row() {
  20. Text('row2')
  21. }
  22. .justifyContent(FlexAlign.Center)
  23. .width(100)
  24. .backgroundColor('#00ae9d')
  25. .alignRules({
  26. top: { anchor: "__container__", align: VerticalAlign.Top },
  27. right: { anchor: "__container__", align: HorizontalAlign.End },
  28. bottom: { anchor: "row1", align: VerticalAlign.Center },
  29. })
  30. .offset({
  31. x: -40,
  32. y: -20
  33. })
  34. .id("row2")
  35. Row() {
  36. Text('row3')
  37. }
  38. .justifyContent(FlexAlign.Center)
  39. .height(100)
  40. .backgroundColor('#0a59f7')
  41. .alignRules({
  42. top: { anchor: "row1", align: VerticalAlign.Bottom },
  43. left: { anchor: "row1", align: HorizontalAlign.End },
  44. right: { anchor: "row2", align: HorizontalAlign.Start }
  45. })
  46. .offset({
  47. x: -10,
  48. y: -20
  49. })
  50. .id("row3")
  51. Row() {
  52. Text('row4')
  53. }
  54. .justifyContent(FlexAlign.Center)
  55. .backgroundColor('#2ca9e0')
  56. .alignRules({
  57. top: { anchor: "row3", align: VerticalAlign.Bottom },
  58. bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
  59. left: { anchor: "__container__", align: HorizontalAlign.Start },
  60. right: { anchor: "row1", align: HorizontalAlign.End }
  61. })
  62. .offset({
  63. x: -10,
  64. y: -30
  65. })
  66. .id("row4")
  67. Row() {
  68. Text('row5')
  69. }
  70. .justifyContent(FlexAlign.Center)
  71. .backgroundColor('#30c9f7')
  72. .alignRules({
  73. top: { anchor: "row3", align: VerticalAlign.Bottom },
  74. bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
  75. left: { anchor: "row2", align: HorizontalAlign.Start },
  76. right: { anchor: "row2", align: HorizontalAlign.End }
  77. })
  78. .offset({
  79. x: 10,
  80. y: 20
  81. })
  82. .id("row5")
  83. Row() {
  84. Text('row6')
  85. }
  86. .justifyContent(FlexAlign.Center)
  87. .backgroundColor('#ff33ffb5')
  88. .alignRules({
  89. top: { anchor: "row3", align: VerticalAlign.Bottom },
  90. bottom: { anchor: "row4", align: VerticalAlign.Bottom },
  91. left: { anchor: "row3", align: HorizontalAlign.Start },
  92. right: { anchor: "row3", align: HorizontalAlign.End }
  93. })
  94. .offset({
  95. x: -15,
  96. y: 10
  97. })
  98. .backgroundImagePosition(Alignment.Bottom)
  99. .backgroundImageSize(ImageSize.Cover)
  100. .id("row6")
  101. }
  102. .width(300).height(300)
  103. .margin({ left: 50 })
  104. .border({ width: 2, color: "#6699FF" })
  105. }
  106. .height('100%')
  107. }
  108. }

多种组件的对齐布局

Row、Column、Flex、Stack等多种布局组件,可按照RelativeContainer组件规则进行对齐排布。


  1. @Entry
  2. @Component
  3. struct Index {
  4. @State value: number = 0
  5. build() {
  6. Row() {
  7. RelativeContainer() {
  8. Row()
  9. .width(100)
  10. .height(100)
  11. .backgroundColor('#a3cf62')
  12. .alignRules({
  13. top: { anchor: "__container__", align: VerticalAlign.Top },
  14. left: { anchor: "__container__", align: HorizontalAlign.Start }
  15. })
  16. .id("row1")
  17. Column()
  18. .width('50%')
  19. .height(30)
  20. .backgroundColor('#00ae9d')
  21. .alignRules({
  22. top: { anchor: "__container__", align: VerticalAlign.Top },
  23. left: { anchor: "__container__", align: HorizontalAlign.Center }
  24. })
  25. .id("row2")
  26. Flex({ direction: FlexDirection.Row }) {
  27. Text('1').width('20%').height(50).backgroundColor('#0a59f7')
  28. Text('2').width('20%').height(50).backgroundColor('#2ca9e0')
  29. Text('3').width('20%').height(50).backgroundColor('#0a59f7')
  30. Text('4').width('20%').height(50).backgroundColor('#2ca9e0')
  31. }
  32. .padding(10)
  33. .backgroundColor('#30c9f7')
  34. .alignRules({
  35. top: { anchor: "row2", align: VerticalAlign.Bottom },
  36. left: { anchor: "__container__", align: HorizontalAlign.Start },
  37. bottom: { anchor: "__container__", align: VerticalAlign.Center },
  38. right: { anchor: "row2", align: HorizontalAlign.Center }
  39. })
  40. .id("row3")
  41. Stack({ alignContent: Alignment.Bottom }) {
  42. Text('First child, show in bottom').width('90%').height('100%').backgroundColor('#a3cf62').align(Alignment.Top)
  43. Text('Second child, show in top').width('70%').height('60%').backgroundColor('#00ae9d').align(Alignment.Top)
  44. }
  45. .margin({ top: 5 })
  46. .alignRules({
  47. top: { anchor: "row3", align: VerticalAlign.Bottom },
  48. left: { anchor: "__container__", align: HorizontalAlign.Start },
  49. bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
  50. right: { anchor: "row3", align: HorizontalAlign.End }
  51. })
  52. .id("row4")
  53. }
  54. .width(300).height(300)
  55. .margin({ left: 50 })
  56. .border({ width: 2, color: "#6699FF" })
  57. }
  58. .height('100%')
  59. }
  60. }

组件尺寸

当同时存在前端页面设置的子组件尺寸和相对布局规则时,子组件的绘制尺寸依据约束规则确定。从API Version 11开始,此规则有所变化,子组件自身设置的尺寸优先级高于相对布局规则中的对齐锚点尺寸。因此,若要使子组件与锚点严格对齐,应仅使用alignRules,避免使用尺寸设置

说明

  • 根据约束条件和子组件自身的size属性无法确定子组件的大小,此时,不绘制该子组件。
  • 在同一方向上设置两个或更多锚点时,若这些锚点的位置顺序有误,该子组件将被视为大小为0而不予绘制。

  1. @Entry
  2. @Component
  3. struct Index {
  4. build() {
  5. Row() {
  6. RelativeContainer() {
  7. Row() {
  8. Text('row1')
  9. }
  10. .justifyContent(FlexAlign.Center)
  11. .width(100)
  12. .height(100)
  13. .backgroundColor('#a3cf62')
  14. .alignRules({
  15. top: { anchor: "__container__", align: VerticalAlign.Top },
  16. left: { anchor: "__container__", align: HorizontalAlign.Start }
  17. })
  18. .id("row1")
  19. Row() {
  20. Text('row2')
  21. }
  22. .justifyContent(FlexAlign.Center)
  23. .width(100)
  24. .backgroundColor('#00ae9d')
  25. .alignRules({
  26. top: { anchor: "__container__", align: VerticalAlign.Top },
  27. right: { anchor: "__container__", align: HorizontalAlign.End },
  28. bottom: { anchor: "row1", align: VerticalAlign.Center },
  29. })
  30. .id("row2")
  31. Row() {
  32. Text('row3')
  33. }
  34. .justifyContent(FlexAlign.Center)
  35. .height(100)
  36. .backgroundColor('#0a59f7')
  37. .alignRules({
  38. top: { anchor: "row1", align: VerticalAlign.Bottom },
  39. left: { anchor: "row1", align: HorizontalAlign.End },
  40. right: { anchor: "row2", align: HorizontalAlign.Start }
  41. })
  42. .id("row3")
  43. Row() {
  44. Text('row4')
  45. }.justifyContent(FlexAlign.Center)
  46. .backgroundColor('#2ca9e0')
  47. .alignRules({
  48. top: { anchor: "row3", align: VerticalAlign.Bottom },
  49. bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
  50. left: { anchor: "__container__", align: HorizontalAlign.Start },
  51. right: { anchor: "row1", align: HorizontalAlign.End }
  52. })
  53. .id("row4")
  54. Row() {
  55. Text('row5')
  56. }.justifyContent(FlexAlign.Center)
  57. .backgroundColor('#30c9f7')
  58. .alignRules({
  59. top: { anchor: "row3", align: VerticalAlign.Bottom },
  60. bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
  61. left: { anchor: "row2", align: HorizontalAlign.Start },
  62. right: { anchor: "row2", align: HorizontalAlign.End }
  63. })
  64. .id("row5")
  65. Row() {
  66. Text('row6')
  67. }
  68. .justifyContent(FlexAlign.Center)
  69. .backgroundColor('#ff33ffb5')
  70. .alignRules({
  71. top: { anchor: "row3", align: VerticalAlign.Bottom },
  72. bottom: { anchor: "row4", align: VerticalAlign.Bottom },
  73. left: { anchor: "row3", align: HorizontalAlign.Start },
  74. right: { anchor: "row3", align: HorizontalAlign.End }
  75. })
  76. .id("row6")
  77. .backgroundImagePosition(Alignment.Bottom)
  78. .backgroundImageSize(ImageSize.Cover)
  79. }
  80. .width(300).height(300)
  81. .margin({ left: 50 })
  82. .border({ width: 2, color: "#6699FF" })
  83. }
  84. .height('100%')
  85. }
  86. }

多个组件形成链

链的形成依赖于组件之间的关联关系。以组件A和组件B构成的最简水平链为例,其依赖关系为:锚点1 <-- 组件A <---> 组件B --> 锚点2,即A具有left锚点,B具有right锚点,同时A的right锚点与B的HorizontalAlign.Start对齐,B的left锚点与A的HorizontalAlign.End对齐。

  • 链的方向和格式在链头组件的chainMode接口中声明;链内元素的bias属性全部失效,链头元素的bias属性作为整个链的bias生效。链头是指在满足成链规则时链的第一个组件(在水平方向上,从左边开始,镜像语言中从右边开始;在竖直方向上,从上边开始)。
  • 如果链内所有元素的size超出链的锚点约束,超出部分将被均匀分配到链的两侧。在Packed链中,可以通过bias设置超出部分的分布。

  1. @Entry
  2. @Component
  3. struct Index {
  4. build() {
  5. Row() {
  6. RelativeContainer() {
  7. Row() {
  8. Text('row1')
  9. }
  10. .justifyContent(FlexAlign.Center)
  11. .width(80)
  12. .height(80)
  13. .backgroundColor('#a3cf62')
  14. .alignRules({
  15. left: { anchor: "__container__", align: HorizontalAlign.Start },
  16. right: { anchor: "row2", align: HorizontalAlign.Start },
  17. top: { anchor: "__container__", align: VerticalAlign.Top }
  18. })
  19. .id("row1")
  20. .chainMode(Axis.Horizontal, ChainStyle.SPREAD)
  21. Row() {
  22. Text('row2')
  23. }
  24. .justifyContent(FlexAlign.Center)
  25. .width(80)
  26. .height(80)
  27. .backgroundColor('#00ae9d')
  28. .alignRules({
  29. left: { anchor: "row1", align: HorizontalAlign.End },
  30. right: { anchor: "row3", align: HorizontalAlign.Start },
  31. top: { anchor: "row1", align: VerticalAlign.Top }
  32. })
  33. .id("row2")
  34. Row() {
  35. Text('row3')
  36. }
  37. .justifyContent(FlexAlign.Center)
  38. .width(80)
  39. .height(80)
  40. .backgroundColor('#0a59f7')
  41. .alignRules({
  42. left: { anchor: "row2", align: HorizontalAlign.End },
  43. right: { anchor: "__container__", align: HorizontalAlign.End },
  44. top: { anchor: "row1", align: VerticalAlign.Top }
  45. })
  46. .id("row3")
  47. Row() {
  48. Text('row4')
  49. }
  50. .justifyContent(FlexAlign.Center)
  51. .width(80)
  52. .height(80)
  53. .backgroundColor('#a3cf62')
  54. .alignRules({
  55. left: { anchor: "__container__", align: HorizontalAlign.Start },
  56. right: { anchor: "row5", align: HorizontalAlign.Start },
  57. center: { anchor: "__container__", align: VerticalAlign.Center }
  58. })
  59. .id("row4")
  60. .chainMode(Axis.Horizontal, ChainStyle.SPREAD_INSIDE)
  61. Row() {
  62. Text('row5')
  63. }
  64. .justifyContent(FlexAlign.Center)
  65. .width(80)
  66. .height(80)
  67. .backgroundColor('#00ae9d')
  68. .alignRules({
  69. left: { anchor: "row4", align: HorizontalAlign.End },
  70. right: { anchor: "row6", align: HorizontalAlign.Start },
  71. top: { anchor: "row4", align: VerticalAlign.Top }
  72. })
  73. .id("row5")
  74. Row() {
  75. Text('row6')
  76. }
  77. .justifyContent(FlexAlign.Center)
  78. .width(80)
  79. .height(80)
  80. .backgroundColor('#0a59f7')
  81. .alignRules({
  82. left: { anchor: "row5", align: HorizontalAlign.End },
  83. right: { anchor: "__container__", align: HorizontalAlign.End },
  84. top: { anchor: "row4", align: VerticalAlign.Top }
  85. })
  86. .id("row6")
  87. Row() {
  88. Text('row7')
  89. }
  90. .justifyContent(FlexAlign.Center)
  91. .width(80)
  92. .height(80)
  93. .backgroundColor('#a3cf62')
  94. .alignRules({
  95. left: { anchor: "__container__", align: HorizontalAlign.Start },
  96. right: { anchor: "row8", align: HorizontalAlign.Start },
  97. bottom: { anchor: "__container__", align: VerticalAlign.Bottom }
  98. })
  99. .id("row7")
  100. .chainMode(Axis.Horizontal, ChainStyle.PACKED)
  101. Row() {
  102. Text('row8')
  103. }
  104. .justifyContent(FlexAlign.Center)
  105. .width(80)
  106. .height(80)
  107. .backgroundColor('#00ae9d')
  108. .alignRules({
  109. left: { anchor: "row7", align: HorizontalAlign.End },
  110. right: { anchor: "row9", align: HorizontalAlign.Start },
  111. top: { anchor: "row7", align: VerticalAlign.Top }
  112. })
  113. .id("row8")
  114. Row() {
  115. Text('row9')
  116. }
  117. .justifyContent(FlexAlign.Center)
  118. .width(80)
  119. .height(80)
  120. .backgroundColor('#0a59f7')
  121. .alignRules({
  122. left: { anchor: "row8", align: HorizontalAlign.End },
  123. right: { anchor: "__container__", align: HorizontalAlign.End },
  124. top: { anchor: "row7", align: VerticalAlign.Top }
  125. })
  126. .id("row9")
  127. }
  128. .width(300).height(300)
  129. .margin({ left: 50 })
  130. .border({ width: 2, color: "#6699FF" })
  131. }
  132. .height('100%')
  133. }
  134. }

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐