ArkTS基础快速入门学习

鸿蒙开发ArkTS基础快速学习

说明

阅读本篇文章,适合一些了解过前端开发以及掌握前端框架的读者。

ArkTS介绍

ArkTS语言在TS语言基础上扩展了声明式UI,组件化,状态管理等功能。

官方推荐两种开发模式开发鸿蒙,一种是基于js扩展的类web范式,另一种是基于TS扩展的声明式UI范式(也就是使用ArkTS开发)。

ArkTs的代码基本结构

Index.ets示例文件

js 复制代码
@Entry
@Component
  struct Index {
    @State message: string = 'Hello World'
 
    build() {
      Row() {
        Column() {
          Text(this.message)
            .fontSize(50)					
            .fontWeight(FontWeight.Bold)
        }
        .width('100%')
      }
      .height('100%')
    }
  }
js 复制代码
@Entry // 装饰器
@Component // 装饰器
struct Hello{ // Hello属于自定义组件
    //  @State表示 装饰器
    @State myText:string = "World";
    build(){ // UI描述
        Column(){ //系统组件
            Text('Hello ${this.myText}')
            	.fontSize(50)
            Divder()
            Button('Click me')
            	.onClick(()=>{  // 事件方法
                this.myText = 'ArkUI' 
            })
            .height(50) // 属性方法
            .width(100)
            .margin({ top : 20 })
        }
    }
}

ArkTS自定义组件

首先,创建一个Stage的项目,也就是ArkTS项目。

在devcoe studio 编辑中依次点击:File》Create Project 》next 》Project name:ArkTSApplication(项目名字自己去);Model:Stage》Finish

创建完成项目之后,编辑器会自动打开index.ets文件

js 复制代码
// index.ets 初始文件

import MyComponent1 from './MyComponent1'


@Entry // 入口组件装饰器,一个ets文件中,只能使用依次@Entry
@Component // 组件装饰器 ,表示以下内容是一个组件 它只能装饰用struct关键字声明的结构;注意:一个组件也只能有一个@Component装饰器
struct Index {
    // @State 是一个状态装饰器,只要这里的状态发生变化,整个ui就会进行刷新
  @State message: string = 'Hello World'

  build() { // build方法必须被实现,是用来描述这个组件的UI
    Row() { // 表示一行 此时占满了整个页面
      Column() { // 表示一列
        Text(this.message) //Text组件 表示文本组件 其中this.message来自于上面的 @State message
          .fontSize(50) // 设置Text组件的字体大小
          .fontWeight(FontWeight.Bold) // 设置Text组件的字体粗心
        // 使用内部自定义组件
        MyComponent()
        // 使用外部自定义组件
        MyComponent1()
        MyComponent1({title:"首页"}) // 会将title传递到自定义组件中的@State title
        MyComponent1({title:'发现'})
      }
      .width('100%') // 设置Column的高度100%,也就是设置列的高度为100%
    }
    .height('100%') // 设置Row的高度100%,也就是设置行的高度为100%
  }
}
// 自定义组件部分,内容往下看

再上面的基础上,自己自定义一个组件,还是在index.ets文件中

js 复制代码
// 省略上面部分代码
// 自定义组件
@Component
struct MyComponent{
    build(){
        Row(){
            Button("按钮")
        }
    }
}

单独定义组件

当组件比较复杂的时候,就定义在单独的一个文件中。

在src/main/pages中新建一个 MyComponent1.ets

创建文件:在pages目录上右键》new》page,输入文件名即可。

js 复制代码
@Component
// 使用 export default 将当前组件导出去,让其他文件能够导入该组件
export default struct MyComponent1{
    @State title:string = "按钮1"
    build(){
        Row(){
            Button(this.title)
        }
    }
}

build函数注意事项

1.一个build函数中,入口组件只能有一个Row()组件[根组件],而且还得是个容器组件,比如说Image()就是非容器组件。

2.在非入口组件中,build方法的根组件可以是非容器组件,案例如下

js 复制代码
@Component
export default struct MyComponent1{
  @State title:string = "按钮1"
  build(){
      // 非入口组件中,根组件可以是非容器组件
    Image($r('app.media.icon'))
  }
}

3.无论是入口组件还是非入口组件 都不能使用ForEach作为根组件。

4.bulid函数中不能定义本地变量,或者打印输出;变量和打印输出要在bulid函数的上方定义。

5.bulid函数中,不允许调用普通方法:this.方法();

6.使用@Builder修饰的函数,就成了组件,也就可以在build函数中使用,案例见如下:

js 复制代码
@Builder doRender(){
    // 被@Builder修饰过的函数,返回值是个组件
    Text("使用Builder修饰的函数")
}
build(){
    Row(){
       this.deRender() 
    } 
}

7.builder函数中的组件中内部变量可以使用 this.函数名

js 复制代码
@State title:string = "按钮"
doName(){
    return this.title;
}
build(){
    Row(){
       Button(this.doName()) 
    } 
}

8.build函数中可以使用判断语句if;但是不能用switch语句

java 复制代码
private score:number = 78;
build(){
    Row(){
       if(this.score>=90){
           Text("优秀")
       }else if(this.score >= 80){
           Text("良好")
       }else if(this.score >= 70){
           Text("及格")
       }else{
           Text("不及格")
       }
    } 
}

9.build函数中不能使用表达式

js 复制代码
// 这种方式不可以,只能用if判断语句
this.score >= 60 > Text("及格") : Text("不及格")

组件样式设置

