一步步实现一个自适应的react-native拖拽排序

2019.2: 优化拖拽不移动时自动恢复,现在这个插件应该没有任何问题。新加一个实战演示例子,后面有时间会对这个例子进行加动画,删除时item向下到待选的item动画,和待选到item。还有滑动时自动向下滑动动画。最近由于业务需求需要实现一个功能需要实现图片的上传和排序和删除,在网上搜索了几款发现都需要固定列数,感觉不太友好,所以自己实现了一个可以不需要设定列数的排序,而且布局高度实现自适应。源码链接效果图对比(固定列数和自适应流布局)![[图片上传中…(iphone.jpg-9f7224-1533711885416-0)]](https://user-gold-cdn.xitu.io…动态图实现其实拖拽排序在大多数编程语言里已经有很多中三方插件可以使用,实现方法都差不多,而且例如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.itemHeightMath.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+moveYNumrowNum 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渐变动画源码链接 ...

March 18, 2019 · 3 min · jiezi

PHP 的 sort 排序模式 SORT_NUMERIC/SORT_STRING/SORT_NATURAL 详解

PHP 的排序函数提供了如下几种排序模式:SORT_REGULAR - compare items normally (don’t change types)SORT_NUMERIC - compare items numericallySORT_STRING - compare items as stringsSORT_LOCALE_STRING - compare items as strings, based on the current locale. It uses the locale, which can be changed using setlocale()SORT_NATURAL - compare items as strings using “natural ordering” like natsort()SORT_FLAG_CASE - can be combined (bitwise OR) with SORT_STRING or SORT_NATURAL to sort strings case-insensitivelySORT_REGULARSORT_REGULAR,默认排序模式,会依照数组成员的原数据类型进行排序:字符串类型使用 ASCII 码字符串排序,即按位依次比较 ASCII 码大小,如相同则比较下一位,比如 “2” > “12"数值类型使用数值排序,即 2 < 12虽然 PHP 的数组高度灵活,可以存储不同类型值,但如果参与排序,建议格式统一类型。否则数据会被划分成两块:字符串类型区/数值类型区。然后分别按各自的默认规则进行排序:<?php$arr = [12, 2, 31, 302, “c”, “a2”, “a12”, 1];sort($arr);print_r($arr);// 字符串按ASCII排序 a12 < a2 < c / 数值按自然排序Array( [0] => a12 [1] => a2 [2] => c ———– [3] => 1 [4] => 2 [5] => 12 [6] => 31 [7] => 302)可以看到,字符串类型(ASCII排序)和数值类型(数值排序)的元素被分开使用各自的默认规则排序,也可以说默认排序以:字符串<数值作为升序。SORT_STRING & SORT_LOCALE_STRINGSORT_STRING 会尝试将元素都当做字符串数据进行排序处理,而且这种模式是安全的,因为数值类型转字符串类型不会有什么非法或截断的问题,然后按照 ASCII排序 进行处理。<?php$arr = [12, 2, 31, 302, “c”, “a2”, “a12”, 1];sort($arr, SORT_STRING);print_r($arr);//可以看到元素以ASCII位序进行排列 比如 “12” < “2” “302” < “31"Array( [0] => 1 [1] => 12 [2] => 2 [3] => 302 [4] => 31 [5] => a12 [6] => a2 [7] => c)SORT_NUMERICSORT_NUMERIC会将元素当做数值类型处理,在使用时需要注意元素的类型,比如示例中的 “a2”, “a12”, “c” 元素并不能安全的由字符串类型转为数值类型,所以排序会将他们放置在最前并不参与排序(原样奉还),“1"可以转为 1,则可以参与排序。<?php$arr = [12, 2, 31, 302, “c”, “a2”, “1”, “a12”];sort($arr, SORT_NUMERIC);print_r($arr);//无法转为数值类型的元素不会参与排序 会保持原先后顺序返回Array( [0] => c [1] => a2 [2] => a12 ———- [3] => 1 [4] => 2 [5] => 12 [6] => 31 [7] => 302)SORT_NATURALSORT_NATURAL看字面意思理解就是自然排序,特点在于对字符串+数值类型的数据也能以对人类友好的方式排序出来。比如待排序数列:“a2” “a12” “c1” “c12"对我们来说,最为容易理解的是先按照字母排序,字母相同的时就按后面的数值整体做排序,即: a2 < a12 < c1 < c12 这个样子先看一下默认的和其他几种排序模式会得到什么结果:SORT_REGULAR:会以 ASCII 字符串排序的方式处理数据 a12 < a2 < c12 < c2 不太自然SORT_STRING:同SORT_REGULAR,a12 < a2 < c12 < c2 不太自然SORT_NUMERIC:数据无法转为数值,无法做排序处理如果使用 SORT_NATURAL 模式呢?SORT_NATURAL便可以按照我们最容易理解的方式对数据进行排序:SORT_NATURAL 模式:a2 < a12 < c2 < c12,可以看到SORT_NUMERIC结合了SORT_STRING+SORT_NUMERIC处理机制大概如下:1、元素可以转为数值, 则做数值自然排序2、元素无法转为数值, 则对元素非数值部分做字符串排序,如果有数值部分则对数值部分做数值排序3、以数值<字符串的升序对数据做分区排列。<?php$arr = [12, 2, 31, 302, “c12”, “a2”, “1”, “a12”, “c1”];sort($arr, SORT_NATURAL);print_r($arr);// “1” 被转为了 1 进行排序// “c12”, “a2”, “a12”, “c1” 以对我们理解友好的方式排序Array( [0] => 1 [1] => 2 [2] => 12 [3] => 31 [4] => 302 ———— [5] => a2 [6] => a12 [7] => c1 [8] => c12)自然排序还有个有个函数natsort可以做保留索引的自然排序,类似 asort($arr, SORT_NATURAL)SORT_FLAG_CASESORT_FLAG_CASE主要配合 SORT_STRING 和 SORT_NATURAL对字符处理时是否忽略大小写,这个很容易理解。 ...

March 13, 2019 · 2 min · jiezi

JavaScript sort() 排序的坑详解

前言:做项目的时候发现使用sort排序后的代码,在android和ios平台解析的结果不一样。1、先从简单的开始,大家都知道sort()函数比较的是ASCII码的大小,而且而且而且:Array的sort()方法默认把所有元素先转换为String再排序,所以就有以下问题。// baiDu排在了最后:[‘Google’, ‘baiDu’, ‘Facebook’].sort(); // [‘Facebook’, ‘Google", ‘baiDu’]// 无法理解的结果:[10, 20, 1, 2].sort(); // [1, 10, 2, 20]结果转换成字符串比较,‘10’排在了'2’的前面,因为字符'1’比字符'2’的ASCII码小2、使用回调函数的错误[10, 2, 3, 100, 6, 9].sort((a, b) => { return a < b;});// 无法理解的结果[10, 2, 3, 100, 6, 9]排序前后结果没有变化问题分析:在sort实现的规范中有这么一条 sortFun(a,b) === 0,则有 a === b 且 b === a 。此时我们再看var sortFun = (a, b) => a < b,它等同于var sortFun = (a, b) => a < b ? 1 : 0。它有一个隐藏的漏洞:当a >= b时,sortFun(a,b) === 0。而根据规范,通过sortFun(a,b) === 0可以推测出a === b,显然这里互相矛盾, 反之亦然(a > b的情况)。所以比较的时候最好使用 a - b 或者 b - a正确写法:[10, 2, 3, 100, 6, 9].sort((a, b) => { return a - b;});// 结果[2, 3, 6, 9, 10, 100]android 和 ios平台解析的sort函数实现方式不同,不规范的写法可能导致解析结果不同 ...

January 31, 2019 · 1 min · jiezi

让前端面试不在难(一)

今天开始,我从面试题切入开始做一些详解和记录,争取每个工作日一篇!欢迎关注吐槽!const obj = { a: 1, b: 3, c: -3, f: 5, d: 8 }要求以对象value的大小排序返回[c,a,b,f,d]问题解析: 1、对象是无序的,我们需要转为有序数据结构,其实也就是转为数组然后后再去排序。 2.按value排序简单,但要求是输入key对应的排序,我们需要想办法做对应关系 function sortObj(obj) { //先转为数组 let arr = [] // 遍历json 方法有 Object.keys() for in 用keys以后还得继续遍历key数组,在这我们选用for in for (let item in obj) { // 这一步很关键,我们需要能按照value排序,有需要做key的对应关系,我的做法是这样的 // 把json的每一项push到数组里,并拆分原对象key和value分别对应 arr.push({ key: item, value: obj[item] }) } console.log(arr) } sortObj(obj)打印数组:接下来就简单多了,多于的数组排序方法我就不一一写了,本次只为解决问题function sortObj(obj) { //先转为数组 let arr = [] // 遍历json 方法有 Object.keys() for in 用keys以后还得继续遍历key数组,在这我们选用for in for (let item in obj) { // 这一步很关键,我们需要能按照value排序,有需要做key的对应关系,我的做法是这样的 // 把json的每一项push到数组里,并拆分原对象key和value分别对应 arr.push({ key: item, value: obj[item] }) } arr = arr.sort((a, b) => { return a.value - b.value }) console.log(arr) } sortObj(obj)此时结果为以value有序的数组了接下来遍历数组生成结果function sortObj(obj) { //先转为数组 let arr = [] // 遍历json 方法有 Object.keys() for in 用keys以后还得继续遍历key数组,在这我们选用for in for (let item in obj) { // 这一步很关键,我们需要能按照value排序,有需要做key的对应关系,我的做法是这样的 // 把json的每一项push到数组里,并拆分原对象key和value分别对应 arr.push({ key: item, value: obj[item] }) } arr = arr.sort((a, b) => { return a.value - b.value }) console.log(arr) return arr.map((item) => { return item.key }) } console.log(sortObj(obj))测试ok!在来个es6的方法 let newArr = Object.entries(obj).sort((a, b) => { return a[1] - b[1] }).map((item) => { return item[0] }) console.log(newArr)这个方法看起来很骚,其实原理和最开始的解析类似,Object.entries(obj) 会输入一个数组,数组的每一项是一个数组,内容每一项是原对象每一项的key和value,看下图:解析完毕!您的点赞or吐槽是我持续下去的动力! ...

January 24, 2019 · 1 min · jiezi

用sort实现orderby

工作到了这个年数, 感觉那些基本函数语法已经跟人合一了, 根本不会为操作一些数据结构而思考半天了. 在做小程序的时候遇到了个orderby的场景, 结果发现没有以为的那么简单. 也许是之前不求甚解的原因, 那么现在来解决orderby的问题.问题的产生与探讨方向在小程序中有个将list的某一条置顶的需求, 在初始化数据到时候可以使用数据库的orderby, 但在更新数据以后再重新初始化就显得有些不妥, 所以我尝试直接使用computed列表来解决这个问题.所以现在的问题是: 输入list, 输出orderby置顶字段.之前以为的sort很简单, 我就尝试了: arr.sort(i => i.stick). 字面看起来是根据stick字段来排序. 输出结果一团糟. 仔细思考了下又尝试了别的方法, 还是失败, 才决定仔细想一下应该如何处理.对sort的理解与快速shuffle先说一下之前对sort的理解.sort接受的参数返回大于0或者小于0. 根据结果来排序.所以有一个快速shuffle数组的方法:arr.sort(() => Math.random() - 0.5)因为函数的返回结果一半是大于0一半是小于0的(不严格, 但之后也认为概率是一半一半). 所以任何输出进行了如此处理, 都会变成一个随机顺序的数组.另外一个例子, 对一个数组: [1, 2, 3, 4, 5, 10, 11, 12]进行排序, 如果不传参数排序结果是错的, 因为默认是localCompare. 所以要写成:arr.sort((a, b) => a - b)这样才能得到正确从小到大的排列.以上就是我多年以来对sort的所有理解. 所以才会写出上面的: arr.sort(i => i.stick)这样搞笑的东西. 因为理解是有问题的.sort是如何排序的因为不知道sort函数得到了结果后是如何排序的. 所以对sort的理解有问题. 而我们知道reduce就是从头到尾遍历并传递每次计算的结果. sort却不知道. 所以打出每次的返回值来看一下每次得到返回值后sort做了些什么.我们要对不同数组进行同样的操作, 排序方法是一样的, 先写一下:const log = (a, b) => { console.log(a, b, a - b > 0) return a - b}开始对不同数组进行排序: 先来1到5[1, 2, 3, 4, 5].sort(log)结果: [1, 2, 3, 4, 5]2 1 true3 2 true4 3 true5 4 true尝试: 从5到1[5, 4, 3, 2, 1].sort(log)结果: [1, 2, 3, 4, 5]4 5 false3 4 false2 3 false1 2 false目前看来, sort应该是插入排序.[3, 5, 7, 9, 2, 1, 6].sort(log)看log的时候我把当前排序结果也打一下:5 3 true [3, 5]7 5 true [3, 5, 7]9 7 true [3, 5, 7, 9]2 9 false // 2还是与当前最大的9比.结果第一次false2 7 false // 于是一路比下来2 5 false2 3 false // 比到最小的, 于是确定了位置 [2, 3, 5, 7, 9]1 5 false // 1选择了与5比, 此时5是中间位置的数, 而不是最大的数1 3 false // 然后一个一个比较下来1 2 false [1, 2, 3, 5, 7, 9]6 5 true // 6还是于5比, 此时5也是中间位置的数6 9 false // 没有选择与7, 而是与9比了6 7 false从这些log能得出一些粗浅的结论:sort是插入排序每次比较的数字会根据两个因素来决定: 分别是之前比较的结果和当前排序的位置如何实现orderby首先明确思路:sort认为每个元素之间的关系是比大小, 所以我们需要做的是写出任意两个元素的相对顺序的普遍公式.先构建一组数据:let gnrt = () => ({ age: Math.round(Math.random() * 50), height: Math.round(Math.random() * 200) })let arr = Array.from({length: 10}).map(() => gnrt())我们先建立纯数字, 无顺序的orderby来理这个思路.let orderby = function (arr, …orders) { return arr.sort((a, b) => { let res = 0 for (let order of orders) { if (a[order] - b[order] !== 0) { res = a[order] - b[order] break } } return res })}调用orderby(arr, ‘height’, ‘age’)就得到了理想的orderby结果了: 根据权重排序, 如果都一样就保持顺序.#后续#这个思路清晰以后, 做兼容就容易了:如果要指定顺序, 在排序参数里带特征, 例如’height’, ‘-height’, 来决定在执行的时候是a - b 还是b - a.如果要指定排序函数(在非数字情况下). 把排序参数改写成兼容function的, 判断是string就执行默认, 是function就调用function即可.当然, 功能越完善的函数就越复杂, 函数本身只是函数复杂度和业务复杂度交换的作用. 具体实现就不写了.所以置顶排序如何实现我们已经想清楚了orderby的实现, 那么置顶排序是stick这个布尔值字段, 就必须根据我上面说的传函数进去, 并且改写orderby函数.这样又要多些2个函数, 所以我选择:[…arr.filter({stick} => stick), …arr.filter({stick} => !stick)]搞定. ...

December 22, 2018 · 2 min · jiezi

[LeetCode] 280. Wiggle Sort

ProblemGiven an unsorted array nums, reorder it in-place such that nums[0] <= nums[1] >= nums[2] <= nums[3]….Example:Input: nums = [3,5,2,1,6,4]Output: One possible answer is [3,5,1,6,2,4]Solutionclass Solution { public void wiggleSort(int[] nums) { for (int i = 0; i < nums.length; i++) { if (i%2 == 1) { if (nums[i] < nums[i-1]) swap(nums, i, i-1); } else { if (i != 0 && nums[i] > nums[i-1]) swap(nums, i, i-1); } } } private void swap(int[] nums, int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; }} ...

December 14, 2018 · 1 min · jiezi