2019.6 新增頂部固定功能,可以設(shè)置開始連續(xù)幾個為不可拖動功能惊橱,類似今日頭條一樣,該功能和今日頭條拖拽一樣箭昵,可以去對比一下李皇。
2019.3: 新增單行拖拽演示,其實這個功能一致宙枷,這個拖拽插件本來就是自適應(yīng)行,有時間會整體優(yōu)化下ScrollView問題掉房,使控件自帶ScrollView功能。
2019.2: 優(yōu)化拖拽不移動時自動恢復(fù)慰丛,現(xiàn)在這個插件應(yīng)該沒有任何問題卓囚。新加一個實戰(zhàn)演示例子,后面有時間會對這個例子進行加動畫诅病,刪除時item向下到待選的item動畫哪亿,和待選到item。還有滑動時自動向下滑動動畫贤笆。
最近由于業(yè)務(wù)需求需要實現(xiàn)一個功能需要實現(xiàn)圖片的上傳和排序和刪除蝇棉,在網(wǎng)上搜索了幾款發(fā)現(xiàn)都需要固定列數(shù),感覺不太友好芥永,所以自己實現(xiàn)了一個可以不需要設(shè)定列數(shù)的排序篡殷,而且布局高度實現(xiàn)自適應(yīng)。
源碼鏈接
效果圖對比(固定列數(shù)和自適應(yīng)流布局)
動態(tài)圖
實現(xiàn)
其實拖拽排序在大多數(shù)編程語言里已經(jīng)有很多中三方插件可以使用,實現(xiàn)方法都差不多埋涧,而且例如Android和iOS或者現(xiàn)在的React-Native他們邏輯幾乎是可以共用板辽,你會寫一個語言的拖拽排序,其他的都差不多棘催。
梳理一下步驟
- 開始觸發(fā): 長按或觸摸到達(dá)一定時間時觸發(fā)開始排序醇坝,這時可以進行把被單機的item放大、透明、抖動動畫。
-
- 開始滑動:
- (1) 被拖拽的item隨著手指的滑動而滑動
- (2) 被拖動的item滑動到第x個時,item到x之間的item進行左滑右滑一個位置的動畫嘁傀。
-
- 松開手指:
- (1) 被拖拽的這個item通過四舍五入進入相應(yīng)的位置细办。
- (2) 數(shù)據(jù)進行替換并重繪加布局矯正笑撞。
tip: 滑動邏輯茴肥,例如當(dāng)你把index=1拖到index=3,不是將1和3替換(0,3,2,1,4)础锐,而是(0,3,1,2,4)這才是拖拽后結(jié)果截粗,只將被拖拽的一個替換到要去的位置财破,其他的向前和向后移動
主要代碼
// 觸摸事件的監(jiān)聽
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => this.props.sortable,
onStartShouldSetPanResponderCapture: (evt, gestureState) => {
this.isMovePanResponder = false
return false
},
// 接管觸摸加滑動事件
onMoveShouldSetPanResponder: (evt, gestureState) => this.isMovePanResponder,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => this.isMovePanResponder,
onPanResponderGrant: (evt, gestureState) => {},
onPanResponderMove: (evt, gestureState) => this.moveTouch(evt,gestureState),
onPanResponderRelease: (evt, gestureState) => this.endTouch(evt),
onPanResponderTerminationRequest: (evt, gestureState) => false,
onShouldBlockNativeResponder: (evt, gestureState) => false,
})
//這里使用長按觸發(fā)開發(fā)拖拽事件俊性,其實開始是使用觸摸一定時間后觸發(fā)事件的趟薄,但和View的單機事件有沖突不好解決杭煎,所以選擇了長按觸發(fā)事件
startTouch(touchIndex) {
// 接管滑動
this.isMovePanResponder = true
//if (this.measureTimeOut) clearTimeout(this.measureTimeOut)
if (sortRefs.has(touchIndex)) {
if (this.props.onDragStart) {
this.props.onDragStart(touchIndex)
}
//變大和加透明
Animated.timing(
this.state.dataSource[touchIndex].scaleValue,
{
toValue: maxScale,
duration: scaleDuration,
}
).start(()=>{
// 備份被觸摸的事件
this.touchCurItem = {
ref: sortRefs.get(touchIndex),
index: touchIndex,
// 記錄之前的位置
originLeft: this.state.dataSource[touchIndex].originLeft,
originTop: this.state.dataSource[touchIndex].originTop,
moveToIndex: touchIndex,
}
})
}
}
//滑動
moveTouch (nativeEvent,gestureState) {
if (this.touchCurItem) {
let dx = gestureState.dx
let dy = gestureState.dy
const rowNum = parseInt(this.props.parentWidth/this.itemWidth);
const maxWidth = this.props.parentWidth-this.itemWidth
const maxHeight = this.itemHeight*Math.ceil(this.state.dataSource.length/rowNum) - this.itemHeight
//出界后取最大或最小值防止出界
if (this.touchCurItem.originLeft + dx < 0) {
dx = -this.touchCurItem.originLeft
} else if (this.touchCurItem.originLeft + dx > maxWidth) {
dx = maxWidth - this.touchCurItem.originLeft
}
if (this.touchCurItem.originTop + dy < 0) {
dy = -this.touchCurItem.originTop
} else if (this.touchCurItem.originTop + dy > maxHeight) {
dy = maxHeight - this.touchCurItem.originTop
}
let left = this.touchCurItem.originLeft + dx
let top = this.touchCurItem.originTop + dy
//置于最上層
this.touchCurItem.ref.setNativeProps({
style: {
zIndex: touchZIndex,
}
})
//滑動時刷新布局,這里直接刷新Animated的數(shù)字就可以進行局部刷新了
this.state.dataSource[this.touchCurItem.index].position.setValue({
x: left,
y: top,
})
let moveToIndex = 0
let moveXNum = dx/this.itemWidth
let moveYNum = dy/this.itemHeight
if (moveXNum > 0) {
moveXNum = parseInt(moveXNum+0.5)
} else if (moveXNum < 0) {
moveXNum = parseInt(moveXNum-0.5)
}
if (moveYNum > 0) {
moveYNum = parseInt(moveYNum+0.5)
} else if (moveYNum < 0) {
moveYNum = parseInt(moveYNum-0.5)
}
moveToIndex = this.touchCurItem.index+moveXNum+moveYNum*rowNum
if (moveToIndex > this.state.dataSource.length-1) moveToIndex = this.state.dataSource.length-1
// 其他item向左和向右滑動
if (this.touchCurItem.moveToIndex != moveToIndex ) {
this.touchCurItem.moveToIndex = moveToIndex
this.state.dataSource.forEach((item,index)=>{
let nextItem = null
if (index > this.touchCurItem.index && index <= moveToIndex) {
nextItem = this.state.dataSource[index-1]
} else if (index >= moveToIndex && index < this.touchCurItem.index) {
nextItem = this.state.dataSource[index+1]
} else if (index != this.touchCurItem.index &&
(item.position.x._value != item.originLeft ||
item.position.y._value != item.originTop)) {
nextItem = this.state.dataSource[index]
//有時前一個或者后一個數(shù)據(jù)有個動畫差的原因無法回到正確位置,這里進行矯正
} else if ((this.touchCurItem.index-moveToIndex > 0 && moveToIndex == index+1) ||
(this.touchCurItem.index-moveToIndex < 0 && moveToIndex == index-1)) {
nextItem = this.state.dataSource[index]
}
//需要滑動的就進行滑動動畫
if (nextItem != null) {
Animated.timing(
item.position,
{
toValue: {x: parseInt(nextItem.originLeft+0.5),y: parseInt(nextItem.originTop+0.5)},
duration: slideDuration,
easing: Easing.out(Easing.quad),
}
).start()
}
})
}
}
}
//觸摸事件
endTouch (nativeEvent) {
//clear
if (this.measureTimeOut) clearTimeout(this.measureTimeOut)
if (this.touchCurItem) {
if (this.props.onDragEnd) {
this.props.onDragEnd(this.touchCurItem.index,this.touchCurItem.moveToIndex)
}
//this.state.dataSource[this.touchCurItem.index].scaleValue.setValue(1)
Animated.timing(
this.state.dataSource[this.touchCurItem.index].scaleValue,
{
toValue: 1,
duration: scaleDuration,
}
).start()
this.touchCurItem.ref.setNativeProps({
style: {
zIndex: defaultZIndex,
}
})
this.changePosition(this.touchCurItem.index,this.touchCurItem.moveToIndex)
this.touchCurItem = null
}
}
//刷新數(shù)據(jù)
changePosition(startIndex,endIndex) {
if (startIndex == endIndex) {
const curItem = this.state.dataSource[startIndex]
this.state.dataSource[startIndex].position.setValue({
x: parseInt(curItem.originLeft+0.5),
y: parseInt(curItem.originTop+0.5),
})
return;
}
let isCommon = true
if (startIndex > endIndex) {
isCommon = false
let tempIndex = startIndex
startIndex = endIndex
endIndex = tempIndex
}
const newDataSource = [...this.state.dataSource].map((item,index)=>{
let newIndex = null
if (isCommon) {
if (endIndex > index && index >= startIndex) {
newIndex = index+1
} else if (endIndex == index) {
newIndex = startIndex
}
} else {
if (endIndex >= index && index > startIndex) {
newIndex = index-1
} else if (startIndex == index) {
newIndex = endIndex
}
}
if (newIndex != null) {
const newItem = {...this.state.dataSource[newIndex]}
newItem.originLeft = item.originLeft
newItem.originTop = item.originTop
newItem.position = new Animated.ValueXY({
x: parseInt(item.originLeft+0.5),
y: parseInt(item.originTop+0.5),
})
item = newItem
}
return item
})
this.setState({
dataSource: newDataSource
},()=>{
if (this.props.onDataChange) {
this.props.onDataChange(this.getOriginalData())
}
//防止RN不繪制開頭和結(jié)尾
const startItem = this.state.dataSource[startIndex]
this.state.dataSource[startIndex].position.setValue({
x: parseInt(startItem.originLeft+0.5),
y: parseInt(startItem.originTop+0.5),
})
const endItem = this.state.dataSource[endIndex]
this.state.dataSource[endIndex].position.setValue({
x: parseInt(endItem.originLeft+0.5),
y: parseInt(endItem.originTop+0.5),
})
})
}