react 控件怎么适应手机_一步步实现一个自适应的react-native拖拽排序

2019.6 新增顶部固定功能,可以设置开始连续几个为不可拖动功能,类似今日头条一样,该功能和今日头条拖拽一样,可以去对比一下。

ezgif.com-resize.gif

2019.3: 新增单行拖拽演示,其实这个功能一致,这个拖拽插件本来就是自适应行,有时间会整体优化下ScrollView问题,使控件自带ScrollView功能。

one-line.gif

2019.2: 优化拖拽不移动时自动恢复,现在这个插件应该没有任何问题。新加一个实战演示例子,后面有时间会对这个例子进行加动画,删除时item向下到待选的item动画,和待选到item。还有滑动时自动向下滑动动画。

demo.gif

最近由于业务需求需要实现一个功能需要实现图片的上传和排序和删除,在网上搜索了几款发现都需要固定列数,感觉不太友好,所以自己实现了一个可以不需要设定列数的排序,而且布局高度实现自适应。

效果图对比(固定列数和自适应流布局)

[图片上传中...(iphone.jpg-9f7224-1533711885416-0)]

iphone.jpg

动态图

olddemo.gif

实现

其实拖拽排序在大多数编程语言里已经有很多中三方插件可以使用,实现方法都差不多,而且例如Android和iOS或者现在的React-Native他们逻辑几乎是可以共用,你会写一个语言的拖拽排序,其他的都差不多。

梳理一下步骤

开始触发: 长按或触摸到达一定时间时触发开始排序,这时可以进行把被单机的item放大、透明、抖动动画。

开始滑动:

(1) 被拖拽的item随着手指的滑动而滑动

(2) 被拖动的item滑动到第x个时,item到x之间的item进行左滑右滑一个位置的动画。

松开手指:

(1) 被拖拽的这个item通过四舍五入进入相应的位置。

(2) 数据进行替换并重绘加布局矫正。

tip: 滑动逻辑,例如当你把index=1拖到index=3,不是将1和3替换(0,3,2,1,4),而是(0,3,1,2,4)这才是拖拽后结果,只将被拖拽的一个替换到要去的位置,其他的向前和向后移动

主要代码

// 触摸事件的监听

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,

})

//这里使用长按触发开发拖拽事件,其实开始是使用触摸一定时间后触发事件的,但和View的单机事件有冲突不好解决,所以选择了长按触发事件

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的数字就可以进行局部刷新了

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]

//有时前一个或者后一个数据有个动画差的原因无法回到正确位置,这里进行矫正

} 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

}

}

//刷新数据

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不绘制开头和结尾

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),

})

})

}

后续会加上添加和删除Item渐变动画

React-Native 篇

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值