ArkUI采用的声明式UI,写法类似 flutter 语法,思想和属性同 Flutter 基本一致;
常用组件
单位统一配置
在鸿蒙应用中,没有web中的 px 单位;鸿蒙采用 vp(屏幕密度单位)即所有尺寸后面追加 vp,如想撑满整个屏幕容器可以用 “100%” 字符串表示;
为了统一调整这些尺寸,官方建议使用 Resource
数据类型统一配置,定义一个 xxx.ets
文件
export default class CommonConstants {
static readonly FULL_LENGTH: string = '100%'
}
使用
import {CommonConstants} from "./xxx"
Image("http://...").width(CommonConstants.FULL_LENGTH)
Text 文本组件
属性
属性 | 注释 |
---|---|
fonstSize(number) | 字体大小,单位 vp |
fontWeight(FontWeight.Bold) | 字体加粗 |
fontColor(“十六进制”) | 字体颜色 |
opacity(number) | 透明度0-1 |
decoration({type:TextDecorationType.LineThrough}) | 下划线 |
示例
Text("鸿蒙todoList")
.fontSize(28)
.fontWeight(FontWeight.Bold)
.padding(10)
.opacity(0.6)
.decoration({type:TextDecorationType.LineThrough})
Image 图片组件
语法规范 Image(src:string | PixelMap | Resource)
- 使用网络图片需要添加授权,在
module.json5
配置
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
- 使用 Resource 中需要将图片放到
AppScope / resources / base / media
的文件夹下
Image($r("app.media.xxx.png"))
Video组件
语法规范 Image(src:string | PixelMap | Resource)
和 Image 元素一致,但可以自定义播放器的控制器
属性
属性 | 注释 |
---|---|
previewUri:string | TS等格式 |
objectFit(ImageFit.Cintain) | 属性保持比例 |
TextInput 输入框组件
语法 textInput({options})
options
属性 | 注释 |
---|---|
placeholder | 文本提示 |
属性
属性 | 注释 |
---|---|
maxLength(number) | 最大字符长度 |
type(InputType.Number) | 设置文本类型 |
onChange((value:string)=>{}) | change事件监听 |
Button 按钮组件
语法 Button("文本",{options})
options
属性 | 注释 |
---|---|
types:ButtonType.Capsule | 设置按钮样式 Capsule 圆角 | Circle 圆形 | Normal 直角 |
容器组件
Row Column 行列容器
和 Flutter 一致,都采用了 flex 思想,属性也和 flex 保持一致
语法格式
Row({space:string | number}) /*space 表示主轴方向的间距*/
属性
属性 | 注释 |
---|---|
justifyConten | 主轴方向 |
dlignItems | 交叉轴方向 |
List 列表容器
滚动类容器,**必须包含子组件 ListItem ** 思想同 flutter 一致
语法
list(options){ ListItem(){}, ListItem(){} , ... }
options : Object
属性 | 注释 |
---|---|
space:number | 设置间距 |
initialIndex:number | 加载的起始位置 |
scroller:Scroller | 控制 list 组件的滚动 |
属性
属性 | 注释 |
---|---|
ListDireCtion() | 设置 list 组件的排列方向 |
divider() | 设置分割线 |
divider 分割线
属性 | 注释 |
---|---|
strokeWidth:number | 线宽 |
color:string | 颜色 |
startMargin:number | 线与侧边起始端距离 |
endMargin:number | 线与侧边结束端距离 |
Grid 网格容器
思想同 flutter 一致 必须包含子组件 GridItem
语法
Grid(scrder?:Scroller)
属性
属性 | 方法 |
---|---|
columnsTemplate() | 列的数量 “1fr 1fr 1fr” |
rowsTemplate() | 行的数量 (不设置行数会出现滑动效果) |
columnsGap() | 列间距 |
rowsGap() | 行间距 |
Tab页签组件
需要子组件 TabContent 一同使用
语法
Tab(options){}
options:Object
属性 | 注释 |
---|---|
barPosition: BarPosition.Start | End | 设置 tabs 的位置,与 vertical 属性强关联 |
index:number | 设置初始值 |
controller | 设置 tab 控制器, 用于控制 tabs 组件页签切换 |
属性
属性 | 注释 |
---|---|
vertical() | 设置 Tabs 方向是否为纵向,默认是 false |
barMode() | Tabbar 布局模式 Fixed 平均分,Scrllable 实际宽度 |
barWidth() | tabbar的宽度 |
barHeight() | tabbar的高度 |
自定义TabBar示例
/*引入两个页面*/
import {Index} from "./Index"
import {ListPage} from "./listPage"
@Entry
@Component
struct TabsPage {
/*tabbar的选中索引下标*/
@State currentIndex:number = 0;
/*创建 tabbas 控制器*/
private tabsController:TabsController = new TabsController();
/*构建按钮模版组件*/
@Builder MyTabBar(name:string,index:number,selectedImg: string, normalImg: string){
/*上图标下文字布局*/
Column(){
/*判断当前选中下标是否为当前菜单数,三目运算切换按钮颜色*/
Image(this.currentIndex === index?selectedImg:normalImg).width(30)
Text(name)
.fontColor(this.currentIndex === index?'#d81e06' : '#6B6B6B')
}
.width('100%')
.height(50)
.justifyContent(FlexAlign.Center)
.onClick(()=>{
/*将当前点击按钮状态同步到选中索引中,并通知 tabsController 控制器切换页面*/
this.currentIndex = index;
this.tabsController.changeIndex(index)
})
}
build() {
Column(){
/*使用Tabs组件*/
Tabs({barPosition:BarPosition.End,controller:this.tabsController}){
TabContent(){
/*当前tabbar要显示的页签内容*/
Index()
}.tabBar(this.MyTabBar("首页",0,"https://z1.ax1x.com/2023/11/29/piD6doV.png","https://z1.ax1x.com/2023/11/29/piD6tLn.png"))
TabContent(){
ListPage()
}.tabBar(this.MyTabBar("我的",1,"https://z1.ax1x.com/2023/11/29/piD6doV.png","https://z1.ax1x.com/2023/11/29/piD6tLn.png"))
}
.barWidth('100%')
.barHeight(50)
.onChange((index:number)=>{
/*左右滑动可以切换页签*/
this.currentIndex = index
})
}
}
}
弹窗类
警告弹窗
AlertDialog.show({
title: '删除联系人', // 标题
message: '是否需要删除所选联系人?', // 内容
autoCancel: false, // 点击遮障层时,是否关闭弹窗。
alignment: DialogAlignment.Center, // 弹窗在竖直方向的对齐方式
primaryButton: {
value: '取消',
action: () => {
console.info('取消');
}
},
secondaryButton: {
value: '删除',
fontColor: '#D94838',
action: () => {
console.info('删除');
}
},
cancel: () => { // 点击遮障层关闭dialog时的回调
console.info('点击遮罩层');
}
})
日期选择器
DatePickerDialog.show({
start: new Date("1900-1-1"), // 设置选择器的起始日期
end: new Date("2023-12-31"), // 设置选择器的结束日期
selected: this.selectedDate, // 设置当前选中的日期
lunar: false,
onAccept: (value: DatePickerResult) => { // 点击弹窗中的“确定”按钮时触发该回调
// 通过Date的setFullYear方法设置按下确定按钮时的日期,这样当弹窗再次弹出时显示选中的是上一次确定的日期
this.selectedDate.setFullYear(value.year, value.month, value.day)
console.info("DatePickerDialog:onAccept()" + JSON.stringify(value))
},
onCancel: () => { // 点击弹窗中的“取消”按钮时触发该回调
console.info("DatePickerDialog:onCancel()")
},
onChange: (value: DatePickerResult) => { // 滑动弹窗中的滑动选择器使当前选中项改变时触发该回调
console.info("DatePickerDialog:onChange()" + JSON.stringify(value))
}
})
文本选择器
private fruits: string[] = ['苹果', '橘子', '香蕉', '猕猴桃', '西瓜'];
TextPickerDialog.show({
range: this.fruits, // 设置文本选择器的选择范围
selected: this.select, // 设置初始选中项的索引值。
onAccept: (value: TextPickerResult) => { // 点击弹窗中的“确定”按钮时触发该回调。
// 设置select为按下确定按钮时候的选中项index,这样当弹窗再次弹出时显示选中的是上一次确定的选项
this.select = value.index;
console.info("TextPickerDialog:onAccept()" + JSON.stringify(value));
},
onCancel: () => { // 点击弹窗中的“取消”按钮时触发该回调。
console.info("TextPickerDialog:onCancel()");
},
onChange: (value: TextPickerResult) => { // 滑动弹窗中的选择器使当前选中项改变时触发该回调。
console.info("TextPickerDialog:onChange()" + JSON.stringify(value));
}
})
自定义选择器(简易版)
抛开官方花里胡哨的定义,创一个 dialog.ets
文件,用于定义弹出层
@CustomDialog
export default struct CustomDialogWidget {
/*选项数据元状态*/
@State hobbyBeans:Array<{label:string,isChecked:boolean}> = [];
/*自定义的数据*/
@State listData:Array<{value:string}> = [{value:"苹果"},{value:'香蕉'}]
/*自定义弹窗的控制器*/
private controller: CustomDialogController;
/*格式化一下自定义数据,将格式好的对象放到 hobbyBeans 数组中,后面直接循环 hobbyBeans 数组*/
aboutToAppear(){
this.listData.forEach((item)=>{
this.hobbyBeans.push({
label:item.value,
isChecked:false
})
})
}
/*定义页面*/
build(){
Column(){
List(){
/*循环 hobbyBeans 数组得到按钮数据*/
ForEach(this.hobbyBeans,
(item,index)=>{
ListItem(){
Row(){
Text(item.label)
Toggle({ type: ToggleType.Checkbox, isOn: false })
.onChange((isCheck) => {
item.isChecked = isCheck;
})
}
.width("100%")
.justifyContent(FlexAlign.SpaceBetween)
}
},
(item,index)=>`${index}`)
}
/*底部的控制按钮*/
Row() {
Button("取消")
.onClick(() => {
this.controller.close();
})
Button("确定")
.onClick(() => {
this.controller.close();
})
}
}
.justifyContent(FlexAlign.SpaceBetween)
.padding(20)
.backgroundColor("#fff")
.height(180)
}
}
在组件中使用
import CustomDialogWidget from "../component/dialog"
/*创建控制器*/
customDialogController: CustomDialogController = new CustomDialogController({
builder:CustomDialogWidget(), /*在这里构建一下 CustomDialogWidget 组件*/
alignment: DialogAlignment.Center, /*模态框的位置*/
customStyle: true,
})
Button("自定义弹窗"){}.onClick(()=>{this.CustomDialogController.open()})
WebView
Web组件
格式 Web(options)
options {}
属性 | 注释 |
---|---|
src:string | 网址地址 |
Controller:webController | 控制器,前进后退 |
webController 实例
属性 | 注释 |
---|---|
controller.foreard() | 控制 webView 的页面路由前进 |
controller.backward() | 控制 webView 的页面路由后退 |
controller.refresh() | 刷新页面 |
controller.stop() | 停止 |
controller.clearHistory() | 清除历史 |
runJavaScript | 调用页面的 js 方法 |
属性
属性 | 注释 |
---|---|
.fileAccess(true) | 允许访问本地文件 |
.javaScriptAccess(true) | 可以调用 JS 脚本 |
.imageAccess(fasle) | 不允许加载图片 |
.zoomAccess(false) | 禁止页面缩放 |
事件
事件 | 注释 |
---|---|
onConfirm | 网页通信,可接收到页面传递的信息 |
示例
在本地创建 html 文件 entry/src/main/resources/rawfile
index.html
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<body>
<h1 id="h1el"></h1>
<button onclick="htmlTest()">调用Web组件里面的方法</button>
</body>
<script type="text/javascript">
function htmlTest() {
h1el.innerHTML = "测试内容";
}
</script>
</html>
webView 使用
controller: WebController = new WebController();
build(){
Column(){
Web({src:$fawfile("index.html"),controller:this.controller})
}
}
接受web端的信息 onConfirm
示例
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<body>
<button onclick="confirmFun()">confirm事件</button>
</body>
<script type="text/javascript">
function confirmFun(){
confirm("来自web端的信息")
}
</script>
</html>
Web({ src: $rawfile('index.html'), controller: this.controller })
.javaScriptAccess(true)
.zoomAccess(false)
.textZoomAtio(150)
.onConfirm((event)=>{
console.log(event.message) /*打印出web端的消息*/
return true
})
web组件调用网页js方法
Web({ src: $rawfile('index.html'), controller: this.controller })
.onPageEnd((event)=>{
this.controller.runJavaScript({
script:"test()",
callback:(result:string)=>{
console.log(result) /*打印js方法返回的内容*/
}
})
})
<script type="text/javascript">
function test(){
return "鸿蒙web组件调用js方法"
}
</script>
JS 调用 Web 组件方法
鸿蒙
@Entry
@Component
export struct WebPage {
@State dataFromHtml: string = ''
controller: WebController = new WebController()
/*定义供页面调用的方法逻辑*/
testObj = {
test: (data) => {
this.dataFromHtml = data;
return '方法被js端调用了';
},
toString: () => {
console.log('Web Component toString');
}
}
build(){
Column(){
Button('将脚本注入到js页面中').onClick(() => {
this.controller.registerJavaScriptProxy({
object: this.testObj,
name: 'objName',
methodList: ['test', 'toString'],
});
this.controller.refresh();
})
Web({ src: $rawfile('index.html'), controller: this.controller })
}
}
}
页面中调用
<button @click="btnFun()">按钮</button>
function btnFun(){
let result = objName.test("调用鸿蒙的方法,传递一个信息")
console.log(result)
}