HarmonyOS 鸿蒙Next 双向可滑动控件选择区间方法

发布于 1周前 作者 phonegap100 来自 鸿蒙OS

HarmonyOS 鸿蒙Next 双向可滑动控件选择区间方法

找了一圈api只有找到Slider这个 请问有没有解决办法大佬们求助cke_356.png

9 回复
弄了个差不多的,仅供参考:

import { MeasureText } from '[@kit](/user/kit).ArkUI'

[@Component](/user/Component)
export struct DualSliders {
/**
* 圆形滑块的大小
*/
circleSize = 25
paddingLeftRight = 15
/**
* 整个组件的宽度
*/
componentWidth = 0
/**
* 滑轨的总宽度
*/
sliderWidth = 0
/**
* 左边圆形滑块的X轴偏移量
*/
[@State](/user/State) leftOffsetX: number = 0
/**
* 右边圆形滑块的X轴偏移量
*/
[@State](/user/State) rightOffsetX: number = 0
/**
* 左边圆形滑块的最终位置
*/
[@State](/user/State) leftPositionX: number = 0
/**
* 右边圆形滑块的最终位置
*/
[@State](/user/State) rightPositionX: number = 0
/**
* 左边进度条的宽度
*/
[@State](/user/State) leftLineWidth: number = 0
/**
* 中间进度条的宽度
*/
[@State](/user/State) middleLineWidth: number = 0
/**
* 右边进度条的宽度
*/
[@State](/user/State) rightLineWidth: number = 0
/**
* 刻度0的位置
*/
[@State](/user/State) positionX0: number = 0
/**
* 刻度30的位置
*/
[@State](/user/State) positionX30: number = 0
/**
* 刻度60的位置
*/
[@State](/user/State) positionX60: number = 0
/**
* 刻度90的位置
*/
[@State](/user/State) positionX90: number = 0
/**
* 刻度100的位置
*/
[@State](/user/State) positionX100: number = 0
/**
* 左边圆形滑块滑动时展示的进度值
*/
[@Link](/user/Link) leftValue: string
/**
* 右边圆形滑块滑动时展示的进度值
*/
[@Link](/user/Link) rightValue: string
/**
* 左边圆形滑块是否在拖拽
*/
[@State](/user/State) isLeftCircleDragging: boolean = false
/**
* 右边圆形滑块是否在拖拽
*/
[@State](/user/State) isRightCircleDragging: boolean = false
/**
* 左边进度值文本的X轴偏移量
*/
[@State](/user/State) leftValueOffsetX: number = 0
/**
* 右边进度值文本的X轴偏移量
*/
[@State](/user/State) rightValueOffsetX: number = 0
/**
* 滑块滑动时进度值文本的宽度
*/
progressTextWidth = 40
leftDefValue = 30
rightDefValue = 60

initComponents() {
this.sliderWidth = this.componentWidth - 2 * this.paddingLeftRight
let leftValue = parseInt(this.leftValue)
let rightValue = parseInt(this.rightValue)
let minValue = this.circleSize / this.sliderWidth * 100
let isSetInvalid =
Number.isNaN(leftValue) || Number.isNaN(rightValue) || leftValue < 0 || rightValue > 100 ||
rightValue < minValue || leftValue + minValue > rightValue
if (isSetInvalid) {
// 无效设置,使用默认值
leftValue = this.leftDefValue
rightValue = this.rightDefValue
this.leftValue = `${this.leftDefValue}`
this.rightValue = `${this.rightDefValue}`
}
this.leftOffsetX = leftValue / 100 * this.sliderWidth - this.circleSize / 2
this.leftPositionX = this.leftOffsetX
this.rightOffsetX = rightValue / 100 * this.sliderWidth - this.circleSize / 2
this.rightPositionX = this.rightOffsetX

this.leftValueOffsetX = this.leftOffsetX + this.circleSize / 2 - this.progressTextWidth / 2
this.rightValueOffsetX = this.rightOffsetX + this.circleSize / 2 - this.progressTextWidth / 2

this.leftLineWidth = this.leftPositionX + this.circleSize / 2
this.middleLineWidth = this.rightPositionX - this.leftPositionX
this.rightLineWidth = this.sliderWidth - this.rightOffsetX - this.circleSize / 2

let width0 = px2vp(MeasureText.measureText({ textContent: '0' }))
let width30 = px2vp(MeasureText.measureText({ textContent: '30' }))
let width60 = px2vp(MeasureText.measureText({ textContent: '60' }))
let width90 = px2vp(MeasureText.measureText({ textContent: '90' }))
let width100 = px2vp(MeasureText.measureText({ textContent: '100' }))
this.positionX0 = this.paddingLeftRight - width0 / 2
this.positionX30 = this.paddingLeftRight + this.sliderWidth * 0.3 - width30 / 2
this.positionX60 = this.paddingLeftRight + this.sliderWidth * 0.6 - width60 / 2
this.positionX90 = this.paddingLeftRight + this.sliderWidth * 0.9 - width90 / 2
this.positionX100 = this.paddingLeftRight + this.sliderWidth - width100 / 2
}

build() {
RelativeContainer() {
Row() {
Text('0').fontColor(Color.Grey).position({ x: this.positionX0 })
Text('30').fontColor(Color.Green).position({ x: this.positionX30 })
Text('60').fontColor(Color.Grey).position({ x: this.positionX60 })
Text('90').fontColor(Color.Grey).position({ x: this.positionX90 })
Text('100').fontColor(Color.Grey).position({ x: this.positionX100 })
}.id('numbers')

Row() {
Divider().width(this.leftLineWidth).backgroundColor(Color.Grey).height(3)
Divider().width(this.middleLineWidth).backgroundColor(Color.Blue).height(3)
Divider().width(this.rightLineWidth).backgroundColor(Color.Grey).height(3)
}.id('line')
.alignRules({ center: { anchor: 'left_circle', align: VerticalAlign.Center } })

Circle()
.width(this.circleSize)
.height(this.circleSize)
.fill(Color.White)
.stroke(Color.Gray)
.id('left_circle')
.alignRules({ top: { anchor: 'numbers', align: VerticalAlign.Bottom } })
.margin({ top: 20 })
.translate({ x: this.leftOffsetX })
.gesture(PanGesture({ direction: PanDirection.Horizontal })
.onActionStart(_ => this.isLeftCircleDragging = true)
.onActionUpdate(event => {
if (event) {
let offsetX = this.leftPositionX + event.offsetX
if (offsetX < -this.circleSize / 2) {
this.leftOffsetX = -this.circleSize / 2
} else if (offsetX > this.rightPositionX - this.circleSize) {
this.leftOffsetX = this.rightPositionX - this.circleSize
} else {
this.leftOffsetX = offsetX
}
this.leftLineWidth = this.leftOffsetX + this.circleSize / 2
this.middleLineWidth = this.rightPositionX - this.leftOffsetX
this.leftValue = (this.leftLineWidth / this.sliderWidth * 100).toFixed()
this.leftValueOffsetX = this.leftOffsetX + this.circleSize / 2 - this.progressTextWidth / 2
}
}).onActionEnd(() => {
this.leftPositionX = this.leftOffsetX
this.isLeftCircleDragging = false
}))

Circle()
.width(this.circleSize)
.height(this.circleSize)
.stroke(Color.Gray)
.fill(Color.White)
.id('right_circle')
.alignRules({ top: { anchor: 'left_circle', align: VerticalAlign.Top } })
.translate({ x: this.rightOffsetX })
.gesture(PanGesture({ direction: PanDirection.Horizontal })
.onActionStart(_ => this.isRightCircleDragging = true)
.onActionUpdate(event => {
if (event) {
let offsetX = this.rightPositionX + event.offsetX
if (offsetX < this.leftOffsetX + this.circleSize) {
this.rightOffsetX = this.leftPositionX + this.circleSize
} else if (offsetX > this.sliderWidth - this.circleSize / 2) {
this.rightOffsetX = this.sliderWidth - this.circleSize / 2
} else {
this.rightOffsetX = offsetX
}
this.middleLineWidth = this.rightOffsetX - this.leftPositionX
this.rightLineWidth = this.sliderWidth - this.rightOffsetX - this.circleSize / 2
this.rightValue = ((this.leftLineWidth + this.middleLineWidth) / this.sliderWidth * 100).toFixed()
this.rightValueOffsetX = this.rightOffsetX + this.circleSize / 2 - this.progressTextWidth / 2
}
}).onActionEnd(() => {
this.rightPositionX = this.rightOffsetX
this.isRightCircleDragging = false
}))

Text(this.leftValue)
.id('left_value')
.backgroundColor(Color.Orange)
.translate({ x: this.leftValueOffsetX })
.width(this.progressTextWidth)
.textAlign(TextAlign.Center)
.alignRules({ bottom: { anchor: 'left_circle', align: VerticalAlign.Top } })
.visibility(this.isLeftCircleDragging ? Visibility.Visible : Visibility.Hidden)
.margin({ bottom: 2 })

Text(this.rightValue)
.backgroundColor(Color.Orange)
.id('right_value')
.width(this.progressTextWidth)
.textAlign(TextAlign.Center)
.translate({ x: this.rightValueOffsetX })
.alignRules({ bottom: { anchor: 'right_circle', align: VerticalAlign.Top } })
.visibility(this.isRightCircleDragging ? Visibility.Visible : Visibility.Hidden)
.margin({ bottom: 2 })
}
.width('100%')
.padding({ left: this.paddingLeftRight, right: this.paddingLeftRight })
.height('auto')
.onAreaChange((_, newArea) => {
this.componentWidth = newArea.width as number
this.initComponents()
})
}
}

