前言
之前负责的一个需求,让在RN端做仿微信朋友圈的图片删除和排序,由于经验和时间限制,就跟PM协商改为点击删除,由此欠下一个技术栈,今天是来还债的。本文基于transform实现,而非定位。
初始时
将数据源转化为如下格式
const dataArray = props.dataArray.map((item, index)=>{
const newData = {}
const translateX = (index % COLUMN_NUM) * this.itemW;
const translateY = parseInt((index/COLUMN_NUM)) * this.itemW;
newData.data = item; // 原始内容
newData.originIndex = index; // 初始下标
newData.originX = translateX; // 当前项基于父试图的相对x值
newData.originY = translateY; // 当前项基于父试图的相对y值
// 当前项的偏移量,初始时都是按照正常的布局来,都不需要偏移,故都为0
newData.transAnimated = new Animated.ValueXY({
x: 0,
y: 0,
});
return newData
});
this.setState({ dataArray });
渲染时
新数据源属性做如下分配
{this.state.dataArray.map((item, index) => (
<Draggable
ref={ref => this.refDrag.set(index, ref)} // 收集每项的ref引用
pan={this.state.dataArray[index].transAnimated} // 创建的动画给到每一项
key={`${item.data}`}
destination={{ // 删除区域
x: 0,
y: ScreenH-40,
}}
onMoveStart={() => this.handleMoveStart(item.originIndex)} // 此处不要使用map的index,因为排序后数组的数组没有变
onMoveEnd={this.handleMoveEnd}
onMove={this.handleMove}
onArriveDestination={() => this.handleArriveDestination(item.data)}
>
{this.renderItem(item.data)}
</Draggable>
))}
开始拖拽
将如下属性赋值给this.touchCurItem
handleMoveStart = (move) => {
this.touchCurItem = {
ref: this.refDrag.get(move), // 获取当前拖拽的item引用
index: move, // 当前拖拽item的下标
moveToIndex: 0, // 将要拖拽到的位置,初始为0即可
}
拖拽中
不断计算所有item(除拖拽项)将要到达的位置,然后用动画移动
handleMove = (e, gestureState) => {
if (!this.touchCurItem) return;
// 偏移量
let translateX = gestureState.dx;
let translateY = gestureState.dy;
// 让拖拽项跟着手指移动
this.state.dataArray[this.touchCurItem.index].transAnimated.setOffset({
x: translateX,
y: translateY
});
let moveXNum = translateX/this.itemW
let moveYNum = translateY/this.itemH;
// 向四个方向移动一半位置就触发排序
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)
}
let moveToIndex = this.touchCurItem.index + moveXNum + moveYNum * COLUMN_NUM;
// 边缘线制
if (moveToIndex > this.state.dataArray.length-1) {
moveToIndex = this.state.dataArray.length-1
} else if (moveToIndex < 0) {
moveToIndex = 0;
}
if (this.touchCurItem.moveToIndex == moveToIndex ) return;
this.touchCurItem.moveToIndex = moveToIndex
this.state.dataArray.forEach((item,index)=> {
let nextItem = null
if (index > this.touchCurItem.index && index <= moveToIndex) { // 向右拖拽
nextItem = this.state.dataArray[index-1]
} else if (index >= moveToIndex && index < this.touchCurItem.index) { // 向左拖拽
nextItem = this.state.dataArray[index+1]
} else if (index != this.touchCurItem.index &&
(item.transAnimated.x._value != item.originX || item.transAnimated.y._value != item.originY)) { // 处理不松开时左右滑动
nextItem = this.state.dataArray[index]
}
if (!nextItem) return;
Animated.timing(
item.transAnimated,
{
// 还记得在初始时的构造的数据源么,curItem和nextItem偏移量的差值就是当前想要到达的位置
toValue: {x: parseInt(nextItem.originX+0.5 - item.originX),y: parseInt(nextItem.originY+0.5 - item.originY)},
duration: 300,
easing: Easing.out(Easing.quad),
useNativeDriver: false,
}
).start()
})
}
拖拽结束
handleMoveEnd = () => {
const curIndex = this.touchCurItem.index;
const moveToIndex = this.touchCurItem.moveToIndex;
const curItem = this.state.dataArray[this.touchCurItem.index];
const moveToItem = this.state.dataArray[moveToIndex];
let translateX = 0;
let translateY = 0;
// 判断移动是否达到移动标准,若是:动画方式返回原位置,否:动画偏移到新位置
if (curIndex != moveToIndex) {
translateX = parseInt(moveToItem.originX - curItem.originX+0.5);
translateY = parseInt(moveToItem.originY - curItem.originY+0.5);
}
Animated.timing(
curItem.transAnimated,
{
toValue: {x: translateX,y: translateY},
duration: 300,
easing: Easing.out(Easing.quad),
useNativeDriver: false,
}
).start()
this.touchCurItem = null;
this.changePosition(curIndex, moveToIndex);
}
// 每次排序结束,按照新的排序整理dataArray
changePosition(startIndex,endIndex) {
let isCommon = true; // 向右边移
if (startIndex > endIndex) { // 向左移
isCommon = false
let tempIndex = startIndex
startIndex = endIndex
endIndex = tempIndex
}
const newDataArray = [...this.state.dataArray].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.dataArray[newIndex]}
newItem.originX = item.originX
newItem.originY = item.originY
newItem.originIndex = item.originIndex;
newItem.transAnimated = new Animated.ValueXY({ // 每次排序过后都将偏移量给清空,准备下次排序
x: 0,
y: 0,
})
item = newItem
}
return item
})
this.setState({
dataArray: newDataArray
});
}
拖拽删除
这个比较简单了,通过通知的方式来传送消息,没啥可说的,有兴趣直接看源码吧