一百天挑战学会HarmanyOS——组件的样式

327 阅读10分钟

大家好我是牛牛,一名软件开发从业者,无意中接触到了鸿蒙移动端开发,对鸿蒙操作系统产生了极大的兴趣,作者将从无到有开发出一款鸿蒙原生APP。每天写一篇关于鸿蒙开发的技术文章。欢迎大家踊跃订阅➕关注,文章中有什么不妥之处可以在评论区中指出。

注意:最好是有开发经验的伙伴来阅读系列文章。零基础的同学可以先去了解一下TypeScript从最基本的开发语言进行学起

前言

上一章节《一百天挑战学会HarmanyOS——组件的双向绑定与事件监听》介绍了组件的双向绑定,组件的状态管理,有了这些使得有着更好的开发体验,而在实际开发过程中,用法比上一章节案例中代码复杂的多,所以有兴趣的伙伴一定要去亲自尝试。有什么疑问的地方大家可以在评论区留言。

本章介绍

本章节主要介绍ArkUI中样式相关的知识点,在其他章节中的代码案例中也出现过样式的使用,但是没有深入的去讲解,包括其中还有框架所提供的定义组件重用样式定义扩展组件样式多态样式,这些内容将在这一章节中展开。

样式

样式-语法(链式调用&枚举)

ArkTs以声明方式组合和扩展组件来描述应用程序的 UI;同时还提供了基本的属性,事件和子组件的配置方法,帮助开发者实现应用的交互逻辑。

样式属性

  • 属性方法以.的链式调用的方式配置系统组件的样式和其他属性。

什么是链式调用,以下面的示例为例:

@Entry
@Component
struct StyleCasePage {
  build() {
    Column() {
      Text('样式演示')
        .fontSize(30)
        .width('100%')
        .textAlign(TextAlign.Center)
    }
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}

以上代码中Text组件后面使用的属性方法.fontSize(),.width(),.textAlign(), 都是给 Text 组件修饰样式的属性方法,使用.的链式调用的方式。

枚举

  • 对于系统组件,ArkUI 还为其属性定义了一些枚举类型。详细见枚举文档链接
@Entry
@Component
struct StyleCasePage {
  build() {
    Column() {
      Text('样式演示')
        .fontSize(30)
        .width('100%')
        //  使用枚举值进行布局的设置
        .textAlign(TextAlign.Center)
    }
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}

以上代码中Text组件后面使用的.textAlign()属性方法里的参数值TextAlign.CenterTextAlign就是一个枚举类型,用来设置文本的对齐方式,示例代码中设置的为居中显示。查看源码得知TextAlign是一个枚举类型。

image.png

样式-单位(vp&fp)

官方定义

  • 使用虚拟像素,使元素在不同密度的设备上具有一致的视觉体量。

vp 是什么?

英文名:virtual pixel