可以这么用:

[@Entry](/user/Entry)
[@Component](/user/Component)
struct Index {
[@State](/user/State) leftValue: string = '20'
[@State](/user/State) rightValue: string = '50'

build() {
Column() {
DualSliders({ leftValue: $leftValue, rightValue: $rightValue })
}
.width('100%')
.height('100%')
}
}
希望能满足你的要求,效果大致如下:

Snipaste_2024-08-08_16-05-10.png

import { MeasureText } from '[@kit](/user/kit).ArkUI'

[@Component](/user/Component)
export struct DualSliders {
/**
* 左边圆形滑块的X轴偏移量
*/
[@State](/user/State) leftOffsetX: number = 0
/**
* 右边圆形滑块的X轴偏移量
*/
[@State](/user/State) rightOffsetX: number = 0
/**
* 左边圆形滑块的最终位置
*/
[@State](/user/State) leftPositionX: number = 0
/**
* 右边圆形滑块的最终位置
*/
[@State](/user/State) rightPositionX: number = 0
/**
* 左边进度条的宽度
*/
[@State](/user/State) leftLineWidth: number = 0
/**
* 中间进度条的宽度
*/
[@State](/user/State) middleLineWidth: number = 0
/**
* 右边进度条的宽度
*/
[@State](/user/State) rightLineWidth: number = 0
/**
* 上方刻度值文本的位置
*/
[@State](/user/State) scaleValuesPosition: number[] = []
/**
* 左边圆形滑块滑动时展示的进度值
*/
[@Link](/user/Link) leftValue: number
/**
* 右边圆形滑块滑动时展示的进度值
*/
[@Link](/user/Link) rightValue: number
/**
* 左边圆形滑块是否在拖拽
*/
[@State](/user/State) isLeftCircleDragging: boolean = false
/**
* 右边圆形滑块是否在拖拽
*/
[@State](/user/State) isRightCircleDragging: boolean = false
/**
* 左边进度值文本的X轴偏移量
*/
[@State](/user/State) leftValueOffsetX: number = 0
/**
* 右边进度值文本的X轴偏移量
*/
[@State](/user/State) rightValueOffsetX: number = 0
/**
* 滑轨两边未选中颜色
*/
sliderNormalColor: ResourceColor = Color.Grey
/**
* 滑轨中间选中颜色
*/
sliderSelectColor: ResourceColor = Color.Blue
/**
* 滑轨高度
*/
sliderHeight: Length = 3
/**
* 圆形滑块的大小
*/
circleSize = 25
/**
* 左右padding
*/
paddingLeftRight = 15
/**
* 滑块滑动时进度值文本的宽度
*/
progressTextWidth = 40
/**
* 需要显示的刻度值文本数组,从小到大依次排列
*/
scaleValues: number[] = [0, 30, 60, 90, 100]
maxValue: number = 100
minValue: number = 0
range = this.maxValue - this.minValue
/**
* 组件大小初次确定时才进行初始化
*/
isInitFinish = false
/**
* 上方刻度显示的粒度,调用的是浮点数的toFixed(),整数值
*/
fractionDigits = 0
/**
* 整个组件的宽度
*/
componentWidth = 0
/**
* 滑轨的总宽度
*/
sliderWidth = 0
/**
* 上方刻度值文本颜色
*/
scaleTextColors: ResourceColor[] = [Color.Grey, Color.Grey, Color.Grey, Color.Grey, Color.Grey]

useDefaultValues() {
this.leftValue = 30
this.rightValue = 60
this.maxValue = 100
this.minValue = 0
this.range = this.maxValue - this.minValue
this.scaleValues = [0, 30, 60, 90, 100]
this.fractionDigits = 0
this.scaleTextColors = [Color.Grey, Color.Grey, Color.Grey, Color.Grey, Color.Grey]
}

initComponents() {
if (this.isInitFinish) {
return
}
this.isInitFinish = true
this.sliderWidth = this.componentWidth - 2 * this.paddingLeftRight
this.range = this.maxValue - this.minValue
let minRange = this.circleSize / this.sliderWidth * this.range
let isSetInvalid = this.leftValue < this.minValue || this.rightValue > this.maxValue ||
this.rightValue < minRange || this.leftValue + minRange > this.rightValue
if (isSetInvalid) {
// 无效设置,使用默认值
this.useDefaultValues()
}
this.leftOffsetX = (this.leftValue - this.minValue) / this.range * this.sliderWidth - this.circleSize / 2
this.leftPositionX = this.leftOffsetX
this.rightOffsetX = (this.rightValue - this.minValue) / this.range * this.sliderWidth - this.circleSize / 2
this.rightPositionX = this.rightOffsetX

this.leftValueOffsetX = this.leftOffsetX + this.circleSize / 2 - this.progressTextWidth / 2
this.rightValueOffsetX = this.rightOffsetX + this.circleSize / 2 - this.progressTextWidth / 2

this.leftLineWidth = this.leftPositionX + this.circleSize / 2
this.middleLineWidth = this.rightPositionX - this.leftPositionX
this.rightLineWidth = this.sliderWidth - this.rightOffsetX - this.circleSize / 2

this.scaleValues.forEach(scale => {
let width = px2vp(MeasureText.measureText({ textContent: `${scale}` }))
let position = this.paddingLeftRight + this.sliderWidth * (scale - this.minValue) / this.range - width / 2
this.scaleValuesPosition.push(position)
})
}

build() {
RelativeContainer() {
Row() {
ForEach(this.scaleValuesPosition, (item: number, index: number) => {
Text(`${this.scaleValues[index]}`).fontColor(this.scaleTextColors[index]).position({ x: item })
})
}.id('numbers')

Row() {
Divider().width(this.leftLineWidth).backgroundColor(this.sliderNormalColor).height(this.sliderHeight)
Divider().width(this.middleLineWidth).backgroundColor(this.sliderSelectColor).height(this.sliderHeight)
Divider().width(this.rightLineWidth).backgroundColor(this.sliderNormalColor).height(this.sliderHeight)
}.id('line')
.alignRules({ center: { anchor: 'left_circle', align: VerticalAlign.Center } })

Circle()
.width(this.circleSize)
.height(this.circleSize)
.fill(Color.White)
.stroke(Color.Gray)
.id('left_circle')
.alignRules({ top: { anchor: 'numbers', align: VerticalAlign.Bottom } })
.margin({ top: 20 })
.translate({ x: this.leftOffsetX })
.gesture(PanGesture({ direction: PanDirection.Horizontal })
.onActionStart(_ => this.isLeftCircleDragging = true)
.onActionUpdate(event => {
if (event) {
let offsetX = this.leftPositionX + event.offsetX
if (offsetX < -this.circleSize / 2) {
this.leftOffsetX = -this.circleSize / 2
} else if (offsetX > this.rightPositionX - this.circleSize) {
this.leftOffsetX = this.rightPositionX - this.circleSize
} else {
this.leftOffsetX = offsetX
}
this.leftLineWidth = this.leftOffsetX + this.circleSize / 2
this.middleLineWidth = this.rightPositionX - this.leftOffsetX
this.leftValue = this.leftLineWidth / this.sliderWidth * this.range + this.minValue
this.leftValueOffsetX = this.leftOffsetX + this.circleSize / 2 - this.progressTextWidth / 2
}
}).onActionEnd(() => {
this.leftPositionX = this.leftOffsetX
this.isLeftCircleDragging = false
}))

Circle()
.width(this.circleSize)
.height(this.circleSize)
.stroke(Color.Gray)
.fill(Color.White)
.id('right_circle')
.alignRules({ top: { anchor: 'left_circle', align: VerticalAlign.Top } })
.translate({ x: this.rightOffsetX })
.gesture(PanGesture({ direction: PanDirection.Horizontal })
.onActionStart(_ => this.isRightCircleDragging = true)
.onActionUpdate(event => {
if (event) {
let offsetX = this.rightPositionX + event.offsetX
if (offsetX < this.leftOffsetX + this.circleSize) {
this.rightOffsetX = this.leftPositionX + this.circleSize
} else if (offsetX > this.sliderWidth - this.circleSize / 2) {
this.rightOffsetX = this.sliderWidth - this.circleSize / 2
} else {
this.rightOffsetX = offsetX
}
this.middleLineWidth = this.rightOffsetX - this.leftPositionX
this.rightLineWidth = this.sliderWidth - this.rightOffsetX - this.circleSize / 2
this.rightValue =
(this.leftLineWidth + this.middleLineWidth) / this.sliderWidth * this.range + this.minValue
this.rightValueOffsetX = this.rightOffsetX + this.circleSize / 2 - this.progressTextWidth / 2
}
}).onActionEnd(() => {
this.rightPositionX = this.rightOffsetX
this.isRightCircleDragging = false
}))

Text(this.leftValue.toFixed(this.fractionDigits))
.id('left_value')
.backgroundColor(Color.Orange)
.translate({ x: this.leftValueOffsetX })
.width(this.progressTextWidth)
.textAlign(TextAlign.Center)
.alignRules({ bottom: { anchor: 'left_circle', align: VerticalAlign.Top } })
.visibility(this.isLeftCircleDragging ? Visibility.Visible : Visibility.Hidden)
.margin({ bottom: 2 })

Text(this.rightValue.toFixed(this.fractionDigits))
.backgroundColor(Color.Orange)
.id('right_value')
.width(this.progressTextWidth)
.textAlign(TextAlign.Center)
.translate({ x: this.rightValueOffsetX })
.alignRules({ bottom: { anchor: 'right_circle', align: VerticalAlign.Top } })
.visibility(this.isRightCircleDragging ? Visibility.Visible : Visibility.Hidden)
.margin({ bottom: 2 })
}
.width('100%')
.padding({ left: this.paddingLeftRight, right: this.paddingLeftRight })
.height('auto')
.onAreaChange((_, newArea) => {
this.componentWidth = newArea.width as number
this.initComponents()
})
}
}

