HarmonyOS 鸿蒙Next RelativeContainer相对布局如何实现复杂布局?自定义组件智能性不足。

发布于 1周前 作者 nodeper 来自 鸿蒙OS

HarmonyOS 鸿蒙Next RelativeContainer相对布局如何实现复杂布局?自定义组件智能性不足。
<markdown _ngcontent-hhg-c237="" class="markdownPreContainer">

想要实现iOS中的导航栏效果,试来试去都达不到,虽然ArkUI提供了很多种布局方式,但是没有一种能实现需求。

先看需求:

  • 一、leftView布局靠左,rightView布局靠右,titleView布局居中;
  • 二、titleView左边不能超过leftView的右边,titleView右边不能超过rightView的左边;
  • 三、titleView的居中约束优先级最低,当titleView不够位置展示时,titleView会整体向剩余空间位置偏移;
  • 四、titleView宽度优先级最低,当titleView宽度超过剩余空间宽度时,宽度占满剩余空间,titleView的内容文字将被裁剪;

看看iOS中的实现效果:

这是iOS系统导航栏自适应效果,就算是自定义,也是可以通过自动布局或frame布局实现,这两种任意一种都能实现所有布局。

而鸿蒙给出的那么多种布局方式,只能实现很基本的布局,稍微复杂一点就办不到?

做鸿蒙开发太心累了。自定义组件太不智能了,想做通用一点的,就是这也不行,那也不行。

有没有真正的大神能帮忙解决一下。

</markdown>

关于HarmonyOS 鸿蒙Next RelativeContainer相对布局如何实现复杂布局?自定义组件智能性不足。的问题,您也可以访问:https://www.itying.com/category-93-b0.html 联系官网客服。
20 回复

有要学HarmonyOS AI的同学吗,联系我:https://www.itying.com/goods-1206.html

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

重点看在于:updateTitlePadding()

目前用下来唯一的问题是:动态改变左右菜单按钮时,标题位置会闪烁一下回到中间。

HarmonyOS的开发者模式提供了很多实用的工具,方便我们进行调试和优化。

主要RelativeContainer 不支持多个布局的优先级。

我自己的实现是通过计算多余空间来实现的。


export interface NavigationItems{
  items:NavigationItem[] | undefined
  left:boolean
}