js 复制代码
组件(){}.样式 = 样式值
js 复制代码
build(){
    row(){
        
    }
    // 下面这些都是给row组件设置的样式
    .width('80%')
    .height('500px')
    .backgroundColor('#ff0')
    .margin({top:"50px",left:'50px'})
}

生命周期

页面生命周期

onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。
onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
onBackPress:当用户点击返回按钮时触发。

组件生命周期

aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build(0函数之前执行。
aboutToDisappear:在自定义组件析构销毁之前执行

生命周期函数

首先在pages文件夹下新建一个页面,就叫LifePages.ets

js 复制代码
import router from '@ohos.router'

@Entry
@Component
struct LifePage {

  @State childShow : boolean = true

  aboutToAppear(){
    console.info("组件将要显示")
  }

  onPageShow(){
    console.info("组件显示")
  }
  onBackPress(){
    console.log("按返回键")
  }

  onPageHide(){
    console.info("页面隐藏")
  }
  aboutToDisappear(){
    console.info("页面将要被销毁")
  }


  build() {
    Row(){
      Column() {
        // this.showChild为true,创建Child子组件,执行Child aboutToAppear
        if (this.childShow) {
          Child()
        }
        // this.showChild为false,删除Child子组件,执行Child aboutToDisappear
        Button('delete Child').onClick(() => {
          this.childShow = false;
        })

        Button("跳转到其他页面").onClick(() =>{
          router.replaceUrl({
            url:"pages/Index"
          })
        })

      }
      .width("100%")
    }
    .height("100%")


  }
}

@Component
struct Child{
  aboutToAppear(){
    console.info("child about appear")
  }
  aboutToDisappear(){
    console.info(("child about to disappear"))
  }

  build(){
    Text("child component")
      .fontSize(100)
      .backgroundColor('#f00')
  }
}

函数式组件@Bulider装饰器

使用 @Bulider修饰的函数,被称为函数式组件,这种方式修饰的组件增加了组件的复用性。

局部函数式组件

js 复制代码
// 这种函数式组件,只能在当前页面使用,不能外部使用
@Bulider 函数式组件名(){
    Text("函数式组件")
}

首先,在pages/下创建一个新页面,就叫BuliderPage.ets

js 复制代码
@Entry
@Component
  struct BuliderPage {
    @State message: string = 'Hello World'
      
      // 使用 @Bulider修饰的函数,被称为函数式组件
      @Bulider MyFunctionComponent(){
          Text("函数式组件")
      }
      
      // 函数式组件可以有多个
      @Bulider MyFunctionComponent2(text){
          // text就是值传递,当text被赋值后,利用事件来触发修改值,组件的UI时不会刷新的
          // 如果想要达到刷新,就要用到@State修饰的变量
          Column(){
              // Text("函数式组件2")
              // 组件也可以传参
              Text(text)
          }.width("400px")
          .backgroundColor("#f00")
      }
      
 
    build() {
      Row() {
        Column() {
            // 使用自定义的函数式组件
            this.MyFunctionComponent()
            this.MyFunctionComponent2(this.message)
        }
        .width('100%')
      }
      .height('100%')
    }
  }

全局函数式组件

js 复制代码
// 定义全局函数式组件
// 参数:值传递;引用传递

@Bulider function GlobalFunctionComponent($$:{param1:string}){
    // GlobalFunctionComponent(param1) 当param1被赋值后,利用事件来触发修改值,组件UI不会刷新
    // 当使用 $$:{param1:string} 这种方式就可以刷新UI组件
    Column(){
        // Text("全局函数式组件")
        Text($$.param1)
    }
    .width("400px")
    .height("200px")
    .backgroundColor("@00f")
}


@Entry
// .........省略下面部分代码.........
Bulid(){
    Row(){
        Column(){
            // 直接调用全局组件,不需要用this
            // this.message来自于省略部分的代码
            // 传参方式: {参数名称:"值"}
            GlobalFunctionComponent({parm1:this.message})
            
            Button("更新message").onClick(()=>{
                this.message = "你好,世界";
            })
        }
    }
}

完整代码

js 复制代码
// 全局函数式组件
@Builder function GlobalFunctionComponent(param1){
  // 这种传递参数的方式,属于值传递,也就是说
  // 当param1被赋值过,利用事件来触发修改值,是不会自动刷新组件的UI的,触发被赋值的参数被@State修饰
  Column(){
    Text(param1)
  }
  .width("400px")
  .height("200px")
  .backgroundColor("#00f")
}


@Builder function GlobalFunctionComponent2($$:{param1:string}){
  //  $$:{参数名:string}  这种传递参数的方式,属于引用传递,也就是说
  // 当param1被赋值过,利用事件来触发修改值,是会自动刷新组件的UI的
  Column(){
    Text($$.param1)
  }
  .width("400px")
  .height("200px")
  .backgroundColor("#00f")
}






@Entry
@Component
struct BuliderPage{
  @State message: string = 'Hello World'
  public text:string =  "组件的传值"

  // 定义函数式组件
  @Builder MyFunctionComponent(){
    Text("局部函数式组件")
  }

  // 定义多个函数式组件,并且能够传参
  @Builder MyFunctionComponent2(text){
    // 这种传参的方式叫做值传递,也就是说,当text被赋值过后,利用事件来触发修改值,不会刷新组件UI
    // 想要刷新组件的UI,那么在调用该组件,传递参数的时候,传的参数得是被@State修饰过的变量
    Text(text)
  }

  build() {
    Row() {
      Column() {
        // 使用局部函数式组件
        this.MyFunctionComponent()

        // 使用可以传参的函数式组件
        this.MyFunctionComponent2(this.message)

        // 使用全局函数式组件(不需要用this),带参数,会不刷新ui
        GlobalFunctionComponent(this.message)

        // 使用全局函数式组件(不需要用this),带参数刷新的
        // 传参方式: {参数名称:"值"}
        GlobalFunctionComponent2({param1:this.message})


        // 尝试使用事件来修改组件中的值
        Button("修改值").onClick(()=>{
          this.message = "修改组件传递的值"
        })

        // 此时,就可以发现,当点击Button的时候
        // GlobalFunctionComponent中的参数值不会变化
        // GlobalFunctionComponent2中的参数值会发生变化


      }
      .width('100%')
    }
    .height('100%')
  }
}

插槽

@BuilderParans装饰器定义插槽

插槽本身无意义,只是在页面布局中占个空位置而已,后期再往整个空位置里插入组件。

通俗的来讲,就是,在页面布局中,提前给组件留个空位,后期再补上

首先创建一个新页面,BuilderParamPage

BuliderParamPage.ets

js 复制代码
// 自定义组件
@Component
struct MyHeader{
  // 1. 定义插槽
  @BuilderParam leftSlot:() => void
  @BuilderParam rightSlot:() => void

  //  2.将插槽放入容器中
  build(){
    Row(){
      Column(){
        // 放入插槽
        this.leftSlot()
      }
      .width("33.33%")
      Column(){
        Text("标题")
      }
      .width("33.33%")
      Column(){
        // 放入插槽
        this.rightSlot()
      }
      .width("33.33%")
    }
  }
}



// 主入口函数
@Entry
@Component
struct BuilderParamPage{
  @State message:string = "hello world"


  // 4.定义函数式组件
  @Builder leftContent(){
    Text("返回")
  }

  @Builder rightContent(){
    Text("更多")
  }

  @Builder leftContent2(){
    Text("back")
  }

  @Builder rightContent2(){
    Text("more")
  }

  build(){
    Column(){
      // 5. 使用带插槽的自定义组件
      MyHeader({leftSlot:this.leftContent,rightSlot:this.rightContent})
      MyHeader({leftSlot:this.leftContent2,rightSlot:this.rightContent2})

      // 上述两行代码,通俗的来讲:
      // 将第39行的组件插入到13行的插槽,将第43行组件插入到22行的插槽中
      // 将第47行的组件插入到13行的插槽,将第51行组件插入到22行的插槽中

    }
    .height("100%")
  }
}

尾随闭包的方式使用插槽

js 复制代码
// 1. 自定义组件
@Component
struct Book{
    // 2. 定义了插槽
    @BuilderParam title:() => void;
    build(){
        Column(){
            // 3.将插槽放入容器中
            this.title()
        }
        .width("400px")
        .height("600px")
        .backgroundColor("#ccf")
        .margin({top:"20px"})
    }
}


// 主入口函数
@Entry
@Component
struct BuilderParamPage{
  @State message:string = "hello world"
  build(){
    Column(){
        // 使用插槽
        Book(){
            // 相当于 把这个组件插入到第8行的插槽中
            Text("三国演义")
        }
        Book(){
            // 相当于 把这个组件插入到第8行的插槽中
            Text("西游记")
            Column(){
                Text("作者:吴承恩")
            }
        }
    }
    .height("100%")
  }
}

@Style装饰器定义样式

@Styles可以将多个重复样式提炼出来,封装成一个方法,然后组件调用这个方法,就能获得该样式。

@Styles支持 局部样式 和 全局样式

局部组件样式

具体案例

首先,新建一个页面,就叫StylePage.ets

js 复制代码
@Entry
@Component
  struct StylePage {
    @State message: string = 'Hello World'
 	@State color:Color = Color.Black;
      // 定义局部组件样式
      @Styles block(){
          // @Styles block(){} 括号中不支持传参
          .width("400px")
          .height("400px")
          // 此时this.color 就是黑色
          .backgroundColor(this.color)
      }
      
    build() {
      Row() {
        Column() {
          Text(this.message)
            .fontSize(50)					
            .fontWeight(FontWeight.Bold)
        }
        .block() // 使用局部组件样式
          
         Column() {
          Text(this.message)
            .fontSize(50)					
            .fontWeight(FontWeight.Bold)
        }
        .block() // 使用局部组件样式
          
          Button("改变颜色").onClick(()=>{
              this.color = Color.blue;
          })
      }
      .height('100%')
    }
  }

全局组件样式

js 复制代码
// 定义全局组件样式
@Styles function commomStyles(){
    .height("200px")
    .width("400px")
    .backgroundColor(Color.Green)
}


@Entry
@Component
  struct StylePage {
    @State message: string = 'Hello World'
      
    build() {
      Row() {
         Column() {
          Text(this.message)
            .fontSize(50)					
            .fontWeight(FontWeight.Bold)
        }
        .commomStyles() // 使用全局组件样式
      }
      .height('100%')
    }
  }

@State装饰器定义响应式数据

组件的状态,当被@State修饰后的变量数据更新了,整个UI组件就会跟着更新。

代码案例

首先新建一个页面,就叫StatePage.ets

js 复制代码
@Entry
@Component
  struct StatePage {
    @State message: string = 'Hello World'
      count:number = 1;
      
 
    build() {
      Column() {          
          Text(this.count.toString())
          
          Button("increase").onClick(()=>{
              this.count++
          })
          // 可以发现,当点击按钮的时候,没有被@State修饰的count变量,页面上的数字永远是1,不会更新
          // 当给count添加@State(@State count:Number =1) ,页面上的数字就会更新了
      }
      .height('100%')
    }
  }

@Prop装饰器

使用@Prop装饰器可以实现组件外部向内部传输数据

@Prop装饰器只能实现组件外部向内部传递数据,是单向的。

而且,组件内部对数据的修改,不会影响到组件外部

代码案例

首先新建一个页面,就叫PropPage.ets

js 复制代码
// 定义一个子组件
@Component
struct GoodsItem{
  // 接受外部组件传进来的title和price参数
  @Prop title:string;
  @Prop price:number;

  build(){
    Column(){
      Text(`商品名称:${this.title}`)
      Text(`商品价格:${this.price}`)
        // 这里的this.price--;只会影响该组件内部的price,不会影响到父组件
        .onClick(()=>{
          this.price--;
        })
        .fontSize(24)
    }
  }
}

// 定义接口
interface Good{
  title:string; // 接口属性
  price:number;
}


@Entry
@Component
struct PropPage {
  @State message: string = 'Hello World'
  @State price :number = 0
  @State goodList:Good[] = [
    {
      title:'手机',price:666
    },
    {
      title:'平板',price:777
    },
    {
      title:'电脑',price:888
    },
  ]

  build() {
    Column() {
      Text(`父组件的price:${this.price}`).fontSize(50)

      // 将字符串手机 和 price变量 传递给GoodsItem组件
      GoodsItem({title:"手机",price:this.price})
      
      // 使用ForEach循环遍历数组
      // ForEach(数组,变量=>{回调函数})
      ForEach(this.goodList,item =>{
        GoodsItem({title:item.title,price:item.price})
      })
      
      Button("父组件修改price").onClick(()=>{
        this.price++;
      })

    }
  }
}

@Link装饰器

@Link装饰器可以实现父子双向同步,也就是说可以实现父组件和子组件的数据同步

代码案例

tsx 复制代码
// 定义子组件
@Component
struct Child{
  // 1. 使用@link修饰变量
  @Link author:string;

  build(){
    Text(`作者:${this.author}`)
      .onClick(()=>{
        // 当点击作者文本的时候,作者发生改变
        this.author = "李四";
      })
  }
}



@Entry
@Component
struct Index {
  @State message: string = 'Hello World'

  // 2.定义与@Link修饰的相同变量名
  @State author:string = "张三"

  build() {
    Column() {
      Text(`父组件中的author:${this.author}`)

      // 3.往子组件里传值 要使用 $变量
      Child({author:$author})
      Button("父组件")
        .onClick(()=>{
          this.author = "王五"
        })
    }
    .height('100%')
  }
}

// 总结:
// 此时,父子组件就实现了双向数据绑定,当子组件中的author发生变化的时候,父组件的author也跟着发生变化
// 当父组件中的author发生变化的时候,子组件的author也跟着发生变化

LocalStorage本地存储

StoragePage可以提供少量数据的本地存储。

@LocalStorageProp:UI中变量和LocalStorage单向同步。被@LocalStorageProp装饰的变量发生改变,不会影响LocalStorage中变量的值。但是LocalStorage中变量的值发生变化会同步被@LocalStorageProp装饰的变量。

@LocalStorageLink:UI中变量和LocalStorage双向同步。

代码案例

首先,新建一个页面,就叫StoragePage.ets

tsx 复制代码
// 1. 创建LocalStorage实例
let storage = new LocalStorage({"count":100})

// 创建子组件
@Component
struct Child{
  // 这里的count与new LocalStorage中的count保持一致
  @LocalStorageLink("count") childCount : number =1;
  @LocalStorageProp("count") childProp : number =1;
  build(){
    Column(){
      Button(`子组件来自localStorage的数据+1:${this.childCount}`)
        .onClick(()=>{
          this.childCount++;
        })
      Button(`子组件更新LocalStorageProp的数据+2:${this.childProp}`)
        .onClick(()=>{
          this.childProp+=2;
        })
    }
  }
}

@Entry
@Component
struct StoragePage{
  @State message:string = "Hello World"
  @LocalStorageLink("count") parentCount : number =1;
  @LocalStorageProp("count") parentProp : number =1;

  build(){
    Row(){
      Column(){
        // 子组件
        Child()

        Text(`父组件来自localSotrage的数据${this.parentCount}`)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
        Button("父组件更新storage-1").onClick(()=>{
          this.parentCount--
        })
        Button("父组件更新storage prop-2").onClick(()=>{
          this.parentProp-=2;
        })
      }
      .width("100%")
    }
    .height("100%")
  }
}

条件渲染

新建一个页面,就叫ifPage

tsx 复制代码
@Entry
@Component
struct IfPage{
    @State score:number = 10;
    build(){
        Column(){
            Text(`分数:${this.score}`).fontSize(24)
            Button("更新分数").onClick(()=>{
                this.score += 10;
            })
            
            if(this.score < 60){
                Text("不及格").fontSize(24)
            }else if(this.score >=60 && this.score < 70){
                Text("及格").fontSize(24)
            }else if(this.score >=70 && this.score <90){
                Text("良好").fontSize(24)
            }else if(this.score>=90){
                Text("优秀").fontSize(24)
            }
        }
    }
}

列表渲染

新建一个页面,就叫ListPage

tsx 复制代码
@Entry
@Component
struct ListPage{
  @State list:number[] = [11,22,33,66,88]
  build(){
    Column({space:10}){
      // item表示元素本身
      ForEach(this.list,(item:string)=>{
        Row(){
          Text(`内容:${item}`).fontSize(24).fontColor(Color.White)
        }
        .height(100)
        .backgroundColor(Color.Blue)
        .padding(20)
      },(item:string)=>item)
    }.width('100%').height('100%')
  }
}

列表应用案例

新建一个页面,就叫BookListPage

tsx 复制代码
interface Book{
  id:number,
  title:string,
  price:number,
  num:number;
}

// 定义图书组件
@Component
struct BookItem{
  @Prop title:string;
  @Prop price:number;
  @Prop num:number;
  build(){
    Row(){
      Column(){
        Image($r('app.media.icon')).width(100).height(100)
      }.width('40%')
      Column(){
        Row(){
          Text(`图书名称:${this.title}`).fontSize(18)
        }
        Row(){
          Text(`图书价格:${this.price}`).fontSize(14).fontColor(Color.Red)
        }
        Row(){
            Button("-").onClick(()=>{
                this.num--;
            })
            Text(`${this.num}`).fontSize(18).margin(10)
            Button("+").onClick(()=>{
                this.num++;
            })
        }
      }.width("60%")
    }
    .backgroundColor("#eee")
    .width("90%")
    .margin({left:'5%'})
  }
}



@Entry
@Component
struct BookListPage {
  @State bookList: Book[] = [
    {
      id: 0,
      title: "三国演义",
      price: 89.9,
      num:1
    },
    {
      id: 1,
      title: "水浒传",
      price: 79.9,
      num:2
    },
    {
      id: 2,
      title: "西游记",
      price: 99.9,
      num:5
    },
    {
      id: 3,
      title: "红楼梦",
      price: 109.9,
      num:7
    }
  ]

  build() {
    Column({ space: 10 }) {
      ForEach(this.bookList, (item: Book) => {
        BookItem({title:item.title,price:item.price,num:item.num})
      }, (item: Book) => item.id + '')
    }
  }
}

线性布局

行布局

新建一个文件,文件名就叫RowPage.ets

行布局也称水平布局

typescript 复制代码
@Entry
@Component
struct RowPage{
  build(){
    // 行容器元素,子元素水平排列
    Row(){
            Text("水平排列").width("20%").backgroundColor(Color.Yellow)
            Row(){

            }.width("20%").height(100).backgroundColor(Color.	Red)
    Row(){

    }.width("20%").height(100).backgroundColor(Color.	Green)
    Row(){

    }.width("20%").height(100).backgroundColor(Color.	Blue)
  }.height(200)
  .width("100%")
  .backgroundColor("#ccc")
  .justifyContent(FlexAlign.Center)
  .alignItems(VerticalAlign.Center)
}
}

列布局

列布局也称垂直布局

新建一个文件,文件名就叫ColumnPage.ets

typescript 复制代码
@Entry
@Component
struct ColumnPage {

  build() {
    // 列容器元素,子元素在垂直方向
    Column({space:50}){
      Text("垂直排列")
      Row(){
        Text("111")
        Text("222")
        Text("333")
      }.width(200).height(200).backgroundColor(Color.Red)
      Column(){
        Text("111")
        Text("222")
        Text("333")
      }.width(200).height(200).backgroundColor(Color.Green)
    }.width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Start)
  }
}