使用方式:

[@Entry](/user/Entry)
[@Component](/user/Component)
struct Index {
[@State](/user/State) leftValue: number = 40
[@State](/user/State) rightValue: number = 70
[@State](/user/State) leftValue0: number = 2
[@State](/user/State) rightValue0: number = 5
[@State](/user/State) leftValue1: number = 0.3
[@State](/user/State) rightValue1: number = 0.6
[@State](/user/State) leftValue2: number = 18
[@State](/user/State) rightValue2: number = 50
[@State](/user/State) leftValue3: number = 18
[@State](/user/State) rightValue3: number = 50

build() {
Column({ space: 15 }) {
DualSliders({
leftValue: this.leftValue,
rightValue: this.rightValue
})

DualSliders({
leftValue: this.leftValue0,
rightValue: this.rightValue0,
scaleValues: [0, 2, 4, 6, 8, 10],
scaleTextColors: [Color.Grey, Color.Orange, Color.Brown, Color.Blue, Color.Red],
sliderNormalColor: Color.Pink,
sliderSelectColor: Color.Red,
minValue: 0,
maxValue: 10,
fractionDigits: 1
})

DualSliders({
leftValue: this.leftValue1,
rightValue: this.rightValue1,
scaleValues: [0, 0.2, 0.4, 0.6, 0.8, 1],
minValue: 0,
maxValue: 1,
fractionDigits: 2
})

DualSliders({
leftValue: this.leftValue2,
rightValue: this.rightValue2,
scaleValues: [18, 50],
minValue: 18,
maxValue: 50,
fractionDigits: 0
})

DualSliders({
leftValue: this.leftValue3,
rightValue: this.rightValue3,
scaleValues: [18, 50],
minValue: 10,
maxValue: 80,
fractionDigits: 0
})
}
.width('100%')
.height('100%')
.padding({ top: 50 })
}
}

大佬 可是设置每步走多少吗? 类似于slider的step

 0 0 我好像没见过这样的组件

自己写个组件好了 0 0 

在HarmonyOS(鸿蒙)中,实现双向可滑动控件以选择区间,你可以使用Slider组件的变体或自定义组件来达成。原生的Slider组件主要支持单向滑动,但你可以通过组合两个Slider(一个用于最小值,一个用于最大值),并同步它们的值来模拟双向选择区间。同时,监听它们的滑动事件来确保一个的值不会小于另一个。

实现时,注意处理边界条件和用户交互的流畅性。如果问题依旧没法解决请加我微信,我的微信是itying888。

回到顶部