ArkUI 学习之自定义内容(contentModifier)

一、概述

ContentModifier 是一个在鸿蒙(HarmonyOS)开发中使用的接口,允许开发者自定义组件的内容区。通过实现这个接口,开发者可以定义如何修改或自定义组件的显示和行为。

contentModifier(modifier:ContentModifier<T>)

这个属性不是所有的组件都有,仅仅下面这些组件才有

按钮组件:Button
多选框组件:CheckBox
数据面板组件:DataPanel
文本钟组件:TextClock
开关组件:Toggle
数据量规图表组件:Gauge
加载动效的组件:LoadingProgress
单选框组件:Radio
进度条组件:Progress
评分组件:Rating
滑动条组件:Slider

参数

参数名类型必填说明
modifier ContentModifier<T>

在当前组件上,定制内容区的方法。

🔈:modifier:内容修改器,开发者需要自定义class实现ContentModifier接口。

class MyContentModifier implements ContentModifier<ButtonConfiguration> {
  /** ! 成员变量 */

  /** ! 构造函数 */
  constructor() {}

  /** ! 自定义内容区域 */
  applyContent(): WrappedBuilder<[ButtonConfiguration]> {
    return wrapBuilder(buildCheckbox)
  }
}
/** 自定义Builder */
@Builder
function buildCheckbox(config: ButtonConfiguration) {
    
}

二、ContentModifier<T>

applyContent

applyContent() : WrappedBuilder<[T]>

定制内容区的Builder。

参数:

参数描述
T 组件的属性类,用来区别不同组件自定义内容区后所需要的不同信息,比如Button组件的ButtonConfiguration,Checkbox组件的CheckBoxConfiguration等。
T参数支持范围:
ButtonConfiguration、
CheckBoxConfiguration、
DataPanelConfiguration、
TextClockConfiguration、
ToggleConfiguration、
GaugeConfiguration、
LoadingProgressConfiguration、
RadioConfiguration、
ProgressConfiguration、
RatingConfiguration、
SliderConfiguration

三、CommonConfiguration<T>

开发者需要自定义class实现ContentModifier接口。

参数名类型说明
enabled boolean 如果该值为true,则contentModifier可用,并且可以响应triggerChange等操作,如果设置为false,则不会响应triggerChange等操作。
contentModifier ContentModifier<T> 用于将用户需要的组件信息发送到自定义内容区。

四、示例

🍀 1. ButtonConfiguration

🦋 接口:
declare interface ButtonConfiguration extends CommonConfiguration<ButtonConfiguration> {
    //Button的文本标签。
    label: string;
    //指示是否按下Button。
    //说明:此属性指示的是原本Button是否被按压,而非build出来的新组件。若新build出来 
    //的组件超过原本组件的大小,那么超出部分按压不触发。
    pressed: boolean;
    //使用builder新构建出来组件的点击事件。
    triggerClick: ButtonTriggerClickCallback;
}
🌰 例子:
class MyButtonContentModifier implements ContentModifier<ButtonConfiguration> {
  /** ! 实例变量 */
  pressColor: ResourceColor
  /** ! 构造器 */
  constructor(pressColor: ResourceColor) {
    this.pressColor = pressColor
  }
  /** ! 内容函数 */
  applyContent(): WrappedBuilder<[ButtonConfiguration]> {
    return wrapBuilder(buttonContentModifier)
  }
}

@Builder
function buttonContentModifier(config: ButtonConfiguration) {
  Column({ space: 10 }) {
    if (config.pressed) {
      Text('这是一个自定义文本按钮').fontColor(Color.Green)
      Image($r('app.media.icon_one'))
    } else {
      Image($r('app.media.icon_two'))
      Text('这是一个自定义文本按钮').fontColor((config.contentModifier as MyButtonContentModifier).pressColor)
    }
  }
}


@Entry
@Component
struct Index {
  /** MARK: 成员变量 */

  /** MARK: 构建函数 */
  build() {
    Row() {
      Column({ space: 50 }) {
        Button('正常按钮')
          .width('100%')
          .height(50)
          .backgroundColor(Color.Blue)

        Button('自定义内容按钮')
          .width('100%')
          .height(50)
          .contentModifier(new MyButtonContentModifier(Color.Red))
          .backgroundColor(Color.Gray)
      }.width('100%').padding({ left: 20, right: 20 })
    }.height('100%')
  }
}

🍀 2. CheckBoxConfiguration

🦋 接口:
declare interface CheckBoxConfiguration extends CommonConfiguration<CheckBoxConfiguration> {
    //当前多选框名称。
    name: string;
    //指示多选框是否被选中。
    //如果select属性没有设置默认值是false。
    //如果设置select属性,此值与设置select属性的值相同。
    selected: boolean;
    //触发多选框选中状态变化。
    triggerChange: Callback<boolean>;
}
🌰 例子:
class MyCheckBoxContentModifier implements ContentModifier<CheckBoxConfiguration> {
  /** ! 成员变量 */
  /** ! 构造函数 */
  constructor() {}
  /** ! 自定义内容 */
  applyContent(): WrappedBuilder<[CheckBoxConfiguration]> {
    return wrapBuilder(checkBoxContentModifier)
  }
}