StackLayout层叠布局

StackLayout层叠布局可以实现元素重叠,类似于css中的绝对定位

新建一个文件,文件名就叫StackLayoutPage.ets

typescript 复制代码
@Entry
@Component
struct StackLayoutPage {

  build() {
    Stack({alignContent:Alignment.Start}){
      Row(){

      }.width(300).height(300).backgroundColor(Color.Pink)
      Text("stack layout").width(200).height(200).backgroundColor(Color.Yellow).zIndex(2)
      Button("按钮").zIndex(3)
    }.width("100%").height(500).backgroundColor(Color.Gray)
  }
}

Flex弹性布局

新建一个文件,文件名就叫FlexPage.ets

typescript 复制代码
@Entry
@Component
struct FlexPage {

  build() {
      // direction:FlexDirection.Row 横向排列
      // wrap:FlexWrap.Wrap 换行
    Flex({direction:FlexDirection.Row,wrap:FlexWrap.Wrap}){
      Text("box1").width(100).height(100).backgroundColor(Color.Red)
      Text("box2").width(100).height(100).backgroundColor(Color.Red)
      Text("box3").width(100).height(100).backgroundColor(Color.Red)
      Text("box4").width(100).height(100).backgroundColor(Color.Red)
      Text("box5").width(100).height(100).backgroundColor(Color.Red)
    }
  }
}

