HarmonyOS学习第一周(实践篇)

Demo1:图片缩放案例

实现图:

代码:
@Entry
@Component
struct Index {
  @State imageWidth: number = 200

  build() {
    Column() {
      Row(){
        Image($r('app.media.icon'))
          .width(this.imageWidth)
          .interpolation(ImageInterpolation.High)
      }
      .width("100%")
      .height(400)
      .justifyContent(FlexAlign.Center)
      Row(){
        Text($r('app.string.width_label'))
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        TextInput({text:this.imageWidth.toFixed(0)})
          .width(200)
          .backgroundColor('#fff')
          .type(InputType.Number)
          .onChange((value)=>{
            this.imageWidth = parseInt(value)
          })
      }
      // .padding(20)
      .padding({left:20,right:20})
      .width("100%")
      .justifyContent(FlexAlign.SpaceBetween)
      Divider()
      .width("90%")

      Row(){
        Button('缩小')
          .width(80)
          .fontSize(20)
          .onClick(()=>{
            if(this.imageWidth>=80){
              this.imageWidth -= 10
            }else{
              AlertDialog.show(
                {
                  title: '警告',
                  message: '不可再减小',
                })
            }
          })
        Button('增大')
          .width(80)
          .fontSize(20)
          .onClick(()=>{
            if(this.imageWidth<=300){
              this.imageWidth += 10
            }else{
              AlertDialog.show(
                {
                  title: '警告',
                  message: '不可再增加',
                })
            }
          })
      }
      .width("100%")
      .margin({top:20})
      .justifyContent(FlexAlign.SpaceEvenly)

      Slider({
        min:100,
        max:300,
        value:this.imageWidth,
        step:10
      })
        .width('100%')
        .blockColor('#36d')
        .margin({top:10})
        .trackThickness(7)
        .showTips(true)
        .onChange((value)=>{
          this.imageWidth=value
        })
    }
    .width('100%')
    .height('100%')
  }
}

本案例为基础入门案例,非常简单。实现了通过输入宽度、按钮、拖拉条三种方式来调节图片的大小。所用组件、方法、布局如下:

布局:

Column和Row容器相结合

Column容器

Column是沿垂直方向布局的容器

Row容器

Row是沿水平方向布局的容器

设置主轴上的排列可以用justifyContent,与CSS中的使用基本一致,有start(从行首开始排列),left(对齐左边缘排列),SpaceEvenly(均匀排列每个元素,元组件间距相等),Center(居中排列)等多种属性可以选择。

Center                                                SpaceEvenly

组件:

Image组件

Image组件可以加载本地图片和网络图片

