#HarmonyOS NEXT 体验官# 长按组件颜色刷新 原创
需求描述
实现一个工具,可以帮助我们实现根据触摸时间来动态更新当前的颜色
原理讲解
ARGB
ARGB颜色字符串是一种用于表示颜色的编码方式,它结合了RGB色彩模式与Alpha(透明度)通道。
ARGB颜色字符串由四个部分组成,分别代表Alpha(透明度)、Red(红色)、Green(绿色)、Blue(蓝色)四个通道的值。每个通道的值通常用十六进制数表示,范围从00到FF(十六进制),对应于十进制中的0到255。因此,一个完整的ARGB颜色字符串看起来像这样:#AARRGGBB,其中AA是Alpha值,RR是红色值,GG是绿色值,BB是蓝色值。
渐变
在渐变中,A、R、G、B四个颜色通道的值会沿着渐变方向逐渐变化。例如,从红色(R:255, G:0, B:0)渐变到蓝色(R:0, G:0, B:255),则红色通道的值会逐渐减小,而蓝色通道的值会逐渐增加,绿色通道的值可能保持不变(取决于中间是否有绿色过渡)。
实现思路
- 定义起始和结束颜色:确定用户在未长按时的颜色(起始颜色)和长按时想要达到的颜色(结束颜色),长按到达结束颜色的时间。
- 颜色解析:将起始颜色 和 结束颜色相应的ARGB值进行重新解码,得到每个通道单独的ARGB值。
- 颜色线性插值计算:根据时间的变化,在两个指定的颜色之间平滑过渡。它首先计算了从某个起始时间到当前时间的差值,并将这个差值限制在一个指定的时间间隔内。然后,它使用线性插值方法来计算当前时间下应该显示的颜色的各个颜色通道(红、绿、蓝、透明度)的值。这些值可以用来在图形界面上动态地改变元素的颜色。
- 颜色编码:将拿到的十进制ARGB值重新编码成十六进制。
- 触摸事件:抓取触摸事件的按下和抬起事件,将记录手指触摸组件的时间点和手指离开组件的时间点。用来给颜色计算。并且定时刷新颜色
实现源码
1.定义起始和结束颜色:
// 开始颜色
startColor = "#ffff0000"
// 结束颜色
endColor = "#ff00ff00"
// 最大长按时间
timeInterval = 4000;
constructor();
constructor(startColor: string, endColor: string, timeInterval: number);
constructor(startColor?: string, endColor?: string, timeInterval?: number) {
if (startColor && endColor && timeInterval) {
this.startColor = startColor;
this.endColor = endColor;
this.timeInterval = timeInterval;
}
}
2. 解析颜色
我们只需要将十六进制的ARGB的颜色通道进行切割,就可以得到每个独立的通道的十六进制值,下面是编写的十六进制切割转义函数。
/**
* 十六进制转数字
* @param hexString
* @returns
*/
hexToNumber = (color: string, l: number, r: number) => {
let hexString = color.substring(l, r)
let number = parseInt(hexString, 16);
return number;
}
3.颜色插值
根据时间的变化,在两个指定的颜色之间平滑过渡。它首先计算了从某个起始时间到当前时间的差值,并将这个差值限制在一个指定的时间间隔内。然后,它使用线性插值方法来计算当前时间下应该显示的颜色的各个颜色通道(红、绿、蓝、透明度)的值。
3.1 线性插值计算
/**
* 线性插值计算函数。
* 根据给定的时间(或任何连续变化的量)在两个已知点之间插值。
*
* @param a 当前的时间或值。
* @param al 起始点的时间或值。
* @param ar 终点的时间或值。
* @param bl 起始点的输出值。
* @param br 终点的输出值。
* @returns 返回在a处的插值结果。
*/
linearInterpolate(a: number, al: number, ar: number, bl: number, br: number) {
return bl + ((a - al) * (br - bl)) / (ar - al);
}
3.2 线性插值计算
3.2.1. 这段代码的原理是基于时间插值来计算从一个颜色(startColor)到另一个颜色(endColor)的过渡色。它使用了一个时间间隔(timeInterval)来控制颜色过渡的速度,以及当前时间差(time)来确定在过渡过程中的位置。此外,它还通过插值函数(this.linearInterpolate)和颜色处理函数(如this.hexToNumber)来实现具体的颜色计算。以下是详细的原理描述:
3.2.2. 计算当前时间差:
首先,计算从触摸按下(downTime)到当前时间(nowTime)的时间差(time)。
然后,检查这个时间差是否超过了设定的时间间隔(timeInterval)。如果超过了,就将时间差设置为时间间隔的值,以确保颜色过渡不会超出设定的时间范围。
3.2.3. 颜色通道插值:
对于每个通道,代码首先设置颜色偏移量(colorDrift),它用于从颜色字符串(如#RRGGBB)中定位到当前要处理的通道的开始位置。
使用this.hexToNumber函数将颜色字符串中指定位置的十六进制颜色值转换为十进制数值。这个函数需要起始和结束位置的索引,这里通过colorDrift和colorDrift + 2来指定(因为每个颜色通道占用两个十六进制字符)。
然后,使用this.linearInterpolate函数进行线性插值。这个函数接收四个参数:当前时间差(time)、插值范围的起始值(0)、插值范围的结束值(timeInterval)、以及要插值的起始颜色和结束颜色的十进制值。它返回在指定时间差下,从起始颜色到结束颜色的过渡颜色的十进制值。
最后,对于每个通道,都会得到一个过渡颜色的十进制值(A、R、G、B)。
// 当前时间
let time = nowTime - downTime;
// 如果超过了时间间距那么显示当前最终颜色
time = time < timeInterval ? time : timeInterval
// 计算透明的通道
let colorDrift = 1 //颜色偏移量(第0位是#所以不参与运算)
let A = this.linearInterpolate(time, 0, timeInterval,
this.hexToNumber(startColor, colorDrift, colorDrift + 2),
this.hexToNumber(endColor, colorDrift, colorDrift + 2)
)
// 计算红色通道
colorDrift += 2;
let R = this.linearInterpolate(time, 0, timeInterval,
this.hexToNumber(startColor, colorDrift, colorDrift + 2),
this.hexToNumber(endColor, colorDrift, colorDrift + 2)
)
// 计算绿色通道
colorDrift += 2;
let G = this.linearInterpolate(time, 0, timeInterval,
this.hexToNumber(startColor, colorDrift, colorDrift + 2),
this.hexToNumber(endColor, colorDrift, colorDrift + 2)
)
// 计算蓝色通道
colorDrift += 2;
let B = this.linearInterpolate(time, 0, timeInterval,
this.hexToNumber(startColor, colorDrift, colorDrift + 2),
this.hexToNumber(endColor, colorDrift, colorDrift + 2)
)
4. 颜色编码
颜色通道拼接原理
颜色代码格式:在Harmony开发中,颜色通常以十六进制代码的形式表示,格式为#AARRGGBB(对于ARGB颜色)。这里的RR、GG、BB和AA分别代表红色、绿色、蓝色和透明度通道的值,每个值都是一个两位的十六进制数。
拼接过程:为了生成这样的颜色代码,我们需要将每个颜色通道的值转换为十六进制字符串,并将它们按顺序拼接起来。首先,我们添加一个"#"作为前缀,然后依次拼接红色、绿色、蓝色(以及可能的透明度)通道的十六进制字符串。
函数调用:在上述代码中,numberToHex函数被用于将每个颜色通道的值转换为两位的十六进制字符串。这个函数接收颜色通道的值和期望的字符串长度(在这个场景下是2)作为参数,并返回转换后的字符串。然后,这些字符串被拼接起来以形成完整的颜色代码。
/**数字转十六进制*/
numberToHex = (num: number, len: number) => {
let hex = Math.round(num).toString(16);
while (hex.length < len)
hex = '0' + hex;
return hex.toUpperCase(); // 如果需要小写,去掉.toUpperCase()
}
// 进行颜色通道拼接
let ansColor = "#";
ansColor += this.numberToHex(A, 2);
ansColor += this.numberToHex(R, 2);
ansColor += this.numberToHex(G, 2);
ansColor += this.numberToHex(B, 2);
5.触摸事件
5.1. 时间追踪:
使用两个属性(downTime 和 nowTime)来追踪触摸事件的时间。downTime 记录触摸按下的时间,而 nowTime 用于记录当前时间或最后一次更新颜色的时间。
当触摸按下时(TouchType.Down),downTime 和 nowTime 被设置为当前时间。
当触摸抬起时(TouchType.Up),downTime 和 nowTime 被重置为0,表示触摸事件已结束。
5.2. 定时器使用:
在触摸按下时,设置一个定时器(通过 setInterval),该定时器以固定的频率(大约每秒61次)执行一个匿名函数。
这个匿名函数负责更新 nowTime 为当前时间,并调用 colorEvent 回调函数来报告基于 downTime 和 nowTime 计算出的新颜色。getColor 函数它使用这两个时间戳来计算并返回一个颜色值。
定时器的ID被存储在 intervalID 数组中,以便稍后可以清除它。
5.3. 定时器管理:
当触摸抬起时,遍历 intervalID 数组并使用 clearInterval 函数来清除每个定时器,以停止进一步的颜色更新。
清除所有定时器后,清空 intervalID 数组以避免内存泄漏。
颜色更新:
触摸按下时,通过定时器定期调用 colorEvent 来报告颜色的变化。
触摸抬起时,虽然 downTime 和 nowTime 被重置,但代码示例中在抬起之前或之后(取决于具体实现)仍然会调用一次 colorEvent 来报告一个最终颜色。
/**
* 定时器事件,做成数组可以防止内存泄漏
*/
private intervalID: number[] = []
/**
* 触摸事件
*/
onTouchEvent = (event: TouchEvent, colorEvent: (color: string) => void) => {
switch (event.type) {
case TouchType.Down: // 如果按下,那么更新按下时间
this.nowTime = this.downTime = new Date().valueOf();
this.intervalID.push(setInterval(() => {
// 每毫秒更新更新当前颜色
this.nowTime = new Date().valueOf();
colorEvent(this.getColor(this.downTime, this.nowTime))
}, 1000 / 61));
break;
case TouchType.Up: // 如果抬起,那么清除按下时间
this.nowTime = this.downTime = 0;
// 在清除定时器之前,更新一次颜色
colorEvent(this.getColor(this.downTime, this.nowTime))
// 删除时间跟新定时器
for (let index = 0; index < this.intervalID.length; index++) {
const element = this.intervalID[index];
clearInterval(element);
}
this.intervalID = [];
break;
}
colorEvent(this.getColor(this.downTime, this.nowTime))
console.log("anran0", "onTouchEvent", "nowTime=", this.nowTime, "downTime=", this.downTime)
}