Skip to content

0x013-鸿蒙应用开发笔记2-ArkUI

布局/容器组件

Row / Column

|400

对齐方式 VerticalAlign/HorizontalAlign

js
@Entry
@Component
struct Index {
  build() {
    Row({ space: 10 }) {
      Text("row1").height(80).padding({ right: 10, left: 10 }).backgroundColor('red')
      Text("row2").height(40).padding({ right: 10, left: 10 }).backgroundColor('green')
      Text("row3").padding({ right: 10, left: 10 }).height(50).backgroundColor('black').foregroundColor('white')
    }
    .alignItems(VerticalAlign.Bottom) 
  }
}
@Entry
@Component
struct Index {
  build() {
    Row({ space: 10 }) {
      Text("row1").height(80).padding({ right: 10, left: 10 }).backgroundColor('red')
      Text("row2").height(40).padding({ right: 10, left: 10 }).backgroundColor('green')
      Text("row3").padding({ right: 10, left: 10 }).height(50).backgroundColor('black').foregroundColor('white')
    }
    .alignItems(VerticalAlign.Bottom) 
  }
}

|280

排列方式 justifyContent

效果与css的flex布局

js
@Entry
@Component
struct Index {
  build() {
    Row({ space: 10 }) {
      Text("row1").height(80).padding({ right: 10, left: 10 }).backgroundColor('red')
      Text("row2").height(40).padding({ right: 10, left: 10 }).backgroundColor('green')
      Text("row3").padding({ right: 10, left: 10 }).height(50).backgroundColor('black').foregroundColor('white')
    }
    .width('100%')
    .alignItems(VerticalAlign.Bottom) 
    .justifyContent(FlexAlign.SpaceBetween)  
  }
}
@Entry
@Component
struct Index {
  build() {
    Row({ space: 10 }) {
      Text("row1").height(80).padding({ right: 10, left: 10 }).backgroundColor('red')
      Text("row2").height(40).padding({ right: 10, left: 10 }).backgroundColor('green')
      Text("row3").padding({ right: 10, left: 10 }).height(50).backgroundColor('black').foregroundColor('white')
    }
    .width('100%')
    .alignItems(VerticalAlign.Bottom) 
    .justifyContent(FlexAlign.SpaceBetween)  
  }
}

|280

自适应伸缩Blank()

使用Blank(), 类比swiftui的spacer()。

尺寸比例拉伸 layoutWeight

例如 .layoutWeight(2).layoutWeight(1) 宽度2呗

Stack

效果类似SwiftUI的VStack

Flex

js
Flex({
      // 主轴 
      justifyContent: FlexAlign.SpaceBetween, 
      alignItems: ItemAlign.Center, 
      wrap: FlexWrap.Wrap 
    }) {
      Text("row1").height(80).padding({ right: 10, left: 10 }).backgroundColor('red')
      Text("row2").height(40).padding({ right: 10, left: 10 }).backgroundColor('green')
      Text("row3").padding({ right: 10, left: 10 }).height(50).backgroundColor('black').foregroundColor('white')
    }
Flex({
      // 主轴 
      justifyContent: FlexAlign.SpaceBetween, 
      alignItems: ItemAlign.Center, 
      wrap: FlexWrap.Wrap 
    }) {
      Text("row1").height(80).padding({ right: 10, left: 10 }).backgroundColor('red')
      Text("row2").height(40).padding({ right: 10, left: 10 }).backgroundColor('green')
      Text("row3").padding({ right: 10, left: 10 }).height(50).backgroundColor('black').foregroundColor('white')
    }

RelativeContainer

示例:

js
RelativeContainer() {
  Text()
	.height(50)
	.width(100)
	.backgroundColor(Color.Black)
	.id("black")

  Text(' hi~ ')
	.border({ radius: 8 })
	.backgroundColor(Color.Orange)
	.alignRules({
	  top: {
		'anchor': 'black', 'align': VerticalAlign.Top
	  },
	  left: {
		'anchor': 'black', 'align': HorizontalAlign.End
	  }
	})
	.offset({
	  left: -10,
	  top: -6
	})
}
RelativeContainer() {
  Text()
	.height(50)
	.width(100)
	.backgroundColor(Color.Black)
	.id("black")

  Text(' hi~ ')
	.border({ radius: 8 })
	.backgroundColor(Color.Orange)
	.alignRules({
	  top: {
		'anchor': 'black', 'align': VerticalAlign.Top
	  },
	  left: {
		'anchor': 'black', 'align': HorizontalAlign.End
	  }
	})
	.offset({
	  left: -10,
	  top: -6
	})
}