@Builder
function checkBoxContentModifier(config: CheckBoxConfiguration) {
  Text(config.name + (config.selected ? "( 选中 )" : "( 非选中 )"))
    .onClick(() => {
      config.triggerChange(!config.selected)   /** ⚠️注意:通过triggerChange改变select状态 */
    })
}

@Entry
@Component
struct Index {
  /** MARK: 成员变量 */
  @State checkBoxEnabled: boolean = true;

  /** MARK: 构建函数 */
  build() {
    Row() {
      Column() {
        Column({ space: 100 }) {
          /** 勾选框 */
          Row({ space: 10 }) {
            Text('正常:').fontWeight(500)
            Checkbox({ name: '复选框状态', group: 'checkboxGroup' })
              .enabled(this.checkBoxEnabled)
              .onChange((value: boolean) => {
                console.info('Checkbox change is' + value)
              })
          }

          Row({ space: 10 }) {
            Text('自定义内容:').fontWeight(500)
            Checkbox({ name: '复选框状态', group: 'checkboxGroup' })
              .enabled(this.checkBoxEnabled)
              .contentModifier(new MyCheckBoxContentModifier())
              .onChange((value: boolean) => {
                console.info('Checkbox change is' + value)
              })
          }

          /** 开关 */
          Row() {
            Toggle({ type: ToggleType.Switch, isOn: true })
              .onChange((value: boolean) => {
                if (value) {
                  this.checkBoxEnabled = true
                } else {
                  this.checkBoxEnabled = false
                }
              })
          }
        }
      }.width('100%').padding({ left: 20, right: 20 })
    }.height('100%')
  }
}

🍀 3. DataPanelConfiguration

🦋 接口:
declare interface DataPanelConfiguration extends CommonConfiguration<DataPanelConfiguration> {
    //当前DataPanel的数据值,最大长度为9。
    values: number[];
    //DataPanel显示的最大值。默认值:100。
    maxValue: number;
}    
🌰 例子:
// xxx.ets
class DataPanelBuilder implements ContentModifier<DataPanelConfiguration> {
  /** ! 构造器 */
  constructor() {}
  /** ! 自定义内容 */
  applyContent(): WrappedBuilder<[DataPanelConfiguration]> {
    return wrapBuilder(buildDataPanel)
  }
}

@Builder
function buildDataPanel(config: DataPanelConfiguration) {
  Column() {
    Column() {
      ForEach(config.values, (item: number, index: number) => {
        ChildItem({ item: item, index: index, max: config.maxValue })
      }, (item: string) => item)
    }.padding(10)

    Column() {
      Line().width("100%").backgroundColor("#ff373737").margin({ bottom: 5 })
    }.padding({ left: 20, right: 20 })

    Row() {
      Text('Length=' + config.values.length + '    ').margin({ left: 10 }).align(Alignment.Start)
      Text('Max=' + config.maxValue).margin({ left: 10 }).align(Alignment.Start)
    }
  }
}

@Entry
@Component
struct Index {
  /** MARK: 构造函数 */
  build() {
    Column() {
      Text("Data panel").margin({ top: 12 });
      Row() {
        DataPanel({ values: [12.3, 21.1, 13.4, 35.2, 26.0, 32.0], max: 140, type: DataPanelType.Circle })
          .width(400)
          .height(260)
          .constraintSize({ maxWidth: "100%" })
          .padding({ top: 10 })
          .contentModifier(new DataPanelBuilder())
      }.margin(15).backgroundColor("#fff5f5f5")
    }
  }
}

@Component
struct ChildItem {
  /** MARK: 成员变量 */
  @Prop item: number;
  @Prop index: number;
  @Prop max: number;
  public color1: string = "#65ff00dd"
  public color2: string = "#6500ff99"
  public color3: string = "#65ffe600"
  public color4: string = "#6595ff00"
  public color5: string = "#65000dff"
  public color6: string = "#650099ff"
  public colorArray: Array<string> = [this.color1, this.color2, this.color3, this.color4, this.color5, this.color6]
  /** MARK: 构建函数 */
  build() {
    RelativeContainer() {
      Row() {
        Rect()
          .height(25)
          .width(this.item * 600 / this.max)
          .foregroundColor(this.colorArray[this.index])
          .radius(5)
          .align(Alignment.Start)
        Text(" " + this.item)
          .fontSize(17)
      }
    }.height(28)
  }
}

🍀 4. TextClockConfiguration