@Component export struct NavigationBarView{

@Consume(‘pathStack’) pathStack:NavPathStack model : NavigationBarModel = new NavigationBarModel(’’, true)

barWidth = 0 titleWidth = 0 titleX = 0

@State title:string = ‘’ @State showBack : boolean = true @State leftItems?:NavigationItem[] = undefined @State rightItems?:NavigationItem[] = undefined

@State titlePadding:Padding = { left:0, right:0}

updateTitlePadding(){ if (this.barWidth == 0 || this.titleWidth == 0){ return }

<span class="hljs-keyword"><span class="hljs-keyword">let</span></span> titleLeftSpace = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.titleX
<span class="hljs-keyword"><span class="hljs-keyword">let</span></span> titleRightSpace = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.barWidth - (<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.titleX + <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.titleWidth)


<span class="hljs-keyword"><span class="hljs-keyword">let</span></span> spaceDiff = titleLeftSpace - titleRightSpace

<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (<span class="hljs-built_in"><span class="hljs-built_in">Math</span></span>.abs(spaceDiff) &gt; <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.barWidth/<span class="hljs-number"><span class="hljs-number">2</span></span>){
  <span class="hljs-keyword"><span class="hljs-keyword">return</span></span>
}

<span class="hljs-keyword"><span class="hljs-keyword">let</span></span> titlePadding:Padding
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (spaceDiff == <span class="hljs-number"><span class="hljs-number">0</span></span>){
  titlePadding = { left:<span class="hljs-number"><span class="hljs-number">0</span></span>, right:<span class="hljs-number"><span class="hljs-number">0</span></span>}
}
<span class="hljs-keyword"><span class="hljs-keyword">else</span></span> <span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (spaceDiff &gt; <span class="hljs-number"><span class="hljs-number">0</span></span>){
  titlePadding = { left:<span class="hljs-number"><span class="hljs-number">0</span></span>, right:spaceDiff}
}
<span class="hljs-keyword"><span class="hljs-keyword">else</span></span> {
  titlePadding = { left:-spaceDiff, right:<span class="hljs-number"><span class="hljs-number">0</span></span>}
}

<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (titlePadding.left != <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.titlePadding.left
  || titlePadding.right != <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.titlePadding.right){
  <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.titlePadding = titlePadding
}

}

public onBackClick = (event: ClickEvent) => { // router.back() this.pathStack.pop() }

@BuilderParam backBuilder: () => void = this.defaultBackBuilder @Builder private defaultBackBuilder() { Row() { Image($r(“app.media.navigation_back”)) .colorFilter(ColorFilterUtils.primaryColorFilter.value) .width(20) .height(20)

    <span class="hljs-comment"><span class="hljs-comment">// .fillColor(this.backImageColor)</span></span>
    <span class="hljs-comment"><span class="hljs-comment">// .margin({left: this.backImageLeftMargin})</span></span>
    <span class="hljs-comment"><span class="hljs-comment">// Text("返回")</span></span>
    <span class="hljs-comment"><span class="hljs-comment">//   .textOverflow({overflow: TextOverflow.Ellipsis})</span></span>
    <span class="hljs-comment"><span class="hljs-comment">//   .maxLines(1)</span></span>
      <span class="hljs-comment"><span class="hljs-comment">// .fontSize(this.backTitleFont)</span></span>
      <span class="hljs-comment"><span class="hljs-comment">// .fontWeight(this.backTitleFontWeight)</span></span>
      <span class="hljs-comment"><span class="hljs-comment">// .fontColor(this.backTitleColor)</span></span>
      <span class="hljs-comment"><span class="hljs-comment">// .margin({left: this.backTitleLeftMargin})</span></span>
}
.onClick(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.onBackClick)

}

@BuilderParam itemsBuilder: ($$:NavigationItems) => void = this.defaultItemsBuilder

@Builder defaultItemsBuilder($$:NavigationItems){ if ($$.items != null){ Row({space:8}) { ForEach($$.items, (navigationItem: NavigationItem) => { this.navigationItemBuilder(navigationItem) }) } // .backgroundColor(’#FF0000’) } }

@BuilderParam navigationItemBuilder: (item:NavigationItem) => void = this.defaultNavigationItemBuilder @Builder defaultNavigationItemBuilder(item:NavigationItem){ NavigationItemView({ model: item as NavigationItem }) }

aboutToAppear(): void { this.model.updateViewCallback = () =>{ this.updateView() } this.updateView() }

updateView(){ this.title = this.model.title this.showBack = this.model.showBack this.leftItems = this.model.leftItems this.rightItems = this.model.rightItems }

build() {

Column(){
  Row({space:<span class="hljs-number"><span class="hljs-number">8</span></span>}){
    <span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.model.showBack){
      <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.backBuilder()
    }


    <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.itemsBuilder({items: <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.leftItems, left:<span class="hljs-literal"><span class="hljs-literal">true</span></span>})

    Text(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.model.title)
      .maxLines(<span class="hljs-number"><span class="hljs-number">1</span></span>)
      .padding(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.titlePadding)
      .textAlign(TextAlign.Center)
      .fontSize(<span class="hljs-number"><span class="hljs-number">15</span></span>)
      .fontWeight(FontWeight.Bold)
      .fontColor($r(<span class="hljs-string"><span class="hljs-string">'app.color.textDark'</span></span>))
      .layoutWeight(<span class="hljs-number"><span class="hljs-number">1</span></span>)
      <span class="hljs-comment"><span class="hljs-comment">// .backgroundColor('#bbccaa')</span></span>
      .onAreaChange((oldValue: Area, newValue: Area) =&gt; {
        <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.titleWidth = <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> <span class="hljs-built_in"><span class="hljs-built_in">Number</span></span>(newValue.width).valueOf()
        <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.titleX = <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> <span class="hljs-built_in"><span class="hljs-built_in">Number</span></span>(newValue.position.x).valueOf()
        <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.updateTitlePadding()
      })

    <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.itemsBuilder({items: <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.rightItems, left:<span class="hljs-literal"><span class="hljs-literal">false</span></span>})
  }
  .padding({left: <span class="hljs-number"><span class="hljs-number">8</span></span>, right:<span class="hljs-number"><span class="hljs-number">8</span></span>})
  .backgroundColor($r(<span class="hljs-string"><span class="hljs-string">"app.color.navigation_background"</span></span>))
  .height(<span class="hljs-number"><span class="hljs-number">40</span></span>)
  .width(<span class="hljs-string"><span class="hljs-string">"100%"</span></span>)
  .onAreaChange((oldValue: Area, newValue: Area) =&gt; {
    <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.barWidth = <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> <span class="hljs-built_in"><span class="hljs-built_in">Number</span></span>(newValue.width).valueOf()
    <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.updateTitlePadding()
  })

  Row().width(<span class="hljs-string"><span class="hljs-string">'100%'</span></span>).height(<span class="hljs-number"><span class="hljs-number">1</span></span>).backgroundColor($r(<span class="hljs-string"><span class="hljs-string">'app.color.main_line'</span></span>))
}<span class="hljs-comment"><span class="hljs-comment">//.margin({left:-30})</span></span>

}

}<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

在List中使用RelativeContainer,不指定高度的话 渲染不出 , 但是高度不能指定啊,由内容控制的

我理解List的高度是要指定的,内容只是控制内容的高度,内容高度超过List高度的话,内容可以滚动。

Snipaste_2024-03-18_08-11-25.png
<button id="copyCode" style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; width: 62px; right: 7px; font-size: 14px; display: none;">复制</button>

要这种?不就是多判断一下左右组件的有无,并限制一下左右控件的宽度嘛~安卓也没有现成的吧?

辛苦代码贴一下,应该和我想的差不多。我发文举例的也是相对简单的场景,如果按钮都是同等大小的,是会好处理一点。但是我们有时会设置文字按钮,例如“完成”、“历史订单”,文字按钮的宽度我们无法预知。

还有更更复杂一点的场景,就是按钮可以由调用方自定义组件,宽度的控制权自然交给调用方。

有问题,就酌情修改一下吧 @Preview @Component export struct ActionBar { @BuilderParam leftView?: () => void = undefined @BuilderParam rightView?: () => void = undefined title: ResourceStr = ‘标题栏’ @State rightViewMinWidth: number = 0 @State leftViewMinWidth: number = 0

build() { Row() { Row() { if (this.leftView) { this.leftView() } } .onAreaChange((_, newValue) => { if (!this.rightView) { this.rightViewMinWidth = newValue.width as number } }) .constraintSize({ minWidth: this.leftView ? this.leftViewMinWidth : 0 })

  Text(this.title)
    .fontSize(18)
    .textOverflow({ overflow: TextOverflow.Ellipsis })
    .layoutWeight(1)
    .width('100%')
    .textAlign(TextAlign.Center)
    .maxLines(1)
    .margin({ left: 15 + this.leftViewMinWidth, right: 15 + this.rightViewMinWidth })

  Row() {
    if (this.rightView) {
      this.rightView()
    }
  }
  .onAreaChange((_, newValue) =&gt; {
    if (!this.leftView) {
      this.leftViewMinWidth = newValue.width as number
    }
  })
  .constraintSize({ minWidth: this.rightView ? this.rightViewMinWidth : 0 })
}
.width('100%')
.alignItems(VerticalAlign.Center)
.height(45)
.padding({ left: 10, right: 10 })

} }

主要还是现在组件对宽高的自适应支持的不行。

感觉现在应该是性能跟不上,所以没有开放对组件自适应。

其实自适应必须有的,不可能每次都根据内容去算宽高吧。

安卓默认有这样的控件吗?

肯定有呀,Android的每个控件都是支持自适应的。如果没有,就没有人会给鸿蒙提问题了呀。其实每个控件,在拿到数据之前,是不知道这个控件显示多大的,需要根据数据的格式和内容来判断的。自适应会导致控件的大小位置多次计算,数据多次绘制。感觉鸿蒙对这个性能优化应该是没有做好,所以现在还支持不了自适应。

咱俩都互相没明白彼此的意思

提供个参考,Row实现就挺好,titleView也可以自定义。
Snipaste_2024-03-13_16-48-10.png
[@Preview](/user/Preview)
[@Component](/user/Component)
export struct ActionBar {
  [@BuilderParam](/user/BuilderParam) leftView: () => void
  [@BuilderParam](/user/BuilderParam) rightView: () => void
  title: ResourceStr = '标题栏'

build() { Row() { this.leftView() Text(this.title) .fontSize(18) .textOverflow({ overflow: TextOverflow.Ellipsis }) .layoutWeight(1) .width(‘100%’) .textAlign(TextAlign.Center) .maxLines(1) .margin({ left: 15, right: 15 }) this.rightView() } .width(‘100%’) .alignItems(VerticalAlign.Center) .height(45) .padding({ left: 10, right: 10 }) } }<button id="copyCode" style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; width: 62px; right: 7px; font-size: 14px; display: none;">复制</button>

@Entry
@Component
export struct Index {
@Builder
leftView() {
Image($r(‘app.media.icon’)).width(40).aspectRatio(1)
}

@Builder rightView() { Image($r(‘app.media.icon’)).width(40).aspectRatio(1) Image($r(‘app.media.icon’)).width(40).aspectRatio(1).margin({ left: 10 }) }

@Builder rightView2() { Image($r(‘app.media.icon’)).width(40).aspectRatio(1) }

build() { Column({ space: 15 }) { ActionBar({ title: ‘标题居中显示’, leftView: () => this.leftView(), rightView: () => this.rightView2() })

  ActionBar({
    <span class="hljs-attribute">title</span>: <span class="hljs-string">'标题向剩余空间偏移'</span>,
    <span class="hljs-attribute">leftView</span>: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-keyword">this</span>.leftView(),
    <span class="hljs-attribute">rightView</span>: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-keyword">this</span>.rightView()
  })

  ActionBar({
    <span class="hljs-attribute">title</span>: <span class="hljs-string">'标题超长时占满剩余空间标题超长了'</span>,
    <span class="hljs-attribute">leftView</span>: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-keyword">this</span>.leftView(),
    <span class="hljs-attribute">rightView</span>: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-keyword">this</span>.rightView()
  })
}
.width(<span class="hljs-string">'100%'</span>)
.height(<span class="hljs-string">'100%'</span>)

} }<button id="copyCode" style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; width: 62px; right: 7px; font-size: 14px; display: none;">复制</button>

抱歉,你应该没完全理解我的意思。就拿第一个样例来说。我的左边一个按钮,右边两个按钮,但是标题依然能相对屏幕水平居中。而你的设置了左边一个按钮,右边也是一个按钮,才能使标题居中,你如果让左右两边按钮不一致,标题就不能相对屏幕水平居中了。我想吐槽的点就在于这里。(当然,针对具体场景单独去实现也是可以做到的,但是这样就做不到通用)

你这个意思我明白:可以不用RelativeContainer,用Stack来做。

Stack这个布局跟Android里面的RelativeLayout差不多。

回到顶部