  • 屏幕密度相关像素,根据屏幕像素密度转换为屏幕的物理像素,当数值不带单位时,默认单位使用vp;在实际像素 1440 物理像素的屏幕上,1vp约等于3px物理像素。

image.png

其实在文章上面的案例代码中有使用过vp,注意 vppx 单位显示效果的是不同的,下面代码作为演示。

使用 vp 为单位:

@Entry
@Component
struct StyleCasePage {
  build() {
    Column() {
      Text('样式演示')
         // 不书写单位默认使用 vp
        .fontSize(30)
        .width('100%')
        .textAlign(TextAlign.Center)
    }
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }

image.png

使用 px 为单位:

@Entry
@Component
struct StyleCasePage {
  build() {
    Column() {
      Text('样式演示')
        // 使用 px 为单位
        .fontSize('30px')
        .width('100%')
        .textAlign(TextAlign.Center)
    }
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}

image.png

fp是什么?

英文名: font pixel

字体像素(font pixel) 大小默认情况下与 vp 相同,即默认情况下 1 fp = 1vp。如果用户在设置中选择了更大的字体,字体的实际显示大小就会在 vp 的基础上乘以用户设置的缩放系数,即 1 fp = 1 vp * 缩放系数。

ArkUI为开发者提供4种像素单位,框架采用vp为基准数据单位。

image.png

提供其他单位与px单位互相转换的方法。

image.png

示例:

image.png

@Entry
@Component
struct Example {
  build() {
    Column() {
      Flex({ wrap: FlexWrap.Wrap }) {
        Column() {
          Text("width(220)")
            .width(220)
            .height(40)
            .backgroundColor(0xF9CF93)
            .textAlign(TextAlign.Center)
            .fontColor(Color.White)
            .fontSize('12vp')
        }.margin(5)

        Column() {
          Text("width('220px')")
            .width('220px')
            .height(40)
            .backgroundColor(0xF9CF93)
            .textAlign(TextAlign.Center)
            .fontColor(Color.White)
        }.margin(5)

        Column() {
          Text("width('220vp')")
            .width('220vp')
            .height(40)
            .backgroundColor(0xF9CF93)
            .textAlign(TextAlign.Center)
            .fontColor(Color.White)
            .fontSize('12vp')
        }.margin(5)

        Column() {
          Text("width('220lpx') designWidth:720")
            .width('220lpx')
            .height(40)
            .backgroundColor(0xF9CF93)
            .textAlign(TextAlign.Center)
            .fontColor(Color.White)
            .fontSize('12vp')
        }.margin(5)

        Column() {
          Text("width(vp2px(220) + 'px')")
            .width(vp2px(220) + 'px')
            .height(40)
            .backgroundColor(0xF9CF93)
            .textAlign(TextAlign.Center)
            .fontColor(Color.White)
            .fontSize('12vp')
        }.margin(5)

        Column() {
          Text("fontSize('12fp')")
            .width(220)
            .height(40)
            .backgroundColor(0xF9CF93)
            .textAlign(TextAlign.Center)
            .fontColor(Color.White)
            .fontSize('12fp')
        }.margin(5)

        Column() {
          Text("width(px2vp(220))")
            .width(px2vp(220))
            .height(40)
            .backgroundColor(0xF9CF93)
            .textAlign(TextAlign.Center)
            .fontColor(Color.White)
            .fontSize('12fp')
        }.margin(5)
      }.width('100%')
    }
  }
}

在样式中,我们如果写px,那么px直接表示的是物理像素,也就是分辨率,那么我们的手机分辨率密度各有不同,无法针对这种密度写一个固定值,所以vp会自动根据手机密度去进行适配,所以vp它提供了一种灵活的方式来适应不同屏幕密度的显示效果。

在不同屏幕物理分辨率下,要想实现等比例适配, 可以吗?

如下图:

bianhua.gif

  • 设置lpx基准值 - resources/base/profile/main-pages.json

  • 添加window属性,设置desigWidth,不设置也可以使用lpx,默认720

image.png

@Entry
@Component
struct PXCase {
  build() {
    Row() {
      Column() {
        Text('375lpx')
          .width('375lpx')
          .height('72lpx')
          .textAlign(TextAlign.Center)
          .backgroundColor(Color.Red)
        Divider().strokeWidth(2)
        Row(){
          Text('72lpx')
        }
        .width('72lpx')
        .height('25lpx')
        .backgroundColor(Color.Brown)
      }
      .width('100%')
    }
    .height('100%')
  }
}

伸缩布局方案

layoutWeight

有三种方式提供选择:

  • 设定基准值,使用lpx,类似于前端的rem
  • 监听元素的变化-可以拿到宽高-重新计算
  • layoutWeight(number)- 剩余资源再分配

下面介绍一下使用 layoutWeight的方式

伸缩 layoutWeight(flex: number) 占剩余空间多少份,可以理解成CSS的 flex: 1

示例:

image.png

我们可以使用layoutWeight属性,让右侧内容去占满剩余宽度

代码:

@Entry
@Component
struct LayoutCasePage {