🦋 接口:
declare interface TextClockConfiguration extends CommonConfiguration<TextClockConfiguration> {
    //当前文本时钟时区偏移量。
    timeZoneOffset: number;
    //指示文本时钟是否启动。默认值:true。
    started: boolean;
    //当前文本时钟时区的UTC秒数。
    timeValue: number;
}
🌰 例子:
该示例实现了自定义文本时钟样式的功能,自定义样式实现了一个时间选择器组件:通过文本时钟的时区偏移量与UTC秒数,来动态改变时间选择器的选中值,实现时钟效果。同时,根据文本时钟的启动状态,实现文本选择器的12小时制与24小时制的切换。
class MyTextClockStyle implements ContentModifier<TextClockConfiguration> {
  /** MARK: 成员变量 */
  currentTimeZoneOffset: number = new Date().getTimezoneOffset() / 60
  title: string = ''
  /** MARK: 构造器 */
  constructor(title: string) {
    this.title = title
  }
  /** MARK: 自定义内容 */
  applyContent(): WrappedBuilder<[TextClockConfiguration]> {
    return wrapBuilder(buildTextClock)
  }
}

@Builder
function buildTextClock(config: TextClockConfiguration) {
  Row() {
    Column() {
      Text((config.contentModifier as MyTextClockStyle).title)
        .fontSize(20)
        .margin(20)
      TimePicker({
        selected: (new Date(config.timeValue * 1000 +
          ((config.contentModifier as MyTextClockStyle).currentTimeZoneOffset - config.timeZoneOffset) * 60 * 60 *
            1000)),
        format: TimePickerFormat.HOUR_MINUTE_SECOND
      }).useMilitaryTime(!config.started)
    }
  }
}

@Entry
@Component
struct TextClockExample {
  /** MARK: 成员变量 */
  @State accumulateTime1: number = 0
  @State timeZoneOffset: number = -8
  private controller1: TextClockController = new TextClockController()
  private controller2: TextClockController = new TextClockController()

  /** MARK: 构建函数 */
  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
      Text('Current milliseconds is ' + this.accumulateTime1)
        .fontSize(20)
        .margin({ top: 20 })
      TextClock({ timeZoneOffset: this.timeZoneOffset, controller: this.controller1 })
        .format('aa hh:mm:ss')
        .onDateChange(this.onDateChange)
        .margin(20)
        .fontSize(30)
      TextClock({ timeZoneOffset: this.timeZoneOffset, controller: this.controller2 })
        .format('aa hh:mm:ss')
        .fontSize(30)
        .contentModifier(new MyTextClockStyle('ContentModifier:'))
      Button("start TextClock")
        .margin({ top: 20, bottom: 10 })
        .onClick(this.onStartTextClock)
      Button("stop TextClock")
        .margin({ bottom: 30 })
        .onClick(this.onEndTextClock)
    }
    .width('100%')
    .height('100%')
  }

  /** MARK: 成员函数 */
  // 启动文本时钟
  private onStartTextClock = () => {
    this.controller1.start()
    this.controller2.start()
  }
  // 停止文本时钟
  private onEndTextClock = () => {
    this.controller1.stop()
    this.controller2.stop()
  }
  // 数据改变事件
  private onDateChange = (value: number) => {
    this.accumulateTime1 = value
  }
}

🍀 5. ToggleConfiguration

🦋 接口
declare interface ToggleConfiguration extends CommonConfiguration<ToggleConfiguration> {
    //开关是否打开。默认值:false
    isOn: boolean;
    //是否可以切换状态。
    enabled: boolean;
    //触发switch选中状态变化。
    triggerChange: Callback<boolean>;
}
🌰 例子
该示例实现了自定义Toggle样式的功能。自定义样式实现了通过按钮切换圆形颜色的功能:点击蓝圆按钮,圆形背景变蓝色,点击黄圆按钮,圆形背景变黄色。
// xxx.ets
class MySwitchStyle implements ContentModifier<ToggleConfiguration> {
  /** ! 成员变量 */
  selectedColor: Color = Color.White
  lamp: string = 'string';
  /** ! 构造器 */
  constructor(selectedColor: Color, lamp: string) {
    this.selectedColor = selectedColor
    this.lamp = lamp;
  }
  /** ! 自定义内容 */
  applyContent(): WrappedBuilder<[ToggleConfiguration]> {
    return wrapBuilder(buildSwitch)
  }
}

@Builder
function buildSwitch(config: ToggleConfiguration) {
  Column({ space: 50 }) {
    Circle({ width: 150, height: 150 })
      .fill(config.isOn ? (config.contentModifier as MySwitchStyle).selectedColor : Color.Blue)
    Row() {
      Button('' + JSON.stringify((config.contentModifier as MySwitchStyle).lamp))
        .onClick(() => {
          config.triggerChange(false);
        })
      Button('' + JSON.stringify((config.contentModifier as MySwitchStyle).lamp))
        .onClick(() => {
          config.triggerChange(true);
        })
    }
  }
}

@Entry
@Component
struct Index {
  build() {
    Column({ space: 50 }) {
      Toggle({ type: ToggleType.Switch })
        .enabled(true)
        .contentModifier(new MySwitchStyle(Color.Yellow, ''))
        .onChange((isOn: boolean) => {
          console.info('Switch Log:' + isOn)
        })
    }.height('100%').width('100%')
  }
}

🍀 6. GaugeConfiguration