Flex的主轴和交叉轴的排列

typescript 复制代码
@Entry
@Component
struct FlexPage {

  build() {
      // justifyContent:FlexAlign.Center左右居中
      // alignItems:ItemAlign.End 靠下排列
    Flex({justifyContent:FlexAlign.Center,alignItems:ItemAlign.End}){
      Text("box1").width(100).height(100).backgroundColor(Color.Red)
      Text("box2").width(100).height(100).backgroundColor(Color.Red)
      Text("box3").width(100).height(100).backgroundColor(Color.Red)
    }.height(500).backgroundColor(Color.Gray)
  }
}

Flex布局综合练习

首先,新建一个LayoutPage.ets文件

typescript 复制代码
@Entry
@Component
struct LayoutPage {
  @State apps: string[] = ['app1','app2','app3','app4','app5','app6','app7','app8']
  @State app2:string[] = ["微信","电话","短信"]

  build() {
    // 使用Stack层叠布局
    Stack({ alignContent: Alignment.Bottom }) {
      Flex({ wrap: FlexWrap.Wrap }) {
        ForEach(this.apps, (item: string) => {
          Text(item)
            .width(100)
            .height(100)
            .backgroundColor("#ccc")
            .borderRadius(10)
            .margin(10)
            .textAlign(TextAlign.Center)
        }, item => item)
      }.width("100%").height("100%")

      Flex({ justifyContent: FlexAlign.SpaceBetween }) {
        ForEach(this.app2, (item: string) => {
          Text(item)
            .width(100)
            .height(100)
            .backgroundColor(Color.Green)
            .borderRadius(10)
            .textAlign(TextAlign.Center)
        }, item => item)
      }
      .width("90%")
      .height(100)
      .backgroundColor("#ccc")
      .borderRadius(10)
      .margin({ bottom: 10 })
    }
  }
}