加载本地图片时可以通过Image($r(app.media.图片名))的形式,($r方法用于读取base目录下的media目录中的图片)或者以Image($rawfile('图片名.图片格式))来实现。

加载网络图片则可以通过Image('https://xxx.png')的格式来实现,但需要注意的是,使用模拟器显示图片时,需要在module.json5配置文件中配置权限

如上图,在requestPermissions中配置网络权限,则可以使用网络图片。

(具体参考访问控制授权申请-访问控制-安全-开发 | 华为开发者联盟 (huawei.com)

Text组件

text组件是用于显示文本的组件

最简单的如Text('hello world')这种直接在组件中填入字符串的方式

或者通过resource格式,读取本地的资源文件夹resources中的限定词目录en_US(英文)和zh_CN(中文)

在en_US(英文)的string.json中添加:   在zh_CN(中文)的string.json中添加

                  

然后在代码中以Text($r('app.string.width_label))来读取value值,系统会根据环境渲染出对应的value值

如当前为zh-CN,则渲染出来的值为“图片宽度”。

这里有个需要注意的点!

除了在上述两个文件夹的string.json中添加外,我们还需要再base的element下的string.json添加一次,value值为中文或者英文都可,因为如果编译器在限定词目录中找不到你所定义的参数的话,他会回到base下寻找,如果没有在base下添加的话,会发生报错。

TextInput组件

这是一个单行文本输入框组件

可以通过设置placeholder的值来设置用户为输入之前的提示文本

或者设置text属性来设置文本行中的初始值

Button组件

按钮组件,可快速创建不同样式的按钮。

常与点击事件onClick方法结合使用,点击后实现某些逻辑操作。

可以通过type属性控制属性样式,Capsule为胶囊型,Circle为圆形(可以结合icon实现图标按钮),Normal为长方形。

同时可以设置stateEffect的boolean值来控制奠基石是否出现按压效果。

        Button({type:ButtonType.Capsule,stateEffect:true})
Slider组件

滑动条组件,通常用于快速调节设置值。

通常需要设置min(最小值),max(最大值),value(初始值),step(步长,即每移动一次增加或者减少多少)。

方法:

onChange方法

当方法中的值发生改变时会触发调用,常用于滚动事件、输入事件等,如上述的TextInput组件和Slider组件中就绑定了onChange方法来检测值的变化。

.onChange((value)=>{
          this.imageWidth=value
        })

如上述代码实现了将滑动条的值赋值给图片的宽度,实现调节图片大小的功能。

onClick方法

当方法绑定的组件被点击时会触发调用,常用于按钮组件,图片点击出现大图等,如上述点击按钮实现增大缩小图片的功能就有所体现。

Demo2:商品列表案例

实现图:

index代码:
class Item {
  name: string
  image: ResourceStr
  price: number
  discount: number

  constructor(name: string, image: ResourceStr, price: number, discount: number = 0) {
    this.name = name
    this.image = image
    this.price = price
    this.discount = discount
  }
}

import {Header} from '../component/TitleHeader'

//全局自定义构建函数
// @Builder function ItemCard(item:Item){
//   Row({space:10}){
//     Image(item.image)
//       .width(100)
//     Column({space:4}){
//       Text(item.name)
//         .fontSize(20)
//         .fontWeight(FontWeight.Bold)
//       if(item.discount){
//         Text('原价:¥'+item.price)
//           .fontColor('#CCC')
//           .fontSize(14)
//           .decoration({type:TextDecorationType.LineThrough})
//         Text('折扣价:¥'+(item.price-item.discount))
//           .fontColor('#F36')
//           .fontSize(18)
//         Text('补贴:¥'+item.discount)
//           .fontColor('#F36')
//           .fontSize(18)
//       }else{
//         Text('¥'+item.price)
//           .fontColor('#F36')
//           .fontSize(18)
//       }
//     }
//     .height('100%')
//     .alignItems(HorizontalAlign.Start)
//   }
//   .width('100%')
//   .backgroundColor('#FFF')
//   .borderRadius(20)
//   .height(120)
//   .padding(10)
// }

//全局公共样式
// @Styles function fillScreen(){
//   .width('100%')
//   .backgroundColor('#EFEFEF')
//   .height("100%")
//   .padding(20)
// }

//不是全局通用样式,所以要用Extend()继承装饰器
@Extend(Text) function priceText(){
  .fontColor('#F36')
  .fontSize(18)
}

@Entry
@Component
struct ItemPage {
  // 商品数据
  private items: Array<Item> = [
    new Item('华为Mate60', $r('app.media.mate60'),6999, 500),
    new Item('MateBookProX', $r('app.media.mateBookProX'),13999),
    new Item('WatchGT4', $r('app.media.watchGT4'),1438),
    new Item('FreeBuds Pro3', $r('app.media.freeBudsPro3'),1499),
    new Item('Mate X5', $r('app.media.mateX5'),12999)
  ]

  build() {
    Column({space: 8}){
      Header({title:'商品页面'})
        .margin({top:20,bottom:10})

      List({space:8}){
        ForEach(
          this.items,
          (item: Item)=>{
            ListItem(){
              this.ItemCard(item)
            }
          }
        )
      }
      .width('100%')
      .layoutWeight(1)

    }
    .fillScreen()
  }
  //局部自定义构建函数
  @Builder ItemCard(item:Item){
    Row({space:10}){
      Image(item.image)
        .width(100)
      Column({space:4}){
        Text(item.name)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        if(item.discount){
          Text('原价:¥'+item.price)
            .fontColor('#CCC')
            .fontSize(14)
            .decoration({type:TextDecorationType.LineThrough})
          Text('折扣价:¥'+(item.price-item.discount))
            .priceText()
          Text('补贴:¥'+item.discount)
            .priceText()
        }else{
          Text('¥'+item.price)
            .priceText()
        }
      }
      .height('100%')
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .backgroundColor('#FFF')
    .borderRadius(20)
    .height(120)
    .padding(10)
  }

  //局部公共样式
  @Styles fillScreen(){
    .width('100%')
    .backgroundColor('#EFEFEF')
    .height("100%")
    .padding(20)
  }
}
TitleHeader代码:
@Component
export struct Header{
  private title:ResourceStr
  build(){
    Row(){
      Image($r('app.media.back'))
        .width(30)
      Text(this.title)
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      Blank()//空白组件
      Image($r('app.media.return'))
        .width(30)
    }
    .width("100%")
    .height(30)
  }
}

这个案例是我们非常熟悉的手机商品列表页,也是非常基础的一个案例。

这个案例中涉及到的大部分组件、布局等前面案例中已经讲过,就不再赘述了。我介绍一些前面案例中所没有涉及的内容。

组件:

Blank组件

空白填充组件,在容器主轴方向上,空白填充组件具有自动填充容器空余部分的能力。仅当父组件为Row/Column/Flex时生效。

如这个页头,又三部分组成,返回符号,商品页面文字,刷新符号,我们如果使用space,他将会让三个部分之间两两间隔,无法实现我们想要的效果。那么我们就可以在文字与刷新符号之间添加一个Blank组件,使其占满两者之间的空余,把刷新符号“挤”到右边去。

自定义组件

在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。

自定义组件的特点:可组合、可重用、数据驱动UI更新。

@Component
struct Header{
  build(){
    Row(){...}
    .width("100%")
    .height(30)
  }
}

自定义组件的基本结构:首先要有@Component装饰器,@Component装饰器仅能装饰struct关键字声明的数据结构,而自定义组件基于struct实现,build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数,三者缺一不可,所以基本结构如上图代码。

@Entry装饰的自定义组件将作为UI页面的入口。在单个UI页面中,最多可以使用@Entry装饰一个自定义组件。在@Entry装饰的组件中,可以调用其他自定义组件。换句话说,我们可以在@Entry中一些重复用到的代码封装为一个自定义组件,以达到复用的效果。

若这些自定义组件不止在一个UI页面中被使用到,我们则可以将其封装为单独的ets文件,存放在Components文件夹中,同时我们需要再struct前加上export关键字,将此方法导出,然后使用import关键字进行引入,如:

import {Header} from '../component/TitleHeader'

这样我们就可以在多个UI页面中使用到我们封装出的自定义组件了。

自定义构建函数

自定义构建函数的装饰器为@Builder,@Builder所装饰的函数遵循build()函数语法规则,开发者可以将重复使用的UI元素抽象成一个方法,在build方法里调用。自定义构建函数分为全局自定义构建函数,局部自定义构建函数。类比全局变量和局部变量我们可知,两者的差别在于其可使用的范围。

全局自定义构建函数:
@Builder function ItemCard(item:Item){
  Row({space:10}){...}
    .height('100%')
    .alignItems(HorizontalAlign.Start)
  }
  .width('100%')
  .backgroundColor('#FFF')
  .borderRadius(20)
  .height(120)
  .padding(10)
}

如上述代码所示,@Builder function 函数名(参数){函数内容},这就是全局自定义构建函数的基本形式。函数内容和在@Component编写代码是相同的,就是将一段代码封装进函数中。同时因为是全局函数,所以不可以定义在组件内,应该在@Component之外的位置进行定义,使用也非常简单,和其他内置的组件一样使用就可以。如上述自定义函数,则在需要的地方添加ItemCard(item)即可。

局部自定义构建函数
@Builder ItemCard(item:Item){
    Row({space:10}){...}
      .height('100%')
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .backgroundColor('#FFF')
    .borderRadius(20)
    .height(120)
    .padding(10)

局部自定义构建函数和全局自定义构建函数的编写方式非常相似,但是局部构建函数不需要添加function关键字,且应定义在要使用到这个构建函数的@Component之中,其余地方与全局自定义构建函数无异。

自定义构建函数和自定义组件都可以做到复用代码的目的,提高了代码的复用性,但是二者也存在着一定的差别。

  • 自定义构建函数(@Builder)更轻量,其作为UI元素抽象的方法,实现和调用相较于自定义组件比较简洁。
  • 在自定义组件中,可以定义成员函数/变量、自定义组件生命周期等。
  • 而自定义构建函数(@Builder)不支持定义状态变量和自定义生命周期。
  • 在自定义组件中,可直接通过状态变量的改变,来驱动UI的刷新。
  • 而自定义构建函数(@Builder)默认的按值参数传递方式不支持动态改变组件,当传递的参数为状态变量时,状态变量的改变不会引起@Builder方法内的UI刷新,要实现UI动态刷新需• 要按引用传递参数。​
  • 在自定义组件中要实现插槽功能,需要使用@Builder和@BuilderParam实现。
  • 具体实现可参考:​​@BuilderParam装饰器:引用@Builder函数​​。
  • 自定义构建函数(@Builder)中使用了自定义组件,那么该方法每次被调用时,对应的自定义组件均会重新创建。

(来自论坛一位网友的总结)

公共样式:

同样,公共样式也分全局公共样式和局部公共样式,类比上述的全局自定义构建函数和局部自定义构建函数。公共样式的装饰器是@Styles,@Styles装饰器可以将多条样式设置提炼成一个方法,直接在组件声明的位置调用。

但是需要注意的是,@Styles装饰器下的样式,需要是组件通用样式,如:width, backgroundcolor等,如果出现类似fontsize,fontcolor时,编译器将会报错。

那是否意味着这些样式就不可封装呢,也不是,这时候我们需要用@Extend装饰器。@Extend是在@Style的基础上,用于扩展原生组件样式的。@Extend支持封装指定的组件的私有属性和私有事件和预定义相同组件的@Extend的方法。也就是说,你需要给他一个预定义的组件。如要封装fontsize,fontcolor时,需要预定义组件为Text,具体使用方法如下:

@Extend(Text) function priceText(){
  .fontColor('#F36')
  .fontSize(18)
}

同时也要注意,@Extend仅支持定义在全局,不支持在组件内部定义。

If I never see you again, good morning, good afternoon and good night. ——《楚门的世界》

  • 19
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值