  build() {
    Row() {
      Text("左侧内容")
      Text("右侧内容")
        .textAlign(TextAlign.End)
        .width('80%')
        .height(60)
        .backgroundColor('red')
        .layoutWeight(1)
    }.width('100%')
    .height('100%')
  }
}

再如下图:

当我们使用layoutWeight属性进行剩余空间布局的时候,我让绿色部分占了剩余空间的 5份,橘黄色部分占用了剩余空间的1份

image.png

@Entry
@Component
struct LayoutCasePage {
  build() {
    Column() {
      Row() {

      }
      .width('100%')
      .height(50)
      .backgroundColor(Color.Blue)

      Column() {

      }
      .width('100%')
      .backgroundColor(Color.Green)
      // 其中绿色占 5 份
      .layoutWeight(5)
      Column() {

      }
      .width('100%')
      .backgroundColor(Color.Orange)
      // 橘黄色占 1 份
      .layoutWeight(1)

      Row() {

      }
      .width('100%')
      .height(50)
      .backgroundColor(Color.Red)
    }
    .height("100%")
    .width("100%")
    .justifyContent(FlexAlign.SpaceBetween)
  }

}

设置元素的宽高比

使用aspectRatio属性方法设置元素的宽高比,使得在不同的大小的设备上,元素的宽高比会等比缩放。元素不会在不同设备上显示形状不统一的情况。

如下图:

image.png

@Entry
@Component
struct LayoutCasePage {
  build() {
    Column()
      .width('50%')
      .height('50%')
      .backgroundColor('blue')
      .aspectRatio(1)
  }
}

样式复用@Styles

在开发过程中会出现大量代码在进行重复样式设置,@Styles装饰器可以帮我们进行样式复用

注意:

  • @Styles@Extend均只支持在当前文件下的全局或者组件内部定义,如果你想要在其他文件导出一个公共样式,导出公共使用,ArtTS是不支持的,这种方式还是需要考虑组件复用。
  • @Styles修饰的函数中能够点出来就是通用属性和事件-Text的字体颜色-字体大小不属于通用属性
  • @Styles修饰的函数不允许传参数
  • 全局@Styles不支持箭头函数语法
  • 全局@Styles扩展符只能和使用它的组件位于同一个文件,不允许导入导出,导入导出也使用不了

下面使用示例为大家演示一下

假如我们要实现下面的功能,页面中有三个按钮,这个三个按钮的颜色,样式,点击事件都是一样的,如果不用样式复用代码是什么样子的呢?

效果图:

image.png

不使用样式复用代码实现

import { promptAction } from '@kit.ArkUI'

@Entry
@Component
struct StylePage {
  build() {
    Column({ space: 20 }) {
      Row() {
        Button("微信支付", { type: ButtonType.Normal })
            .width('100%')
            .height(50)
            .borderRadius(4)
             .backgroundColor("#00c168")
            .onClick(() => {
              promptAction.showToast({ message: '支付宝支付成功' })
            })
          .fontColor(Color.White)
      }
      .padding(10)
      Row() {
        Button("微信支付", { type: ButtonType.Normal })
          .width('100%')
          .height(50)
          .borderRadius(4)
          .backgroundColor("#00c168")
          .onClick(() => {
            promptAction.showToast({ message: '支付宝支付成功' })
          })
          .fontColor(Color.White)
      }
      .padding(10)
      Row() {
        Button("微信支付", { type: ButtonType.Normal })
          .width('100%')
          .height(50)
          .borderRadius(4)
          .backgroundColor("#00c168")
          .onClick(() => {
            promptAction.showToast({ message: '支付宝支付成功' })
          })
          .fontColor(Color.White)
      }
      .padding(10)
    }
  }
}

我们发现在以上代码中每个Button 组件中使用的样式属性都是统一的,如果页面有 10 个按钮呢?我们需要些 10遍吗?

使用@Styles的方式进行样式的复用代码实现

我们把代码中每个Button 组件中使用的样式属性抽成一个方法,用@Styles 进行装饰,然后在代码中调用这个方法即可。

@Styles
  payStyle () {
  .width('100%')
  .height(50)
  .borderRadius(4)
  .backgroundColor("#00c168")
  .onClick(() => {
    promptAction.showToast({ message: '微信支付成功' })
  })
}

完整示例

import { promptAction } from '@kit.ArkUI'

@Entry
@Component
struct StylePage {

