HarmonyOS 鸿蒙Next RelativeContainer相对布局如何实现复杂布局?自定义组件智能性不足。
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 联系官网客服。
有要学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) > <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 > <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) => {
<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) => {
<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的高度是要指定的,内容只是控制内容的高度,内容高度超过List高度的话,内容可以滚动。
辛苦代码贴一下,应该和我想的差不多。我发文举例的也是相对简单的场景,如果按钮都是同等大小的,是会好处理一点。但是我们有时会设置文字按钮,例如“完成”、“历史订单”,文字按钮的宽度我们无法预知。
还有更更复杂一点的场景,就是按钮可以由调用方自定义组件,宽度的控制权自然交给调用方。
有问题,就酌情修改一下吧 @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) => {
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 })
} }
主要还是现在组件对宽高的自适应支持的不行。
感觉现在应该是性能跟不上,所以没有开放对组件自适应。
其实自适应必须有的,不可能每次都根据内容去算宽高吧。
安卓默认有这样的控件吗?
咱俩都互相没明白彼此的意思
提供个参考,Row实现就挺好,titleView也可以自定义。 [@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> =></span> <span class="hljs-keyword">this</span>.leftView(), <span class="hljs-attribute">rightView</span>: <span class="hljs-function"><span class="hljs-params">()</span> =></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> =></span> <span class="hljs-keyword">this</span>.leftView(), <span class="hljs-attribute">rightView</span>: <span class="hljs-function"><span class="hljs-params">()</span> =></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差不多。