综合项目练习

项目准备

新建一个项目,项目名字就叫jaofei

在pages目录下创建一个文件夹,名叫components

将项目所需要的图片复制到src/main/resources/rawfile目录下

具体代码

1.在components目录下创建一个文件,就叫MyHeader.ets

typescript 复制代码
// MyHeader.ets 文件
@Component
export default struct MyHeader{
    build(){
        Row(){
            Column(){
                // Image组件读取src/main/resources/base/media下的文件:Image($r('app.media.图片名称(不需要带后缀)'))
                Image($r('app.media.shuifei')).width(16).height(16)
            }.width(57)
            // 第二列
            Column(){
                Text("缴费记录")
            }
        }.height(44)
        .width("100%")
        .backgroundColor("#F7F8FA")
    }
}

Index.ets文件

typescript 复制代码
import  MyHeader from './components/MyHeader'
import  JFItem from './components/JFItem'

@Entry
@Component
struct Index {
  @State message: string = 'Hello World'

  @State list: Object[] = [
    {
      icon:'shuifei.png',
      title:"水费",
      date:"2022-01-09 14:30:20",
      price:"100.00"

    },
    {
      icon:'dianfei.png',
      title:"电费",
      date:"2022-01-09 14:30:20",
      price:"100.00"

    },
    {
      icon:'fangzu.png',
      title:"房租",
      date:"2022-01-09 14:30:20",
      price:"100.00"

    },
    {
      icon:'wuye.png',
      title:"物业费",
      date:"2022-01-09 14:30:20",
      price:"100.00"

    }
  ]