  @Styles
  payStyle () {
  .width('100%')
  .height(50)
  .borderRadius(4)
  .backgroundColor("#00c168")
  .onClick(() => {
    promptAction.showToast({ message: '微信支付成功' })
  })
}

  build() {
    Column({ space: 20 }) {
      Row() {
        Button("微信支付", { type: ButtonType.Normal })
          .payStyle()
          .fontColor(Color.White)
      }
      .padding(10)
      Row() {
        Button("微信支付", { type: ButtonType.Normal })
          .payStyle()
          .fontColor(Color.White)
      }
      .padding(10)
      Row() {
        Button("微信支付", { type: ButtonType.Normal })
          .payStyle()
          .fontColor(Color.White)
      }
      .padding(10)

    }
  }
}

样式复用@Extend

假设我们就想针对Button进行字体和样式的复用,此时可以使用@Extend来修饰一个全局的方法

注意:

  • 使用 @Extend 装饰器修饰的函数只能是 全局
  • 函数可以进行 传参,如果参数是状态变量,状态更新后会刷新UI
  • 且参数可以是一个函数,实现复用事件且可处理不同逻辑
  • @Extend扩展符只能和使用它的组件位于同一个文件,不允许导入导出,导入导出也使用不了

假如我们有这样一个需求,根据不同类型设置不同按钮的样式与点击事件。这个时候使用@Styles 就满足不了这个场景啦。下面使用@Extend来实现。

示例:

image.png

代码:

import { promptAction } from '@kit.ArkUI'

@Entry
@Component
struct ExtendCase {

  build() {
    Column({ space: 20 }) {
      Button("微信支付")
        .payButton("alipay")
      Button("微信支付")
        .payButton("wechat")
      Button("微信支付")
        .payButton("alipay")
      Button("微信支付")
        .payButton("wechat")
      Button("微信支付")
        .payButton("alipay")
      Button("微信支付")
        .payButton("wechat")
      Button("微信支付")
        .payButton("alipay")

    }
    .padding(20)
    .width('100%')
  }
}

// 不允许导出
@Extend(Button)
function  payButton (type: "alipay" | "wechat") {
  .type(ButtonType.Normal)
  .fontColor(Color.White)
  .width('100%')
  .height(50)
  .borderRadius(4)
  .backgroundColor(type === "wechat" ? "#00c168" : "#ff1256e0")
  .onClick(() => {
    if(type === "alipay") {
      promptAction.showToast({ message: '支付宝支付成功' })
    }else {
      promptAction.showToast({ message: '微信支付成功' })
    }

  })
}

多态样式stateStyles

@Styles@Extend仅仅应用于静态页面的样式复用,stateStyles可以依据组件的内部状态的不同,快速设置不同样式。这就是我们要介绍的内容stateStyles(又称为:多态样式)。

ArkUI 提供以下五种状态:

  • focused:获焦态。
  • normal:正常态。
  • pressed:按压态。
  • disabled:不可用态。
  • selected: 选中态

假设我们想做一个微信中点击的选中状态, 如图

image.png

该图在点击时会有变色,抬起时消失,此时就可以利用多态样式进行设置

bianhua.gif

代码:

@Entry
@Component
struct StateStylesCase {
  build() {
    Column({ space: 20 }) {
      Row() {
        Text("你今天想我了吗")
      }
      .padding(20)
      .height(80)
      .border({
        color: '#f3f4f5',
        width: 3
      })
      .borderRadius(4)
      // 多态样式
      .stateStyles({
        // 正常态
        normal: {
          .backgroundColor(Color.White)
        },
        pressed: {
          .backgroundColor("#eee")
        }
      })
      .width('100%')
    }
    .padding(20)
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('100%')
  }
}

这一期就到这里啦!为了方便大家我这一期代码放到git仓库上,建议大家动起手实操起来。 项目代码git