展示效果:

GridRow、GridCol

使用栅格的默认列数12列(可以通过columns属性修改)。默认有断点规则,可以根据不同的屏幕宽度定制col的宽度。

js
@Entry
@Component
struct Index {
  @State bgColors: Color[] =
    [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Pink, Color.Grey, Color.Blue, Color.Brown];

  build() {
    GridRow() {
      ForEach(this.bgColors, (color: Color, index?: number | undefined) => {
        GridCol({
	      // span: 2 // 固定比例
	      // 动态比例
          span: {
            xs: 2, // 在最小宽度类型设备上,栅格子组件占据的栅格容器2列。
            sm: 3, // 在小宽度类型设备上,栅格子组件占据的栅格容器3列。
            md: 4, // 在中等宽度类型设备上,栅格子组件占据的栅格容器4列。
            lg: 6, // 在大宽度类型设备上,栅格子组件占据的栅格容器6列。
            xl: 8, // 在特大宽度类型设备上,栅格子组件占据的栅格容器8列。
            xxl: 12 // 在超大宽度类型设备上,栅格子组件占据的栅格容器12列。
          }
        }) {
          Row() {
            Text(`${index}`)
          }.width("100%").height('50vp')
        }.backgroundColor(color)
      })
    }
  }
}
@Entry
@Component
struct Index {
  @State bgColors: Color[] =
    [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Pink, Color.Grey, Color.Blue, Color.Brown];

  build() {
    GridRow() {
      ForEach(this.bgColors, (color: Color, index?: number | undefined) => {
        GridCol({
	      // span: 2 // 固定比例
	      // 动态比例
          span: {
            xs: 2, // 在最小宽度类型设备上,栅格子组件占据的栅格容器2列。
            sm: 3, // 在小宽度类型设备上,栅格子组件占据的栅格容器3列。
            md: 4, // 在中等宽度类型设备上,栅格子组件占据的栅格容器4列。
            lg: 6, // 在大宽度类型设备上,栅格子组件占据的栅格容器6列。
            xl: 8, // 在特大宽度类型设备上,栅格子组件占据的栅格容器8列。
            xxl: 12 // 在超大宽度类型设备上,栅格子组件占据的栅格容器12列。
          }
        }) {
          Row() {
            Text(`${index}`)
          }.width("100%").height('50vp')
        }.backgroundColor(color)
      })
    }
  }
}

|400

@ohos.mediaquery

List

js
@Entry
@Component
struct Index {
  @State bgColors: Color[] =
    [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Pink, Color.Grey, Color.Blue, Color.Brown];

  build() {
    List() {
      ForEach(this.bgColors, (item: Color, idx) => {
        ListItem() {
          Text('' + idx)
            .textAlign(TextAlign.Center)
            .foregroundColor(Color.Black)
            .width(100)
            .height(40)
            .backgroundColor(item)
        }
      })
    }
    .backgroundColor(Color.Black)
    .listDirection(Axis.Vertical)
    .alignListItem(ListItemAlign.Center)
  }
}
@Entry
@Component
struct Index {
  @State bgColors: Color[] =
    [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Pink, Color.Grey, Color.Blue, Color.Brown];

  build() {
    List() {
      ForEach(this.bgColors, (item: Color, idx) => {
        ListItem() {
          Text('' + idx)
            .textAlign(TextAlign.Center)
            .foregroundColor(Color.Black)
            .width(100)
            .height(40)
            .backgroundColor(item)
        }
      })
    }
    .backgroundColor(Color.Black)
    .listDirection(Axis.Vertical)
    .alignListItem(ListItemAlign.Center)
  }
}