🦋 接口:
declare interface GaugeConfiguration extends CommonConfiguration<GaugeConfiguration> {
     //当前数据值。
    value: number;
     //当前数据段最小值。
    min: number;
     //当前数据段最大值
    max: number;
}
🌰 例子:

该示例通过contentModifier接口,实现了定制量规图内容区的功能。

class MyGaugeStyle implements ContentModifier<GaugeConfiguration> {
  /** MARK: 成员变量 */
  value: number = 0
  min: number = 0
  max: number = 0

  /** MARK: 构造函数 */
  constructor(value: number, min: number, max: number) {
    this.value = value
    this.min = min
    this.max = max
  }

  /** MARK: 自定义内容 */
  applyContent(): WrappedBuilder<[GaugeConfiguration]> {
    return wrapBuilder(buildGauge)
  }
}

@Builder
function buildGauge(config: GaugeConfiguration) {
  Column({ space: 30 }) {
    Row() {
      Text('【ContentModifier】 value:' + JSON.stringify((config.contentModifier as MyGaugeStyle).value) +
        '  min:' + JSON.stringify((config.contentModifier as MyGaugeStyle).min) +
        '  max:' + JSON.stringify((config.contentModifier as MyGaugeStyle).max))
        .fontSize(12)
    }
    Text('【Config】value:' + config.value + '  min:' + config.min + '  max:' + config.max).fontSize(12)
    Gauge({
      value: config.value,
      min: config.min,
      max: config.max
    }).width("50%")
  }
  .width("100%")
  .padding(20)
  .margin({ top: 5 })
  .alignItems(HorizontalAlign.Center)
}


@Entry
@Component
struct refreshExample {
  /** MARK: 成员变量 */
  @State gaugeValue: number = 20
  @State gaugeMin: number = 0
  @State gaugeMax: number = 100

  /** MARK: 构建函数 */
  build() {
    Column({ space: 20 }) {
      Gauge({
        value: this.gaugeValue,
        min: this.gaugeMin,
        max: this.gaugeMax
      }).contentModifier(new MyGaugeStyle(30, 10, 100))

      Column({ space: 20 }) {
        Row({ space: 20 }) {
          Button('增加').onClick(() => {
            if (this.gaugeValue < this.gaugeMax) {
              this.gaugeValue += 1
            }
          })
          Button('减少').onClick(() => {
            if (this.gaugeValue > this.gaugeMin) {
              this.gaugeValue -= 1
            }
          })
        }
      }.width('100%')
    }.width('100%').margin({ top: 5 })
  }
}

🍀 7. LoadingProgressConfiguration

🦋 接口:
declare interface LoadingProgressConfiguration extends CommonConfiguration<LoadingProgressConfiguration> {
    // LoadingProgress动画是否显示。默认值:true
    enableLoading: boolean;
}
🌰 例子:

该示例通过contentModifier接口,实现了定制内容区的功能,并通过enableLoading接口实现了通过按钮切换是否显示LoadingProgress的效果。

// xxx.ets
import { promptAction } from '@kit.ArkUI'

class MyLoadingProgressStyle implements ContentModifier<LoadingProgressConfiguration> {
  /** MARK: 成员变量 */
  enableLoading: boolean = false

  /** MARK: 构造器 */
  constructor(enableLoading: boolean) {
    this.enableLoading = enableLoading
  }

  /** MARK: 自定义内容 */
  applyContent(): WrappedBuilder<[LoadingProgressConfiguration]> {
    return wrapBuilder(buildLoadingProgress)
  }
}

let arr1: string[] =
  ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"]
let arr2: string[] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]

