【鸿蒙开发】自定义顶部Tabs标签栏,支持字体大小和颜色渐变

478 阅读3分钟

引言

最近在学习鸿蒙开发,发现系统的Tabs不支持左右切换时标签文字的字体大小和颜色渐变,于是通过查阅相关资料及文章自己自定义了一个。先看下效果图:

实现

自定义指示器滑动和字体大小渐变是参考【OpenHarmony动效开发】实现Tabs翻页效果(二)这篇文章实现的,这里主要说下颜色渐变的实现。
在鸿蒙中颜色是一个混合类型ResourceColor,支持ColornumberstringResource,要实现两个颜色的渐变我们需要将传入的ResourceColor转成对应的rgb颜色,然后通过滑动的进度生成新的rgb颜色,再设置给文字标签。

首先我们定义rgb颜色的接口RgbaColor

interface RgbaColor {
  red: number,
  green: number,
  blue: number,
  alpha: number
}

接下来我们把传入的ResourceColor先转成string类型,经测试系统的Color也是string类型

// 根据resourceColor获取hex颜色值
static getHex(color: ResourceColor): string {
  let colorStr = '';
  if (typeof color === 'number') { // number类型
    colorStr = `#${(color as number).toString(16).padStart(6, '0')}`;
  } else if (typeof color === 'string') { // string类型或Color类型
    if ((color as string).startsWith('#')) {
      colorStr = color
    } else {
      colorStr = Utils.rgbaToHex(color)
    }
  } else { // Resource类型
    let newColor = color as Resource;
    colorStr = `#${getContext(Utils).resourceManager.getColorSync(newColor).toString(16).padStart(6, '')}`;
  }
  return colorStr
}

在鸿蒙中string类型的颜色支持两种格式#开头和rgb开头的,比如黑色可以用#000#000000#ff000000表示,或者用rgb(0, 0, 0)rgba(0, 0, 0, 1)表示,我们在判断string类型时如果是rgb表示的要先转成hex

// rgba转hex
static rgbaToHex(rgbaStr: string): string {
  // 匹配逗号分割的数字,join('.')是为了处理小数
  let rgbaArr = rgbaStr.split(',').map(item => item.match(/\d+/g)!.join('.'));
  // 最终的hex字符串
  let hexStr = '#';
  // 判断是否是正确的格式
  if (rgbaArr.length === 3 || rgbaArr.length === 4) {
    hexStr += rgbaArr.map((item, index) => {
      // 如果r、g、b任何一个是0,转成16进制后把0重复两遍
      let str: string = index < 3 ? Math.floor(Number(item)).toString(16) : Math.floor(Number(item) * 255).toString(16);
      return str.length === 1 ? str.repeat(2) : str;
    })
      .join('')
    return hexStr
  } else {
    // 格式不正确
    return ''
  }
}

接下来我们根据hex颜色获取对应的r、g、b、a值

// hex转rgbaColor
static hexToRgbaColor(hex: string): RgbaColor {
  // 初始化color
  let rgbaColor: RgbaColor = { red: 0, green: 0, blue: 0, alpha: 1 }
  // 去除#号
  let excludePrefix = hex.replace('#', '');
  // hex值的长度
  let hexLength = excludePrefix.length;
  // hex的格式 #bfa, #bbffaa,#ffbbffaa(经测试鸿蒙的8位alpha在前面,即ff)
  if (hex.startsWith('#') && (hexLength === 3 || hexLength === 6 || hexLength === 8)) {
    let hexArr: string[] = [];
    // 针对不同格式分别处理
    switch (hexLength) {
      case 3: // 3位
        excludePrefix.split('').forEach((item, index) => {
          let number = parseInt(item.repeat(2), 16);
          if (index === 0) {
            rgbaColor.red = number;
          } else if (index === 1) {
            rgbaColor.green = number;
          } else if (index === 2) {
            rgbaColor.blue = number;
          }
        })
        rgbaColor.alpha = 1;
        break;
      case 6: // 6位
        for (let i = 0; i < 3; i++) {
          hexArr.push(excludePrefix.slice(i * 2, i * 2 + 2));
        }
        hexArr.forEach((item, index) => {
          let number = parseInt(item, 16);
          if (index === 0) {
            rgbaColor.red = number;
          } else if (index === 1) {
            rgbaColor.green = number;
          } else if (index === 2) {
            rgbaColor.blue = number;
          }
        })
        rgbaColor.alpha = 1;
        break;
      case 8: // 8位
        for (let i = 0; i < 4; i++) {
          hexArr.push(excludePrefix.slice(i * 2, i * 2 + 2));
        }
        hexArr.forEach((item, index) => {
          let number = parseInt(item, 16);
          if (index === 0) {
            // alpha取值[0,1]所以做下处理
            rgbaColor.alpha = Number((number / 255).toFixed(2))
          } else if (index === 1) {
            rgbaColor.red = number
          } else if (index === 2) {
            rgbaColor.green = number
          } else if (index === 3) {
            rgbaColor.blue = number
          }
        })
        break;
    }
  }
  return rgbaColor
}

然后把传入的ResourceColor转成rgbaColor

// 根据resourceColor获取rgbaColor
static getRgbaColor(color: ResourceColor) {
  let hex = Utils.getHex(color);
  return Utils.hexToRgbaColor(hex);
}

接着我们通过传入的两个颜色和进度生成新的颜色

// 生成颜色
static interpolationColor(from: ResourceColor, to: ResourceColor, percent: number): string {
  let fromColor = Utils.getRgbaColor(from);
  let toColor = Utils.getRgbaColor(to);
  let red = Math.round(Utils.interpolation(fromColor.red, toColor.red, percent));
  let green = Math.round(Utils.interpolation(fromColor.green, toColor.green, percent));
  let blue = Math.round(Utils.interpolation(fromColor.blue, toColor.blue, percent));
  let alpha = Utils.interpolation(fromColor.alpha, toColor.alpha, percent).toFixed(2);
  return `rgba(${red}, ${green}, ${blue}, ${alpha})`
}

// 计算插值
static interpolation(from: number, to: number, percent: number): number {
  percent = Math.max(0, Math.min(1, percent));
  return from + (to - from) * percent;
}

然后定义方法,生成颜色并设置到对应的Text

// 根据状态是否选中,确定标签状态颜色
private getTextFontColor(index: number): ResourceColor {
  let normalColor = this.config.normalColor;
  let selectColor = this.config.selectColor;
  if (this.swiperState === SwiperState.DRAG) {
    if (this.swiperIndex === index) {
      return Utils.interpolationColor(normalColor, selectColor, 1 - this.swiperRatio);
    }
    if (this.nextIndex === index) {
      return Utils.interpolationColor(normalColor, selectColor, this.swiperRatio);
    }
  }
  return this.currentIndex === index ? selectColor : normalColor;
}


Text(item.title)
  .fontSize(this.getTextFontSize(index))
  .fontColor(this.getTextFontColor(index))
  .fontWeight(this.getTextFontWeight(index))

至此,自定义标签栏颜色渐变就完成了。

总结

本篇主要是通过学习【OpenHarmony动效开发】实现Tabs翻页效果(二)的自定义标签栏,在它的基础上实现了标签栏文字颜色渐变过渡效果。