前言
小程序中滑动选择器相关属性如下:
属性 | 类型 | 默认值 | 必填 | 说明 | 最低版本 |
---|---|---|---|---|---|
min | number | 否 | 最小值 | 1.0.0 | |
max | number | 100 | 否 | 最大值 | 1.0.0 |
step | number | 1 | 否 | 步长,取值必须大于 0,并且可被(max - min)整除 | 1.0.0 |
disabled | boolean | false | 否 | 是否禁用 | 1.0.0 |
value | number | 否 | 当前取值 | 1.0.0 | |
color | color | #e9e9e9 | 否 | 背景条的颜色(请使用 backgroundColor) | 1.0.0 |
selected-color | color | #1aad19 | 否 | 已选择的颜色(请使用 activeColor) | 1.0.0 |
activeColor | color | #1aad19 | 否 | 已选择的颜色 | 1.0.0 |
backgroundColor | color | #e9e9e9 | 否 | 背景条的颜色 | 1.0.0 |
block-size | number | 28 | 否 | 滑块的大小,取值范围为 12 - 28 | 1.9.0 |
block-color | color | #ffffff | 否 | 滑块的颜色 | 1.9.0 |
show-value | boolean | false | 否 | 是否显示当前 value | 1.0.0 |
bindchange | eventhandle | 否 | 完成一次拖动后触发的事件,event.detail = {value} | 1.0.0 | |
bindchanging | eventhandle | 否 | 拖动过程中触发的事件,event.detail = {value} | 1.7.0 |
一、slider滑动选择器
1.wxml
<view class="page__bd">
<view class="section section_gap">
<text class="section__title">设置step</text>
<view class="body-view">
<slider bindchange="slider2change" step="5" />
</view>
</view>
<view class="section section_gap">
<text class="section__title">显示当前value</text>
<view class="body-view">
<slider bindchange="slider3change" show-value />
</view>
</view>
<!-- 测试changing事件 -->
<view class="section section_gap">
<text class="section__title">设置最小/最大值</text>
<view class="body-view">
<slider bindchange="slider4change" bindchanging="onSliderChanging" min="50" max="200" show-value />
</view>
</view>
</view>
2.js
var pageData = {}
for (var i = 1; i < 5; ++i) {
(function (index) {
pageData[`slider${index}change`] = function (e) {
console.log(`slider${index}发生change事件,携带值为`, e.detail.value)
}
})(i);
}
Page(Object.assign({
data:{},
onSliderChanging(e){
console.log(e.type, e.detail.value);
},
}, pageData))
3.效果
二、自定义滑动选择器
1.组件的封装
index.js
Component({
properties: {
blockColor: {
type: String,
value: "#ffffff"
},
blockSize: {
type: Number,
value: 28
},
backgroundColor: {
type: String,
value: "#e9e9e9"
},
activeColor: {
type: String,
value: "#1aad19"
},
step: {
type: Number,
value: 1
},
min: {
type: Number,
value: 0
},
max: {
type: Number,
value: 100
},
value: {
type: Number,
value: 0
},
disabled: {
type: Boolean,
value: false
},
showValue: {
type: Boolean,
value: false
},
},
observers: {
'blockSize': function(blockSize) {
// 这个地方是规范blockSize的最大、最小值
if (blockSize > 28) {
this.setData({
blockSize: 28
})
} else if (blockSize < 12) {
this.setData({
blockSize: 12
})
}
},
'showValue': function(){
this.queryHeight() // 由于显示数字后,滑动区域变化,需要重新查询可滑动高度
console.log('showValue----',this.properties.showValue);
}
},
data: {
totalTop: null,
totalHeight: null,
currentValue: 0,
},
ready(){
// 适合在这里调用
this.queryHeight()
},
methods: {
// 设置当前值是多少,是加上最小值之后的当前值
setCurrent: function(e){
this.setData({
currentValue: e
})
},
queryHeight: function(){
wx.createSelectorQuery().in(this).select('.slider-container').boundingClientRect((res) => {
this.setData({
totalTop: res.top,
totalHeight: res.height
})
}).exec()
},
empty: function(){},
}
})
index.wxml
Component({
properties: {
blockColor: {
type: String,
value: "#ffffff"
},
blockSize: {
type: Number,
value: 28
},
backgroundColor: {
type: String,
value: "#e9e9e9"
},
activeColor: {
type: String,
value: "#1aad19"
},
step: {
type: Number,
value: 1
},
min: {
type: Number,
value: 0
},
max: {
type: Number,
value: 100
},
value: {
type: Number,
value: 0
},
disabled: {
type: Boolean,
value: false
},
showValue: {
type: Boolean,
value: false
},
},
observers: {
'blockSize': function(blockSize) {
// 这个地方是规范blockSize的最大、最小值
if (blockSize > 28) {
this.setData({
blockSize: 28
})
} else if (blockSize < 12) {
this.setData({
blockSize: 12
})
}
},
'showValue': function(){
this.queryHeight() // 由于显示数字后,滑动区域变化,需要重新查询可滑动高度
// 如果被设置了,相应的observer是自动触发的
// 因为这个组件在使用时,设置了showValue属性,所以queryHeight被调用了
// 如果没有设置,这个函数是不会被调用的
console.log('showValue----',this.properties.showValue);
}
},
data: {
totalTop: null,
totalHeight: null,
currentValue: 0,
},
ready(){
// 适合在这里调用
this.queryHeight()
},
methods: {
// 设置当前值是多少,是加上最小值之后的当前值
setCurrent: function(e){
this.setData({
currentValue: e
})
},
queryHeight: function(){
// 这个地方是为了取出滚动容器在页面中的起点,距离页面顶边的y距离是多少totalTop
// totalTop相当于startY
// 还有整个滑动容器的高度totalHeight,相当于sliderHeight
// 因在wxs模块中组件或页面描述对象,没有createSelectorQuery方法,所以这个查询只能放在逻辑层js中进行,好在这个查询并不需要重复执行
// 整个totalHeight,要被min,max的差,以step为粒度平分掉
wx.createSelectorQuery().in(this).select('.slider-container').boundingClientRect((res) => {
this.setData({
totalTop: res.top,
totalHeight: res.height
})
}).exec()
},
empty: function(){},
}
})
index.wxs
var notInt = function(num) {
return num !== parseInt(num)
}
/*
* state:共享临时数据对象
* state.max:最大值
* state.min:最小值
* state.offset:当前高度,即value - min的值(未按照step纠正的值)
* state.step:步长
* ins:页面或组件实例
*/
// 这个函数主要是为了计算,当value变化时,计算出上、下两部分灰、绿色各占百分多少
// 然后通过组件描述对象设置样式
// 这个方法会在第一次属性设置中、调用
var calculate = function(instance,state,changeCallback){
var max = state.max
var min = state.min
var offset = state.offset
var step = state.step
// 1、计算 offset 按照 step 算应该是几个。
// Math.round函数,是大于等于0.5算一步,否则不算
// Math.round(offset % step / step) 计算的是 offset 对 step 取模后剩下的长度四舍五入,就是多出来的部分是否该算一步
//
// Math.floor(offset / step) 计算的是 offset 中包含多少个完整的 step
var stepNum = Math.round(offset % step / step) + Math.floor(offset / step)
// 2、纠正后的当前高度
// 当前的步值数
var current = stepNum * step
// 3、当前高度所占比例,由于 offset 的大小已经在进方法前经过了修正,所以这里不需要再判断是否小于0或者大于100了
// 算出百分比
var percent = current * 100 / (max - min)
// var percent = (current * 100 / (max - min)).toFixed(2)
// 设置上灰色是多少高度,百分比
// value是从底部开始计算的,所以这里是100-percent
instance.selectComponent("#upper").setStyle({
height: (100 - percent) + "%"
})
// 设置选中的绿色部分百分比
// instance.selectComponent("#lower").setStyle({
// height: percent + "%"
// })
// 如果值有变化,调用回调函数
if(state.current !== current){
state.current = current
changeCallback(current+min)
}
}
module.exports = {
// 这是由wxsPropObserver机制调用的
propsChange: function(newValue, oldValue, ownerIns, ins) {
var state = ownerIns.getState()
var step = newValue.step;
var min = newValue.min;
var max = newValue.max;
// value是设置的当前值
var value = newValue.value;
if (notInt(step) || notInt(min) || notInt(max) || notInt(value)) {
console.log("你不把 step min max value 设成正整数,我没法做啊")
return
}
if (min > max) {
min = oldValue.min
max = oldValue.max
}
if (value > max) {
console.log("value的值比max大,将value强制设为max")
value = max
} else if (value < min) {
console.log("value的值比min小,将value强制设为min:"+min)
value = min
}
if (step <= 0 || (max - min) % step != 0) {
console.log("step只能是正整数且必须被(max-min)整除,否则将step强制设为1")
step = 1
}
state.min = min
state.max = max
state.step = step
state.offset = value - min
state.disabled = newValue.disabled
state.totalTop = newValue.totalTop
state.totalHeight = newValue.totalHeight
if (newValue.totalTop !== null && newValue.totalHeight !== null) {
calculate(ownerIns, state, function(currentValue){
ownerIns.callMethod("setCurrent", state.current + state.min)
})
}
},
// 这是由顶点、底点单击时调用的
// 直接在最大、最小值之间切换
// 因为在组件上设置了dataset属性,所以可以用一种函数
//
tapEndPoint: function(e, ins){
var state = ins.getState()
if (state.disabled) return
var percent = e.currentTarget.dataset.percent
state.offset = (state.max - state.min) * percent
calculate(ins, state, function (currentValue) {
ins.triggerEvent("change", {
value: currentValue
})
ins.callMethod("setCurrent", currentValue)
})
},
// 单击upper、lower两处或灰色、或绿色,都是调用这个函数
tap: function(e, ins) {
var state = ins.getState()
if (state.disabled) return
// (总高度+头部高度-点击点高度)/ 总高度 = 点击点在组件的位置
// 点击事件只在线条上,所以percent是不可能小于0,也不可能超过100%,无需另加判断
// 计算从滑块底部,到音击点之间的距离,再除为总高度,计算出百分比
var percent = (state.totalTop + state.totalHeight - e.changedTouches[0].pageY) / state.totalHeight
// 依据百分比,计算出偏移量数量,这个数量与上面计算出来的从滑块底部到音击点之间的距离,并不一定一致,因为单位是由调用者定义的
state.offset = (state.max - state.min) * percent
// 最后调用计算函数,完成后派发事件,设置逻辑层的当前值
calculate(ins, state, function(currentValue){
ins.callMethod("setCurrent", currentValue)
ins.triggerEvent("change", {
value: currentValue
})
})
},
// 滑块开始滑动时,记录当前坐标,及当前的current值
// 由于单击点,与当前滑块的高度,只能作一个绑定,并不能完全等同,因为每次单击的点并不太一样
start: function(e, ins) {
var state = ins.getState()
if (state.disabled) return
state.startPoint = e.changedTouches[0]
// 本次滑动之前的高度px = 当前高度value / (最大值-最小值) * 最大高度
var currentPx = state.current / (state.max - state.min) * state.totalHeight
state.currentPx = currentPx
},
// 滑块开始移动了
// getState是获取模块变量对象,可以在模块内共享利用,因为没有this,模块内并不方便在各个方法之间共享变量
move: function(e, ins) {
var state = ins.getState()
if (state.disabled) return
//
var startPoint = state.startPoint
var endPoint = e.changedTouches[0]
console.log('endPoint',JSON.stringify(endPoint));
// 当前的高度px = 滑动之前的高度px + 起始点高度 - 当前点高度
// 依据移动点,计算出偏移之后的当前值是多少
var currentPx = state.currentPx + startPoint.pageY - endPoint.pageY
// 计算出百分比
var percent = currentPx / state.totalHeight
// 由于可能滑出slider范围,所以要限制比例在 0-1之间
// 有可能会移动范围之处,值可能会超过0~1的范围
percent = percent>1?1:percent
percent = percent<0?0:percent
state.offset = (state.max - state.min) * percent
calculate(ins, state, function(currentValue){
ins.triggerEvent("changing", {
value: currentValue
})
ins.callMethod("setCurrent", currentValue)
})
},
// 滑块结束
end: function(e, ins) {
var state = ins.getState()
if (state.disabled) return
ins.triggerEvent("change", {
value: state.current + state.min
})
}
}
index.wxss
.slider {
height: 100%;
padding: 30rpx 0;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
}
.slider-container {
flex: 1;
margin: 0 20px;
width: 0;
display: flex;
flex-direction: column;
align-items: center;
}
.slider-upper {
flex-shrink: 0;
height: 100%;
}
/*
flex-shrink这个样式是决定flex元素的收缩规则的
并且,它不是决定元素的所占宽度比,而是决定了不足的空间,各个元素各分摊多少的缩小额度
*/
.slider-lower {
flex-shrink: 0;
height: auto;
flex:1;
}
.slider-upper-line {
height: 100%;
margin: 0 12px;
width: 2px;
}
.slider-lower-line {
height: 100%;
margin: 0 12px;
width: 2px;
}
.slider-middle {
flex-shrink: 1;
width: 0;
height: 0;
display: flex;
align-items: center;
justify-content: center;
}
.slider-block {
flex-shrink: 0;
width: 20rpx;
height: 20rpx;
border-radius: 50%;
position: relative;
z-index: 1;
}
.slider-value {
flex-shrink: 0;
pointer-events: none;
}
.slider-append {
flex-shrink: 0;
height: 10px;
padding: 0 20px;
}
2.组件的使用
{
"usingComponents": {
"slider-vertical": "../../components/vertical-slider/index"
},
}
<view class="section section_gap">
<text class="section__title">自定义竖向slider</text>
<view class="body-view">
<view style="height: 400rpx;margin: 20px;display: flex;justify-content: space-around">
<slider-vertical block-color="#ffffff" block-size="28" backgroundColor="#e9e9e9" activeColor="#1aad19" bindchange="slider1change" bindchanging="slider1changing" step="1" min="50" max="200" value="0" disabled="{{false}}" show-value="{{true}}"></slider-vertical>
<slider-vertical block-color="#ffffff" block-size="28" backgroundColor="#e9e9e9" activeColor="#1aad19" bindchange="slider1change" bindchanging="slider1changing" step="5" min="50" max="200" value="115" disabled="{{false}}" show-value="{{false}}"></slider-vertical>
</view>
</view>
</view>
slider1change: function (e) {
console.log("change:",e)
},
slider1changing: function (e) {
console.log("changing:",e)
}
效果