|300

其他能力:

  • 间距 space
  • 分割线
  • 粘性标题
  • 分组列表
  • 支持滚动到某位置
  • 支持响应滚动位置
  • 侧滑支持
  • 下拉刷新与上拉加载

Grid

Swiper

UI组件

@CustomDialog

CustomDialog装饰器用于装饰自定义弹框,此装饰器内进行自定义内容(也就是弹框内容)。

示例:

js
@Entry
@Component
struct Index {
  dialogController: CustomDialogController = new CustomDialogController({ 
    builder: CustomDialogExample(), 
  }) 

  build() {
    Column() {
      Button() {
        Text("open dialog")
          .foregroundColor(Color.White)
          .padding(20)
      }
      .onClick(() => {
        this.dialogController.open()
      })
    }
  }
}

@CustomDialog   
struct CustomDialogExample { 
  controller: CustomDialogController = new CustomDialogController({   
    builder: CustomDialogExample({}),   
  })   
  
  cancel?: () => void  
  confirm?: () => void  
  
  build() {  
    Column() {  
      Text('我是内容').fontSize(20).margin({ top: 10, bottom: 10 })  
  
      Row() {  
        Blank()  
        Button('cancel')  
          .onClick(() => {  
            this.controller.close()  
            if (this.cancel) {  
              this.cancel()  
            }  
          }).backgroundColor(0xffffff).fontColor(Color.Black)  
        Blank()  
  
        Button('confirm')  
          .onClick(() => {  
            this.controller.close()  
            if (this.confirm) {  
              this.confirm()  
            }  
          }).backgroundColor(0xffffff).fontColor(Color.Red)  
        Blank()  
  
      }.width('100%')  
    }  
  }}
@Entry
@Component
struct Index {
  dialogController: CustomDialogController = new CustomDialogController({ 
    builder: CustomDialogExample(), 
  }) 

  build() {
    Column() {
      Button() {
        Text("open dialog")
          .foregroundColor(Color.White)
          .padding(20)
      }
      .onClick(() => {
        this.dialogController.open()
      })
    }
  }
}

@CustomDialog   
struct CustomDialogExample { 
  controller: CustomDialogController = new CustomDialogController({   
    builder: CustomDialogExample({}),   
  })   
  
  cancel?: () => void  
  confirm?: () => void  
  
  build() {  
    Column() {  
      Text('我是内容').fontSize(20).margin({ top: 10, bottom: 10 })  
  
      Row() {  
        Blank()  
        Button('cancel')  
          .onClick(() => {  
            this.controller.close()  
            if (this.cancel) {  
              this.cancel()  
            }  
          }).backgroundColor(0xffffff).fontColor(Color.Black)  
        Blank()  
  
        Button('confirm')  
          .onClick(() => {  
            this.controller.close()  
            if (this.confirm) {  
              this.confirm()  
            }  
          }).backgroundColor(0xffffff).fontColor(Color.Red)  
        Blank()  
  
      }.width('100%')  
    }  
  }}

额外的:

  • 支持修改样式
  • 支持弹窗嵌套
  • 支持设置动画

富文本字符串