@Builder
function buildLoadingProgress(config: LoadingProgressConfiguration) {
  Column({ space: 8 }) {
    Row() {
      Column() {
        Circle({
          width: ((config.contentModifier as MyLoadingProgressStyle).enableLoading) ? 100 : 80,
          height: ((config.contentModifier as MyLoadingProgressStyle).enableLoading) ? 100 : 80
        })
          .fill(((config.contentModifier as MyLoadingProgressStyle).enableLoading) ? Color.Grey : 0x2577e3)
      }.width('50%')

      Column() {
        Button('' + ((config.contentModifier as MyLoadingProgressStyle).enableLoading))
          .onClick((event: ClickEvent) => {
            promptAction.showToast({
              message: ((config.contentModifier as MyLoadingProgressStyle).enableLoading) + ''
            })
          })
          .fontColor(Color.White)
          .backgroundColor(((config.contentModifier as MyLoadingProgressStyle).enableLoading) ? Color.Grey : 0x2577e3)
      }.width('50%')
    }

    Row() {
      Column() {
        Gauge({
          value: (config.contentModifier as MyLoadingProgressStyle).enableLoading ? 50 : 30, min: 11, max: 100
        }) {
          Column() {
            Text('60')
              .maxFontSize("180sp")
              .minFontSize("160.0vp")
              .fontWeight(FontWeight.Medium)
              .fontColor("#ff182431")
              .width('40%')
              .height('30%')
              .textAlign(TextAlign.Center)
              .margin({ top: '22.2%' })
              .textOverflow({ overflow: TextOverflow.Ellipsis })
              .maxLines(1)
          }.width('100%').height('100%')
        }
        .colors(((config.contentModifier as MyLoadingProgressStyle).enableLoading) ? Color.Grey : 0x2577e3)
        .width(200)
        .strokeWidth(18)
        .padding(5)
        .trackShadow({ radius: 7, offsetX: 7, offsetY: 7 })
        .height(200)
      }.width('100%')
    }

    Column() {
      List({ space: 20, initialIndex: 0 }) {
        ForEach(arr2, (item: string) => {
          ListItem() {
            Text((config.contentModifier as MyLoadingProgressStyle).enableLoading ? '' + item : Number(item) * 2 + '')
              .width('100%')
              .height('100%')
              .fontColor((config.contentModifier as MyLoadingProgressStyle).enableLoading ? Color.White : Color.Orange)
              .fontSize((config.contentModifier as MyLoadingProgressStyle).enableLoading ? 16 : 20)
              .textAlign(TextAlign.Center)
              .backgroundColor((config.contentModifier as MyLoadingProgressStyle).enableLoading ? Color.Grey : 0x2577e3)
          }
          .height(110)
          .border({
            width: 2,
            color: Color.White
          })
        }, (item: string) => item)
      }
      .height(200)
      .width('100%')
      .friction(0.6)
      .lanes({
        minLength: (config.contentModifier as MyLoadingProgressStyle).enableLoading ? 40 : 80,
        maxLength: (config.contentModifier as MyLoadingProgressStyle).enableLoading ? 40 : 80
      })
      .scrollBar(BarState.Off)
    }
  }.width("100%").padding(10)
}

@Entry
@Component
struct LoadingProgressDemoExample {
  /** MARK: 成员变量 */
  @State loadingProgressList: (boolean | undefined | null)[] = [undefined, true, null, false]
  @State widthList: (number | string)[] = ['110%', 220, '40%', 80]
  @State loadingProgressIndex: number = 0
  @State clickFlag: number = 0
  private scroller: Scroller = new Scroller()

  /** MARK: 构建函数 */
  build() {
    Column() {
      /** 滚动视图 */
      Scroll(this.scroller) {
        Column({ space: 5 }) {
          Column() {
            LoadingProgress()
              .color('#106836')
              .size({ width: '100%' })
              .contentModifier(new MyLoadingProgressStyle(this.loadingProgressList[this.loadingProgressIndex]))
          }.width('100%').backgroundColor(0xdcdcdc)
        }.width('100%').margin({ top: 5 })
      }.height('85%')

      /** 按钮 */
      Button('点击切换config.enableloading').onClick(this.onClickEvent).margin(20)
    }
  }

  /** MARK: 成员函数 */
  private onClickEvent = () => {
    this.clickFlag++
    this.loadingProgressIndex = (this.loadingProgressIndex + 1) % this.loadingProgressList.length
    console.log('enableLoading:' + this.loadingProgressList[this.loadingProgressIndex])
  }
}

🍀 8. RadioConfiguration

🦋 接口:
declare interface RadioConfiguration extends CommonConfiguration<RadioConfiguration> {
    // 当前单选框的值。
    value: string;
    // 设置单选框的选中状态。 默认值:false
    checked: boolean;
    // 触发单选框选中状态变化。
    triggerChange: Callback<boolean>;
}
🌰 例子:

该示例通过contentModifier实现自定义单选框样式。

class MyRadioStyle implements ContentModifier<RadioConfiguration> {
  /** MARK: 成员变量 */
  type: number = 0
  selectedColor: ResourceColor = Color.Black

  /** MARK: 构造器 */
  constructor(numberType: number, colorType: ResourceColor) {
    this.type = numberType
    this.selectedColor = colorType
  }

  /** MARK: 自定义内容 */
  applyContent(): WrappedBuilder<[RadioConfiguration]> {
    return wrapBuilder(buildRadio)
  }
}

/** 构建函数 */
@Builder
function buildRadio(config: RadioConfiguration) {
  Row({ space: 30 }) {
    Circle({ width: 50, height: 50 })
      .stroke(Color.Black)
      .fill(config.checked ? (config.contentModifier as MyRadioStyle).selectedColor : Color.White)
    Button(config.checked ? "off" : "on")
      .width(100)
      .type(config.checked ? (config.contentModifier as MyRadioStyle).type : ButtonType.Normal)
      .backgroundColor('#2787D9')
      .onClick(() => {
        if (config.checked) {
          config.triggerChange(false)
        } else {
          config.triggerChange(true)
        }
      })
  }
}

@Entry
@Component
struct refreshExample {
  build() {
    Column({ space: 50 }) {
      /** 样式一: */
      Row() {
        Radio({ value: 'Radio1', group: 'radioGroup' })
          .contentModifier(new MyRadioStyle(1, '#004AAF'))
          .checked(false)
          .width(300)
          .height(100)
      }

      /** 样式二: */
      Row() {
        Radio({ value: 'Radio2', group: 'radioGroup' })
          .checked(true)
          .width(300)
          .height(60)
          .contentModifier(new MyRadioStyle(2, '#004AAF'))
      }
    }
  }
}