  build() {
    Column(){
      MyHeader()

      Flex(){
        Column(){
          Text("缴费类型").height(56)
        }.width('50%')

        Column(){
          Text("全部时间").height(56)
        }.width('50%')
      }
      .height(56)
      .width('100%')
      .linearGradient({
        direction:GradientDirection.Top,
        colors:[['#F7F8FA',0],['#fff',1]]
      })


      ForEach(this.list,(item) =>{
        JFItem({
          icon:item.icon,
          title:item.title,
          date:item.date,
          price:item.price
        })
      }, item=> item.icon)


    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

在component文件夹下创建一个新组件,就叫JFItem.ets

typescript 复制代码
// JFItem.ets
@Component
export default struct JFItem{
    @Prop icon:string = $r('app.media.dianfei');
    @Prop title:string;
    @Prop date:string;
    @Prop price:string;
    build(){
        Row(){
            // dianfei是media文件夹下的dainfei.png图片 访问src/main/resources/rawfile下的资源,就用$rawfile('图片路径')
            Image($rawfile(this.icon)).width(56).height(56).margin({left:16,right:10})

            Column(){
                Text(this.title).textAlign(TextAlign.Start)
                Text(this.date)
            }
            .alignItems(HorizontalAlign.Start)
            Text(`${this.price}元`)
            .width(60)
            .fontSize(14)
            .margin({left:10})
        }
        .width(345)
        .height(90)
        .borderRadius(5)
        .shadow({offsetX:0,offsetY:4,radius:20,color:"#ccc"})
        .margin({top:10})
    }
}

ArkUI内置组件

按钮组件

Button组件

typescript 复制代码
@Entry
@Component
  struct ButtonPage {
    @State message: string = 'Hello World'
 
    build() {
      Row() {
        Column() {
            // 按钮组件
            // Normal方形按钮
            // Capsule 圆角按钮
            // Circle 圆形按钮
            // stateEffect:false 取消按钮点击效果
            Button("按钮",{type:ButtonType.Normal,stateEffect:true})
            // 给方形按钮添加样式
            .borderRadius(10)
            .backgroundColor(0xfff000)
            .fontColor(0x0000ff)
            .fontSize(100)
            Button("按钮",{type:ButtonType.Capsule})
            Button("按钮",{type:ButtonType.Circle})
            
            Button({type:ButtonType.Normal,stateEffect:true}){
               Row(){
                    Image($r('app.media.loading')).width(30).height(30).margin({right:10})
                	Text("正在加载").fontSize(24).fontColor(0xffffff)
               }.padding(10)
            }
            .width(200)
            .borderRadius(5)
            
            Button(){
                Image($r('app.media.delete')).width(50).height(50)
            }.padding(20)
            .onClick(()=>{
                console.info("按钮被点击")
            })
        }
      }
    }
  }

单选按钮

Radio组件

typescript 复制代码
@Entry
@Component
struct RadioPage {
  @State message: string = 'Hello World'
  @State gender:string = "男"

  build() {
    Column(){
        Row(){
            Text(`性别:${this.gender}`).fontSize(24)
        }
      Row() {
        // 单选按钮
        Text("男: ")
        // checked(true) 默认选中
        // .onChange() 给单选按钮绑定事件
        Radio({value:'男',group:'gender'}).checked(true)
          .width(50).height(50)
          .onChange((isCheck:Boolean)=>{
            if(isCheck){
              // 按钮被选中
              this.gender = "男"
            }
          })
        Text("女: ")
        Radio({value:'女',group:'gender'})
          .onChange((isCheck:Boolean)=>{
            if(isCheck){
              // 按钮被选中
              this.gender = "女"
            }
          })
      }
    }
  }
}

多选按钮

Toggle组件

typescript 复制代码
@Entry
@Component
struct TogglePage{
  @State message: string = 'Hello World'
  @State hobbies: string[] = ["唱歌"]
  @State isOpen:boolean = true;

  build() {
    Column() {
      Row() {
        Text(`爱好:${this.hobbies.toString()}`)
      }

      Row() {
        // 多选按钮
        // isOn:true 默认选中
        Text("爱好").fontSize(24)
        Text("唱歌:")
        Toggle({ type: ToggleType.Checkbox, isOn: true })
          .onChange((isOn: boolean) => {
            if (isOn) {
              this.hobbies.push('唱歌')
            } else {
              this.hobbies = this.hobbies.filter(item => item != "唱歌")
            }
          })
        Text("跳舞:")
        Toggle({ type: ToggleType.Checkbox, isOn: false })
          .onChange((isOn: boolean) => {
            if (isOn) {
              this.hobbies.push('跳舞')
            } else {
              this.hobbies = this.hobbies.filter(item => item != "跳舞")
            }
          })
        Text("打篮球:")
        Toggle({ type: ToggleType.Checkbox, isOn: false })
          .onChange((isOn: boolean) => {
            if (isOn) {
              this.hobbies.push('打篮球')
            } else {
              this.hobbies = this.hobbies.filter(item => item != "打篮球")
            }
          })
      }

      Row(){
        Text(`是否打开:${this.isOpen}`)
        // 开关按钮
        Toggle({type:ToggleType.Switch,isOn:true})
          .switchPointColor(Color.Red)//未选中
          .selectedColor(Color.Pink) // 选中
          .onChange((isOn:boolean)=>{
            this.isOpen = isOn
          })
      }

      Row(){
        Toggle({type:ToggleType.Button}){
          Text("是否打开")
        }.selectedColor(Color.Pink)
      }


    }
  }
}

输入框组件

typescript 复制代码
@Entry
@Component
struct InputPage {
  @State message: string = 'Hello World'

  build() {
    Column() {

      // 输入框组件
      TextInput({placeholder:"请输入"})
        .width(300)
        .height(50)
        .backgroundColor(Color.Green)
        .fontSize(24)
        .fontColor(Color.White)


      // 密码输入框
      TextInput().type(InputType.Password)
      // 数字输入框
      TextInput().type(InputType.Number)

      // 获取输入框的内容
      TextInput().onChange((value:string)=>{
        console.info(value)
      })

      // 多行输入框
      TextArea().width(200).height(100)
        .backgroundColor(0xcccccc)
          // 获取多行输入框的值
        .onChange((value:string)=>{
          console.info(value)
        })

    }
  }
}

注册表单案例

typescript 复制代码
@Entry
@Component
struct RegistPage {
  @State message: string = 'Hello World'

  build() {
    Column() {
      Row(){
        Text("姓名:")
        TextInput().width(300)
          .height(50)
      }
      Row(){
        Text("密码:")
        TextInput()
          .width(300)
          .type(InputType.Password)
          .height(50)
      }.margin({top:10})
      Row(){
        Button("提交")
          .width(300)
          .onClick(()=>{

          })
      }
    }
  }
}

页面跳转

页面跳转有两种方式,一种是用pushUrl跳转,另一种是用replaceUrl进行跳转。

使用Index.ets页面,新建一个ListPage.ets页面

Index.ets

typescript 复制代码
// 导入路由对象
import router from '@ohos.router'
@Entry
@Component
  struct Index {
    @State message: string = 'Hello World'
 
    build() {
      Column() {
          Button("跳转到list页面").onClick(()=>{
              // pushUrl会在当前页面添加一个页面
              // 下一页可以实现上一页返回操作
              router.pushUrl({
                  url:"pages/ListPage"
              })
          })
          
          
          Button().onClick(()=>{
              // replaceUrl直接替换当前页面
            // 下一页无法实现返回操作
              router.replaceUrl({
                  url:"Pages/ListPage"
              })
          })
           	
      }
    }
  }

ListPage.ets

typescript 复制代码
import router from '@ohos.router'
@Entry
@Component
  struct ListPage {
    @State message: string = 'Hello World'
 
    build() {
      Column() {
          Text("list 页面").fontSize(32)
          Button("返回").onClick(()=>{
              // 返回到用pushUrl跳转过来的页面
              router.back()
          })
      }
    }
  }

路由跳转练习

新建DetailPage.ets页面

typescript 复制代码
import router from '@ohos.router'
@Entry
@Component
struct DetailPage {
  @State title: string = 'Hello World'
  @State price: number = 0
    
  // 当页面一开始加载就会自动执行onPageShow函数
  onPageShow(){
      // 获取路由传来的参数
      const params = router.getParams()
      this.title = params['title']
      this.price = params['price']
      
  }

  build() {
    Column() {
        Button("返回").onClick(()=>{
            router.back()
        })
        Text("详情页").fontSize(24)
        
        Text(`名称${this.title}`)
        Text(`名称${this.price}`)
    }
  }
}

ListPage.ets

typescript 复制代码
import router from '@ohos.router'
@Entry
@Component
  struct ListPage {
    @State message: string = 'Hello World'
      @State list:Object[] = [
          {
              id:"1",
              title:"三国演义",
              price:78.8
          },
          {
              id:"2",
              title:"西游记",
              price:78.8
          },
          {
              id:"3",
              title:"红楼梦",
              price:78.8
          },
          {
              id:"4",
              title:"水浒传",
              price:78.8
          }
		]
 
    build() {
      Column() {
          Text("list 页面").fontSize(32)
          Button("返回").onClick(()=>{
              // 返回到用pushUrl跳转过来的页面
              router.back()
          })
          
          Column(){
              ForEach(this.list,(item)=>{
                  Row(){
                      Text(item.title)
                  }.height(50).width("100%").backgroundColor(0xeeeeee).margin({top:10})
                  .onClick(()=>{
                      router.pushUrl({
                          url:"pages/DetailPage",
                          params:item
                      })
                  })
              },(item)=>item.id)
          }.width("100%")
      }.width("100%").height("100%")
    }
  }

模板文件待删除

typescript 复制代码
@Entry
@Component
struct TextPage {
  @State message: string = 'Hello World'

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
    }
    .height('100%')
  }
}

综合练习

做一个todolist,待办任务。

新建一个工程项目,就叫TodoList。

在pages目录下,新建一个TodoList.ets文件

typescript 复制代码
// TodoList.ets
// 导入Todos文件
import Todos from './components/Todos'
import {ObjectData,ListData} from '../viewmodel/DataModel'

@Entry
@Component
struct TodoList {
  // 所有的列表
  @State myLists: Array<ObjectData> = []
  // 当前要渲染的列表
  @State curList:Array<ObjectData> = []
  @State inputText:string = ""