参考 属性字符串(StyledString/MutableStyledString

组件封装优化

[分层架构与模块化设计 、ArkUI组件封装与UI动态操作及解决方案(https://developer.huawei.com/consumer/cn/training/course/live/C101719310316478739)

AttributeModifier

参考: AttributeModifier-自定义扩展

Q&A

如何根据设计图进行开发

  • vp 屏幕密度相关像素,根据屏幕像素密度转换为屏幕物理像素,当数值不带单位时,默认单位vp1vp约等于3px? vp与px的比例与屏幕像素密度有关。
  • px 屏幕物理像素单位
  • fp 字体像素,与vp类似适用屏幕密度变化,随系统字体大小设置变化。
  • lpx 视窗逻辑像素单位,lpx单位为实际屏幕宽度与逻辑宽度(通过designWidth配置)的比值,designWidth默认值为720。当designWidth为720时,在实际宽度为1440物理像素的屏幕上,1lpx为2px大小。$r(20fp)

开发建议根据设计图基准设置好designWidth,使用lpx。 其他单位系统提供的函数转换。

修改main_pages.json文件:

json
{
  "src": [
    "pages/Index",
    "pages/Second"
  ],
  "window": {
    "designWidth": 720, 
    "autoDesignWidth": false 
  }
}
{
  "src": [
    "pages/Index",
    "pages/Second"
  ],
  "window": {
    "designWidth": 720, 
    "autoDesignWidth": false 
  }
}

演示:

js
Column() {
      Text(`当前:1vp = ${vp2px(1)}px \n width=260vp`)
        .foregroundColor(Color.White)
        .height(70)
        .width("260vp") 
        .backgroundColor(Color.Black)
        .margin({ bottom: 10 })

      // 预览模式显示为0,真机模式显示正常
      Text(`当前:1lpx = ${lpx2px(1)}px \n width=360lpx`)
        .height(70)
        .width("360lpx") 
        .backgroundColor(Color.Black)
        .margin({ bottom: 10 })

      Text(`width = 50%`)
        .height(70)
        .width("50%")
        .backgroundColor(Color.Black)
    }
    .alignItems(HorizontalAlign.Start)
    .foregroundColor(Color.White)
Column() {
      Text(`当前:1vp = ${vp2px(1)}px \n width=260vp`)
        .foregroundColor(Color.White)
        .height(70)
        .width("260vp") 
        .backgroundColor(Color.Black)
        .margin({ bottom: 10 })

      // 预览模式显示为0,真机模式显示正常
      Text(`当前:1lpx = ${lpx2px(1)}px \n width=360lpx`)
        .height(70)
        .width("360lpx") 
        .backgroundColor(Color.Black)
        .margin({ bottom: 10 })

      Text(`width = 50%`)
        .height(70)
        .width("50%")
        .backgroundColor(Color.Black)
    }
    .alignItems(HorizontalAlign.Start)
    .foregroundColor(Color.White)

|300

参考:

像素单位-ArkTS组件-ArkUI(方舟UI框架)-应用框架 - 华为HarmonyOS开发者
尺寸设置-通用属性-组件通用信息-ArkTS组件-ArkUI(方舟UI框架)-应用框架 - 华为HarmonyOS开发者

持久化存储示例

用户偏好设举例:

js
import { preferences } from '@kit.ArkData';

saveCache(key: string, value: string, dbName: string = 'myStore') {
let preference = preferences.getPreferences(getContext(this), dbName)
  .then(async (p) => {
	await p.put(key, value)
	p.flush()
  }).catch((err: Error) => {
	console.error('put the preferences failed, err: ' + err)
  });
}

getCache(key: string, dbName: string = 'myStore') {
let preference = preferences.getPreferences(getContext(this), dbName)
  .then(async (p) => {
	const result = await p.get(key, '') as string
	console.log(result)
  })
}
import { preferences } from '@kit.ArkData';

saveCache(key: string, value: string, dbName: string = 'myStore') {
let preference = preferences.getPreferences(getContext(this), dbName)
  .then(async (p) => {
	await p.put(key, value)
	p.flush()
  }).catch((err: Error) => {
	console.error('put the preferences failed, err: ' + err)
  });
}

getCache(key: string, dbName: string = 'myStore') {
let preference = preferences.getPreferences(getContext(this), dbName)
  .then(async (p) => {
	const result = await p.get(key, '') as string
	console.log(result)
  })
}

是否需要请求网络权限

新版似乎不需要请求网络权限了。

json
// module.json5
"requestPermissions": [
  {
	"name": "ohos.permission.INTERNET",
	"reason": "$string:network_dependency_reason",
	"usedScene": {
	  "abilities": [
		"EntryAbility"
	  ],
	  "when": "inuse"
	}
  }
],
// module.json5
"requestPermissions": [
  {
	"name": "ohos.permission.INTERNET",
	"reason": "$string:network_dependency_reason",
	"usedScene": {
	  "abilities": [
		"EntryAbility"
	  ],
	  "when": "inuse"
	}
  }
],

列表的上拉刷新下拉加载

参考示例:新闻数据加载