From 932437ae586b9d632ef88b3edfd68e4020caef2d Mon Sep 17 00:00:00 2001 From: Timur Gibadullin Date: Wed, 22 Nov 2017 00:39:48 +0300 Subject: [PATCH] Improve perfomance while reordering --- src/SortableList.js | 204 ++++++++++++++++++++++++++++++++++--------- src/utils/inRange.js | 3 + src/utils/index.js | 10 +-- 3 files changed, 168 insertions(+), 49 deletions(-) create mode 100644 src/utils/inRange.js diff --git a/src/SortableList.js b/src/SortableList.js index 73fcf31..5f54a8d 100644 --- a/src/SortableList.js +++ b/src/SortableList.js @@ -1,7 +1,7 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; import {ScrollView, View, StyleSheet, Platform, RefreshControl, ViewPropTypes} from 'react-native'; -import {shallowEqual, swapArrayElements} from './utils'; +import {inRange, shallowEqual, swapArrayElements} from './utils'; import Row from './Row'; const AUTOSCROLL_INTERVAL = 100; @@ -220,7 +220,6 @@ export default class SortableList extends Component { const {horizontal, rowActivationTime, sortingEnabled, renderRow} = this.props; const {animated, order, data, activeRowKey, releasedRowKey, rowsLayouts} = this.state; - let nextX = 0; let nextY = 0; @@ -302,11 +301,13 @@ export default class SortableList extends Component { } _onUpdateLayouts() { + const {horizontal} = this.props; + const {order} = this.state; Promise.all([this._headerLayout, this._footerLayout, ...Object.values(this._rowsLayouts)]) .then(([headerLayout, footerLayout, ...rowsLayouts]) => { // Can get correct container’s layout only after rows’s layouts. this._container.measure((x, y, width, height, pageX, pageY) => { - const rowsLayoutsByKey = {}; + let rowsLayoutsByKey = {}; let contentHeight = 0; let contentWidth = 0; @@ -315,10 +316,12 @@ export default class SortableList extends Component { contentHeight += layout.height; contentWidth += layout.width; }); + rowsLayoutsByKey = this._getRowsLocations(rowsLayoutsByKey, order); this.setState({ containerLayout: {x, y, width, height, pageX, pageY}, rowsLayouts: rowsLayoutsByKey, + rowsSwapRanges: this._getRowsSwapRanges(rowsLayoutsByKey, order), headerLayout, footerLayout, contentHeight, @@ -330,6 +333,54 @@ export default class SortableList extends Component { }); } + _getRowsLocations(_rowsLayouts, order) { + const {horizontal} = this.props; + const rowsLayouts = {}; + let nextX = 0; + let nextY = 0; + + for (let i = 0, len = order.length; i < len; i++) { + const rowKey = order[i]; + const rowLayout = _rowsLayouts[rowKey]; + + rowsLayouts[rowKey] = { + ...rowLayout, + x: nextX, + y: nextY, + }; + + if (horizontal) { + nextX += rowLayout.width; + } else { + nextY += rowLayout.height; + } + } + + return rowsLayouts; + } + + _getRowsSwapRanges(rowsLayouts, order) { + const {horizontal} = this.props; + const rowsSwapRanges = {}; + + for (let i = 0, len = order.length; i < len; i++) { + const rowKey = order[i]; + const rowLayout = rowsLayouts[rowKey]; + + rowsSwapRanges[rowKey] = horizontal + ? { + left: [rowLayout.x + rowLayout.width / 3, rowLayout.x + rowLayout.width], + right: [rowLayout.x, rowLayout.x + 2 * rowLayout.width / 3], + } + : { + top: [rowLayout.y + rowLayout.height / 3, rowLayout.y + rowLayout.height], + bottom: [rowLayout.y, rowLayout.y + 2 * rowLayout.height / 3], + }; + } + + return rowsSwapRanges; + } + _scroll(animated) { this._scrollView.scrollTo({...this._contentOffset, animated}); } @@ -339,7 +390,8 @@ export default class SortableList extends Component { * swaps them, else shifts rows. */ _setOrderOnMove() { - const {activeRowKey, activeRowIndex, order} = this.state; + const {activeRowKey, activeRowIndex, order, rowsLayouts} = this.state; + const {horizontal} = this.props; if (activeRowKey === null || this._autoScrollInterval) { return; @@ -369,9 +421,14 @@ export default class SortableList extends Component { nextOrder.splice(rowUnderActiveIndex, 0, activeRowKey); } + const nextRowsLayouts = this._getRowsLocations(rowsLayouts, nextOrder); + const nextRowsSwapRanges = this._getRowsSwapRanges(nextRowsLayouts, nextOrder); + this.setState({ order: nextOrder, activeRowIndex: rowUnderActiveIndex, + rowsLayouts: nextRowsLayouts, + rowsSwapRanges: nextRowsSwapRanges, }, () => { if (this.props.onChangeOrder) { this.props.onChangeOrder(nextOrder); @@ -381,53 +438,114 @@ export default class SortableList extends Component { } /** - * Finds a row, which was covered with the moving row’s half. + * Finds a row, which was covered with the moving row’s third. */ _findRowUnderActiveRow() { const {horizontal} = this.props; - const {rowsLayouts, activeRowKey, activeRowIndex, order} = this.state; - const movingRowLayout = rowsLayouts[activeRowKey]; - const rowLeftX = this._activeRowLocation.x - const rowRightX = rowLeftX + movingRowLayout.width; - const rowTopY = this._activeRowLocation.y; - const rowBottomY = rowTopY + movingRowLayout.height; - - for ( - let currentRowIndex = 0, x = 0, y = 0, rowsCount = order.length; - currentRowIndex < rowsCount - 1; - currentRowIndex++ + const {rowsLayouts, rowsSwapRanges, activeRowKey, activeRowIndex, order} = this.state; + const movingDirection = this._movingDirection; + const rowsCount = order.length; + const activeRowLayout = rowsLayouts[activeRowKey]; + const activeRowLeftX = this._activeRowLocation.x + const activeRowRightX = this._activeRowLocation.x + activeRowLayout.width; + const activeRowTopY = this._activeRowLocation.y; + const activeRowBottomY = this._activeRowLocation.y + activeRowLayout.height; + + const prevRowIndex = activeRowIndex - 1; + const prevRowKey = order[prevRowIndex]; + const prevRowSwapRages = rowsSwapRanges[prevRowKey] + const nextRowIndex = activeRowIndex + 1; + const nextRowKey = order[nextRowIndex]; + const nextRowSwapRages = rowsSwapRanges[nextRowKey] + + if (horizontal + ? (movingDirection === 1 + ? ( + (activeRowIndex === 0 || activeRowLeftX > prevRowSwapRages.right[0]) && + (activeRowIndex === rowsCount - 1 || activeRowRightX < nextRowSwapRages.left[0]) + ) + : ( + (activeRowIndex === 0 || activeRowLeftX > prevRowSwapRages.right[1]) && + (activeRowIndex === rowsCount - 1 || activeRowRightX < nextRowSwapRages.left[1]) + ) + ) + : (movingDirection === 1 + ? ( + (activeRowIndex === 0 || activeRowTopY > prevRowSwapRages.bottom[0]) && + (activeRowIndex === rowsCount - 1 || activeRowBottomY < nextRowSwapRages.top[0]) + ) + : ( + (activeRowIndex === 0 || activeRowTopY > prevRowSwapRages.bottom[1]) && + (activeRowIndex === rowsCount - 1 || activeRowBottomY < nextRowSwapRages.top[1]) + ) + ) ) { - const currentRowKey = order[currentRowIndex]; - const currentRowLayout = rowsLayouts[currentRowKey]; - const nextRowIndex = currentRowIndex + 1; - const nextRowLayout = rowsLayouts[order[nextRowIndex]]; - - x += currentRowLayout.width; - y += currentRowLayout.height; - - if (currentRowKey !== activeRowKey && ( - horizontal - ? ((x - currentRowLayout.width <= rowLeftX || currentRowIndex === 0) && rowLeftX <= x - currentRowLayout.width / 3) - : ((y - currentRowLayout.height <= rowTopY || currentRowIndex === 0) && rowTopY <= y - currentRowLayout.height / 3) - )) { - return { - rowKey: order[currentRowIndex], - rowIndex: currentRowIndex, - }; - } + return { + rowKey: activeRowKey, + rowIndex: activeRowIndex, + }; + } + if (movingDirection === 1) { if (horizontal - ? (x + nextRowLayout.width / 3 <= rowRightX && (rowRightX <= x + nextRowLayout.width || nextRowIndex === rowsCount - 1)) - : (y + nextRowLayout.height / 3 <= rowBottomY && (rowBottomY <= y + nextRowLayout.height || nextRowIndex === rowsCount - 1)) + ? inRange(activeRowRightX, ...nextRowSwapRages.left) + : inRange(activeRowBottomY, ...nextRowSwapRages.top) ) { - return { - rowKey: order[nextRowIndex], + return { + rowKey: nextRowKey, rowIndex: nextRowIndex, }; } + } else { + if (horizontal + ? inRange(activeRowLeftX, ...prevRowSwapRages.right) + : inRange(activeRowTopY, ...prevRowSwapRages.bottom) + ) { + return { + rowKey: prevRowKey, + rowIndex: prevRowIndex, + }; + } } - return {rowKey: activeRowKey, rowIndex: activeRowIndex}; +// let startIndex = 0; +// let endIndex = rowsCount - 1; +// let middleIndex; +// let it = 0 +// console.log(movingDirection); +// while (startIndex < endIndex) { +// middleIndex = Math.floor((endIndex - startIndex) / 2); + +// if (it++ > 10) { +// console.log(startIndex, middleIndex, endIndex); +// break +// } +// const middleRowSwapRanges = rowsSwapRanges[middleIndex]; + +// if (horizontal) { +// if (movingDirection === 1) { +// if (inRange(activeRowRightX, ...middleRowSwapRanges.left)) break; +// else if (activeRowRightX < middleRowSwapRanges.left[0]) endIndex = middleIndex; +// else if (activeRowRightX > middleRowSwapRanges.left[1]) startIndex = middleIndex; +// } else { +// if (inRange(activeRowLeftX, ...middleRowSwapRanges.right)) break; +// else if (activeRowLeftX < middleRowSwapRanges.right[0]) endIndex = middleIndex; +// else if (activeRowLeftX > middleRowSwapRanges.right[1]) startIndex = middleIndex; +// } +// } else { +// if (movingDirection === 1) { +// if (inRange(activeRowBottomY, ...middleRowSwapRanges.top)) break; +// else if (activeRowBottomY < middleRowSwapRanges.top[0]) endIndex = middleIndex; +// else if (activeRowBottomY > middleRowSwapRanges.top[1]) startIndex = middleIndex; +// } else { +// if (inRange(activeRowTopY, ...middleRowSwapRanges.bottom)) break; +// else if (activeRowTopY < middleRowSwapRanges.bottom[0]) endIndex = middleIndex; +// else if (activeRowTopY > middleRowSwapRanges.bottom[1]) startIndex = middleIndex; +// } +// } +// } + +// return {rowKey: order[middleIndex], rowIndex: middleIndex}; } _scrollOnMove(e) { @@ -595,9 +713,11 @@ export default class SortableList extends Component { const prevMovingDirection = this._movingDirection; this._activeRowLocation = location; - this._movingDirection = this.props.horizontal - ? prevMovingRowX < this._activeRowLocation.x - : prevMovingRowY < this._activeRowLocation.y; + this._movingDirection = ( + this.props.horizontal + ? prevMovingRowX <= this._activeRowLocation.x + : prevMovingRowY <= this._activeRowLocation.y + ) ? 1 : -1; this._movingDirectionChanged = prevMovingDirection !== this._movingDirection; this._setOrderOnMove(); diff --git a/src/utils/inRange.js b/src/utils/inRange.js new file mode 100644 index 0000000..304007d --- /dev/null +++ b/src/utils/inRange.js @@ -0,0 +1,3 @@ +export default function inRange(number, start, end) { + return start <= number && number <= end; +} diff --git a/src/utils/index.js b/src/utils/index.js index f9a3c34..813b81d 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,7 +1,3 @@ -import shallowEqual from './shallowEqual'; -import swapArrayElements from './swapArrayElements'; - -export { - shallowEqual, - swapArrayElements -}; +export {default as inRange} from './inRange' +export {default as shallowEqual} from './shallowEqual'; +export {default as swapArrayElements} from './swapArrayElements';