  aboutToAppear(){
    this.myLists = new ListData().getList()
    this.curList = this.myLists;
  }

  build() {
    Stack({alignContent:Alignment.Bottom}){
      Row() {
        Column() {
          Text("任务列表")
            .fontSize(30)
            .fontWeight(FontWeight.Bold)
            .margin({top:20})

          Row({space:20}){
            Button("全部").backgroundColor(Color.Red)
              .onClick(()=>{
                this.curList = this.myLists
              })
            Button("已完成").backgroundColor(Color.Blue)
              .onClick(()=>{
                // 使用过滤器filter来过滤出status为true的对象
                this.curList = this.myLists.filter(item=>item.status)
              })
            Button("未完成").backgroundColor(Color.Blue)
              .onClick(()=>{
                // 使用过滤器filter来过滤出status为false的对象
                this.curList = this.myLists.filter(item=>!item.status)
              })
          }.margin({top:20})

          // 调用外部组件
          // 调用Array<ObjectData>类型数据,要用$
          Todos({lists:$curList})
        }
        .width("100%")
        .height("100%")
        .alignItems(HorizontalAlign.Center)
      }
      .height('100%')
      .backgroundColor(0xcccccc)
      .alignItems(VerticalAlign.Top)


      // 输入框
      Row(){
        // text:this.inputText 表示将this.inputText的值显示到输入框中
        TextInput({placeholder:"请输入待办任务",text:this.inputText})
          .width("60%")
          .height(50)
          .backgroundColor(Color.White)
          .onChange(value=>{
            this.inputText = value
          })
        Button("添加")
          .width("30%")
          .height(50)
          .margin({left:20})
          .onClick(()=>{
            if(this.inputText){
              this.myLists.push(new ObjectData(this.myLists.length+1,this.inputText,false))
              // 清空输入框
              this.inputText = ""
            }

          })
      }
      .margin({bottom:20})
    }
  }
}

在pages目录下新建一个文件夹,就叫components,然后再在该目录下新建一个Todos.ets文件

tsx 复制代码
// Todos.ets
import {ObjectData,ListData} from '../../viewmodel/DataModel'
@Component
export default struct Todos{
  @Link lists:Array<ObjectData>;