🍀 9. ProgressConfiguration

🦋 接口:
declare interface ProgressConfiguration extends CommonConfiguration<ProgressConfiguration> {
    //当前进度值。    
    value: number;
   // 进度总长。
    total: number;
}
🌰 例子:

该示例通过contentModifier接口,实现了自定义进度条的功能,自定义实现星形,其中总进度为3,且当前值可通过按钮进行增减,达到的进度被填充自定义颜色。

// xxx.ets
class MyProgressModifier implements ContentModifier<ProgressConfiguration> {
  /** MARK: 成员变量 */
  color: Color = Color.White

  /** MARK: 构造器 */
  constructor(color: Color) {
    this.color = color
  }

  /** MARK: 自定义内容 */
  applyContent(): WrappedBuilder<[ProgressConfiguration]> {
    return wrapBuilder(myProgress)
  }
}

@Builder
function myProgress(config: ProgressConfiguration) {
  Column({ space: 30 }) {
    Text("当前进度:" + config.value + "/" + config.total).fontSize(20)
    Row() {
      Flex({ justifyContent: FlexAlign.SpaceBetween }) {
        Path()
          .width('30%')
          .height('30%')
          .commands('M108 0 L141 70 L218 78.3 L162 131 L175 205 L108 170 L41.2 205 L55 131 L1 78 L75 68 L108 0 Z')
          .fill(config.enabled && config.value >= 1 ? (config.contentModifier as MyProgressModifier).color :
          Color.White)
          .stroke(Color.Black)
          .strokeWidth(3)
        Path()
          .width('30%')
          .height('30%')
          .commands('M108 0 L141 70 L218 78.3 L162 131 L175 205 L108 170 L41.2 205 L55 131 L1 78 L75 68 L108 0 Z')
          .fill(config.enabled && config.value >= 2 ? (config.contentModifier as MyProgressModifier).color :
          Color.White)
          .stroke(Color.Black)
          .strokeWidth(3)
        Path()
          .width('30%')
          .height('30%')
          .commands('M108 0 L141 70 L218 78.3 L162 131 L175 205 L108 170 L41.2 205 L55 131 L1 78 L75 68 L108 0 Z')
          .fill(config.enabled && config.value >= 3 ? (config.contentModifier as MyProgressModifier).color :
          Color.White)
          .stroke(Color.Black)
          .strokeWidth(3)
      }.width('100%')
    }
  }.margin({ bottom: 100 })
}

@Entry
@Component
struct Index {
  /** MARK: 成员变量 */
  @State currentValue: number = 0
  @State myModifier: (MyProgressModifier | undefined) = this.modifier
  private modifier = new MyProgressModifier(Color.Red)

  /** MARK: 构造函数 */
  build() {
    Column() {
      /** 进度条 */
      Progress({ value: this.currentValue, total: 3, type: ProgressType.Ring }).contentModifier(this.modifier)
      /** 操作按钮 */
      Column() {
        Button('Progress++').onClick(() => {
          if (this.currentValue < 3) {
            this.currentValue += 1
          }
        }).width('30%')
        Button('addProgress--').onClick(() => {
          if (this.currentValue > 0) {
            this.currentValue -= 1
          }
        }).width('30%')
      }
    }.width('100%').height('100%')
  }
}

🍀 10. RatingConfiguration

🦋 接口:
declare interface RatingConfiguration extends CommonConfiguration<RatingConfiguration> {
    // 评分条当前评分数。默认值:0
    rating: number;
    //评分条是否作为一个指示器。默认值:false
    indicator: boolean;
    //评分条的星级总数。默认值:5
    stars: number;
    //评分条的评分步长。默认值:0.5
    stepSize: number;
    //触发评分数量变化。
    triggerChange: Callback<number>;
}
🌰 例子:

该示例实现了自定义评分条的功能,每个圆圈表示0.5分。ratingIndicator为true时表示评分条作为一个指示器不可改变评分;

为false时可以进行评分。ratingStars可改变评分总数。ratingStepsize可改变评分步长。

// xxx.ets
class MyRatingStyle implements ContentModifier<RatingConfiguration> {
  /** MARK: 成员变量 */
  name: string = ""
  style: number = 0

  /** MARK: 构造器 */
  constructor(value1: string, value2: number) {
    this.name = value1
    this.style = value2
  }

  /** MARK: 自定义内容 */
  applyContent(): WrappedBuilder<[RatingConfiguration]> {
    return wrapBuilder(buildRating)
  }
}

/** 构造函数 */
@Builder
function buildRating(config: RatingConfiguration) {
  Column() {
    Row() {
      Circle({ width: 25, height: 25 })
        .fill(config.rating >= 0.4 ? Color.Black : Color.Red)
        .onClick((event: ClickEvent) => {
          if (!config.indicator) {
            if (config.stepSize = 0.5) {
              config.triggerChange(0.5);
              return
            }
            if (config.stepSize = 1) {
              config.triggerChange(1);
              return
            }
          }
        }).visibility(config.stars >= 1 ? Visibility.Visible : Visibility.Hidden)
      Circle({ width: 25, height: 25 })
        .fill(config.rating >= 0.9 ? Color.Black : Color.Red)
        .onClick((event: ClickEvent) => {
          if (!config.indicator) {
            config.triggerChange(1);
          }
        }).visibility(config.stars >= 1 ? Visibility.Visible : Visibility.Hidden)
      Circle({ width: 25, height: 25 })
        .fill(config.rating >= 1.4 ? Color.Black : Color.Red)
        .onClick((event: ClickEvent) => {
          if (!config.indicator) {
            if (config.stepSize = 0.5) {
              config.triggerChange(1.5);
              return
            }
            if (config.stepSize = 1) {
              config.triggerChange(2);
              return
            }
          }
        }).visibility(config.stars >= 2 ? Visibility.Visible : Visibility.Hidden).margin({ left: 10 })
      Circle({ width: 25, height: 25 })
        .fill(config.rating >= 1.9 ? Color.Black : Color.Red)
        .onClick((event: ClickEvent) => {
          if (!config.indicator) {
            config.triggerChange(2);
          }
        }).visibility(config.stars >= 2 ? Visibility.Visible : Visibility.Hidden)
      Circle({ width: 25, height: 25 })
        .fill(config.rating >= 2.4 ? Color.Black : Color.Red)
        .onClick((event: ClickEvent) => {
          if (!config.indicator) {
            if (config.stepSize = 0.5) {
              config.triggerChange(2.5);
              return
            }
            if (config.stepSize = 1) {
              config.triggerChange(3);
              return
            }
          }
        }).visibility(config.stars >= 3 ? Visibility.Visible : Visibility.Hidden).margin({ left: 10 })
      Circle({ width: 25, height: 25 })
        .fill(config.rating >= 2.9 ? Color.Black : Color.Red)
        .onClick((event: ClickEvent) => {
          if (!config.indicator) {
            config.triggerChange(3);
          }
        }).visibility(config.stars >= 3 ? Visibility.Visible : Visibility.Hidden)
      Circle({ width: 25, height: 25 })
        .fill(config.rating >= 3.4 ? Color.Black : Color.Red)
        .onClick((event: ClickEvent) => {
          if (!config.indicator) {
            if (config.stepSize = 0.5) {
              config.triggerChange(3.5);
              return
            }
            if (config.stepSize = 1) {
              config.triggerChange(4);
              return
            }
          }
        }).visibility(config.stars >= 4 ? Visibility.Visible : Visibility.Hidden).margin({ left: 10 })
      Circle({ width: 25, height: 25 })
        .fill(config.rating >= 3.9 ? Color.Black : Color.Red)
        .onClick((event: ClickEvent) => {
          if (!config.indicator) {
            config.triggerChange(4);
          }
        }).visibility(config.stars >= 4 ? Visibility.Visible : Visibility.Hidden)
      Circle({ width: 25, height: 25 })
        .fill(config.rating >= 4.4 ? Color.Black : Color.Red)
        .onClick((event: ClickEvent) => {
          if (!config.indicator) {
            if (config.stepSize = 0.5) {
              config.triggerChange(4.5);
              return
            }
            if (config.stepSize = 1) {
              config.triggerChange(5);
              return
            }
          }
        }).visibility(config.stars >= 5 ? Visibility.Visible : Visibility.Hidden).margin({ left: 10 })
      Circle({ width: 25, height: 25 })
        .fill(config.rating >= 4.9 ? Color.Black : Color.Red)
        .onClick((event: ClickEvent) => {
          if (!config.indicator) {
            config.triggerChange(5);
          }
        }).visibility(config.stars >= 5 ? Visibility.Visible : Visibility.Hidden)
    }
    Text("分值:" + config.rating)
  }
}

@Entry
@Component
struct ratingExample {
  /** MARK: 成员变量 */
  @State rating: number = 0;
  @State ratingIndicator: boolean = true;
  @State ratingStars: number = 0;
  @State ratingStepsize: number = 0.5;
  @State ratingEnabled: boolean = true;