  scroller:Scroller = new Scroller()

  @Builder
  taskItem(item:ObjectData){
    Row(){
      // 使用复选框组件
      Toggle({type:ToggleType.Checkbox,isOn:item.status})
        .onChange(event => {
          // 找出当前被点击的对象
          const curTodo = this.lists.find(todo => todo.id === item.id)
          curTodo.status = event;
        })
      Text(item.task)
        .margin({left:20})
    }
    .width("90%")
    .height(50)
    .padding({left:20})
    .margin({top:20})
    .backgroundColor(Color.White)
    .borderRadius(20)
  }

  build(){
    // Scroll使用滚动组件,实现页面滚动
    Scroll(this.scroller){
      Column(){
        ForEach(this.lists,(item)=>{
          this.taskItem(item)
        },item=>item.id)
      }
    }
    .scrollable(ScrollDirection.Vertical) // 设置滚动方向为垂直滚动
    .scrollBar(BarState.On) // 设置开启滚动条
    .scrollBarColor(Color.Gray) // 设置滚动条颜色
    .scrollBarWidth(10) //设置滚动条宽度
    .edgeEffect(EdgeEffect.Spring)
    .margin({bottom:200})
  }
}

在ets文件夹下新建一个目录,就叫viewmodel,然后在该文件夹下新建一个文件,就叫DataModel.ets

typescript 复制代码
// DataModel.ets
export class ObjectData{
    id:number;
    task:string;
    status:boolean;
    
    constructor(id:number,task:string,status:boolean){
        this.id = id;
        this.task = task;
        this.status = status
    }
}

export class ListData{
    private lists:Array<ObjectData> = [
        new ObjectData(1,"吃饭",true)
        new ObjectData(2,"唱歌",false)
        new ObjectData(3,"写作业",false)
    ]
    
    getList(){
        retrun this.lists
    }
}
相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习
im_AMBER5 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J5 天前
从“Hello World“ 开始 C++
c语言·c++·学习