  /** MARK: 构造函数 */
  build() {
    Row() {
      Column() {
        Rating({
          rating: 0,
          indicator: this.ratingIndicator
        })
          .stepSize(this.ratingStepsize)
          .stars(this.ratingStars)
          .backgroundColor(Color.Transparent)
          .width('100%')
          .height(50)
          .onChange((value: number) => {
            console.info('Rating change is' + value);
            this.rating = value
          })
          .contentModifier(new MyRatingStyle("hello", 3))

        Column({ space: 20 }) {
          /** 按钮一 */
          Button(this.ratingIndicator ? "ratingIndicator : true" : "ratingIndicator : false")
            .onClick((event) => {
              if (this.ratingIndicator) {
                this.ratingIndicator = false
              } else {
                this.ratingIndicator = true
              }
            }).margin({ top: 5 })
          /** 按钮二 */
          Button(this.ratingStars < 5 ? "ratingStars + 1, ratingStars =" + this.ratingStars : "ratingStars最大值为5")
            .onClick((event) => {
              if (this.ratingStars < 5) {
                this.ratingStars += 1
              }
            })
          /** 按钮三 */
          Button(this.ratingStars > 0 ? "ratingStars - 1, ratingStars =" + this.ratingStars :
            "ratingStars小于等于0时默认等于5")
            .onClick((event) => {
              if (this.ratingStars > 0) {
                this.ratingStars -= 1
              }
            })
          /** 按钮四 */
          Button(this.ratingStepsize == 0.5 ? "ratingStepsize : 0.5" : "ratingStepsize : 1")
            .onClick((event) => {
              if (this.ratingStepsize == 0.5) {
                this.ratingStepsize = 1
              } else {
                this.ratingStepsize = 0.5
              }
            })
        }
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
    }
    .height('100%')
  }
}

🍀 11. SliderConfiguration

🦋 接口:
declare interface SliderConfiguration extends CommonConfiguration<SliderConfiguration> {
    //当前进度值
    value: number;
    //最小值。
    min: number;
    //最大值。
    max: number;
    //Slider滑动步长.
    step: number;
    //触发Slider变化。
    triggerChange: SliderTriggerChangeCallback;
}
🌰 例子:
/** ! 构造函数 */
@Builder
function buildSlider(config: SliderConfiguration) {
  Row() {
    Column({ space: 30 }) {
      Progress({ value: config.value, total: config.max, type: ProgressType.Ring })
        .margin({ top: 20 })

      Button('增加')
        .onClick(() => {
          config.value = config.value + config.step
          config.triggerChange(config.value, SliderChangeMode.Click)
        })
        .width(100)
        .height(25)
        .fontSize(10)
        .enabled(config.value < config.max)

      Button('减少')
        .onClick(() => {
          config.value = config.value - config.step
          config.triggerChange(config.value, SliderChangeMode.Click)
        })
        .width(100)
        .height(25)
        .fontSize(10)
        .enabled(config.value > config.min)

      Slider({
        value: config.value,
        min: config.min,
        max: config.max,
        step: config.step,
      })
        .width(config.max)
        .visibility((config.contentModifier as MySliderStyle).showSlider ? Visibility.Visible : Visibility.Hidden)
        .showSteps(true)
        .onChange((value: number, mode: SliderChangeMode) => {
          config.triggerChange(value, mode)
        })
      Text('当前状态:' + ((config.contentModifier as MySliderStyle).sliderChangeMode == 0 ? "Begin"
        : ((config.contentModifier as MySliderStyle).sliderChangeMode == 1 ? "Moving"
          : ((config.contentModifier as MySliderStyle).sliderChangeMode == 2 ? "End"
            : ((config.contentModifier as MySliderStyle).sliderChangeMode == 3 ? "Click" : "")))))
        .fontSize(10)
      Text('进度值:' + config.value)
        .fontSize(10)
      Text('最小值:' + config.min)
        .fontSize(10)
      Text('最大值:' + config.max)
        .fontSize(10)
      Text('步长:' + config.step)
        .fontSize(10)
    }
    .width('80%')
  }
  .width('100%')
}

class MySliderStyle implements ContentModifier<SliderConfiguration> {
  /** MARK: 成员变量 */
  showSlider: boolean = true
  sliderChangeMode: number = 0

  /** MARK: 构造函数 */
  constructor(showSlider: boolean, sliderChangeMode: number) {
    this.showSlider = showSlider
    this.sliderChangeMode = sliderChangeMode
  }

  /** MARK: 自定义内容 */
  applyContent(): WrappedBuilder<[SliderConfiguration]> {
    return wrapBuilder(buildSlider)
  }
}

@Entry
@Component
struct SliderExample {
  /** MARK: 成员变量 */
  @State showSlider: boolean = true
  @State sliderValue: number = 0
  @State sliderMin: number = 10
  @State sliderMax: number = 100
  @State sliderStep: number = 20
  @State sliderChangeMode: number = 0

  /** MARK: 构建函数 */
  build() {
    Column({ space: 8 }) {
      Row() {
        Slider({
          value: this.sliderValue,
          min: this.sliderMin,
          max: this.sliderMax,
          step: this.sliderStep,
        })
          .showSteps(true)
          .onChange((value: number, mode: SliderChangeMode) => {
            this.sliderValue = value
            this.sliderChangeMode = mode
            console.info('【SliderLog】value:' + value + 'mode:' + mode.toString())
          })
          .contentModifier(new MySliderStyle(this.showSlider, this.sliderChangeMode))
      }
      .width('100%')
    }.width('100%')
  }
}

posted on 2025-03-09 00:32  梁飞宇  阅读(57)  评论(0)    收藏  举报