UNPKG

react-native-big-list-fixed

Version:

A big and fast list implementation for react-native with a recycler API focused on performance and ram usage while processing thousand items on the list.

390 lines (372 loc) 10.4 kB
import { BigListItemType } from "./BigListItem"; import BigListItemRecycler from "./BigListItemRecycler"; import { isNumeric } from "./utils"; export default class BigListProcessor { /** * Constructor. * @param {ScrollView} scrollView * @param {array[]|object|null|undefined} sections * @param {number|function|null|undefined} headerHeight * @param {number|function|null|undefined} footerHeight * @param {number|function|null|undefined} sectionHeaderHeight * @param {number|function|null|undefined} itemHeight * @param {number|function|null|undefined} sectionFooterHeight * @param {number|function|null|undefined} insetTop * @param {number|function|null|undefined} insetBottom * @param {number|null|undefined} numColumns */ constructor({ scrollView, sections, headerHeight, footerHeight, sectionHeaderHeight, itemHeight, sectionFooterHeight, insetTop, insetBottom, numColumns }) { this.headerHeight = headerHeight; this.footerHeight = footerHeight; this.sectionHeaderHeight = sectionHeaderHeight; this.itemHeight = itemHeight; this.sectionFooterHeight = sectionFooterHeight; this.sections = sections; this.insetTop = insetTop; this.insetBottom = insetBottom; this.uniform = isNumeric(itemHeight); this.scrollView = scrollView; this.numColumns = numColumns; } /** * Get item height. * @returns {number|*} */ getItemHeight(section, index) { const { itemHeight } = this; return isNumeric(itemHeight) ? Number(itemHeight) : itemHeight(section, index); } /** * Get header height. * @returns {number|*} */ getHeaderHeight() { const { headerHeight } = this; return isNumeric(headerHeight) ? Number(headerHeight) : headerHeight(); } /** * Get footer height. * @returns {number|*} */ getFooterHeight() { const { footerHeight } = this; return isNumeric(footerHeight) ? Number(footerHeight) : footerHeight(); } /** * Get section height. * @returns {number|*} */ getSectionHeaderHeight(section) { const { sectionHeaderHeight } = this; return isNumeric(sectionHeaderHeight) ? Number(sectionHeaderHeight) : sectionHeaderHeight(section); } /** * Get section footer height. * @returns {number|*} */ getSectionFooterHeight(section) { const { sectionFooterHeight } = this; return isNumeric(sectionFooterHeight) ? Number(sectionFooterHeight) : sectionFooterHeight(section); } /** * Process list items. * @param {number} top * @param {number} bottom * @param {array} prevItems * @returns {{items: [], height: *}} */ process(top, bottom, prevItems) { const { sections } = this; const items = []; const recycler = new BigListItemRecycler(prevItems); let position; let counter = -1; // Counter of items per row pushed let height = this.insetTop; let spacerHeight = height; /** * The width of the row is the entire line. * @param {object} item * @returns {boolean} */ const isFullRow = item => { // Only items can be rendered with column format, so all others are full row return item.type !== BigListItemType.ITEM; }; /** * Is visible below. * @param {object} item * @returns {boolean} */ const isVisibleBelow = item => { const { height: itemHeight } = item; counter = -1; if (height > bottom) { spacerHeight += itemHeight; return false; } else { return true; } }; /** * Is the item visible. * @param {object} item * @param {bool} force * @returns {boolean} */ const isVisible = (item, force = false) => { // Check section headers visibility below if (item.type === BigListItemType.SECTION_HEADER) { return isVisibleBelow(item); } // Dimensions const { height: itemHeight } = item; const fullRow = isFullRow(item); const prevHeight = height; // Increase or reset counter counter = fullRow ? -1 : counter + 1; if (fullRow || counter % this.numColumns === 0) { height += itemHeight; } // Check if is visible if (force || height > top && prevHeight < bottom) { return true; } else { if (fullRow || counter % this.numColumns === 0) { spacerHeight += itemHeight; } return false; } }; /** * Get recycled views and push items. * @param {object} itemsArray */ const push = (...itemsArray) => { itemsArray.forEach(item => { items.push(recycler.get(item)); }); }; /** * Push spacer. * @param {object} item */ const pushSpacer = item => { if (spacerHeight > 0) { push({ type: BigListItemType.SPACER, position: item.position - spacerHeight, height: spacerHeight, section: item.section, index: item.index }); spacerHeight = 0; } }; /** * Push the item when is visible. * @param {object} item * @param {bool} force */ const pushItem = (item, force = false) => { if (isVisible(item, force)) { pushSpacer(item); push(item); } }; /** * Calculate spacer height. */ const getSpacerHeight = () => { let itemsCounter = -1; return items.reduce((totalHeight, item, i) => { if (i !== items.length - 1) { const fullRow = isFullRow(item); itemsCounter = fullRow ? 0 : itemsCounter + 1; if (fullRow || itemsCounter % this.numColumns === 0) { return totalHeight + item.height; } } return totalHeight; }, 0); }; // Header const headerHeight = this.getHeaderHeight(); if (headerHeight > 0) { position = height; pushItem({ type: BigListItemType.HEADER, position: position, height: headerHeight }, true); } // Sections for (let section = 0; section < sections.length; section++) { const rows = sections[section]; if (rows === 0) { continue; } // Section Header const sectionHeaderHeight = this.getSectionHeaderHeight(section); position = height; height += sectionHeaderHeight; if (section > 1 && items.length > 0 && items[items.length - 1].type === BigListItemType.SECTION_HEADER) { // Top Spacer const initialSpacerHeight = getSpacerHeight(); const prevSection = items[items.length - 1]; items.splice(0, items.length); push({ type: BigListItemType.HEADER, position: position, height: headerHeight }, { type: BigListItemType.SPACER, position: 0, height: initialSpacerHeight - headerHeight, section: prevSection.section, index: 0 }, prevSection); } pushItem({ type: BigListItemType.SECTION_HEADER, position: position, height: sectionHeaderHeight, section: section }); // Items let itemHeight = this.getItemHeight(section); for (let index = 0; index < rows; index++) { if (!this.uniform) { itemHeight = this.getItemHeight(section, index); } position = height; pushItem({ type: BigListItemType.ITEM, position: position, height: itemHeight, section: section, index: index }); } // Section Footer const sectionFooterHeight = this.getSectionFooterHeight(section); if (sectionFooterHeight > 0) { position = height; pushItem({ type: BigListItemType.SECTION_FOOTER, position: position, height: sectionFooterHeight, section: section }); } } // Footer const footerHeight = this.getFooterHeight(); if (footerHeight > 0) { position = height; pushItem({ type: BigListItemType.FOOTER, position: position, height: footerHeight }, true); } // Bottom Spacer height += this.insetBottom; spacerHeight += this.insetBottom; if (spacerHeight > 0) { push({ type: BigListItemType.SPACER, position: height - spacerHeight, height: spacerHeight, section: sections.length }); } recycler.fill(); return { height, items }; } /** * Scroll to position. * @param {int} targetSection * @param {int} targetIndex * @param {boolean} animated */ scrollToPosition(targetSection, targetIndex, animated) { const { sections, insetTop } = this; // Header + inset let scrollTop = insetTop + this.getHeaderHeight(); let section = 0; let foundIndex = false; while (section <= targetSection) { const rows = Math.ceil(sections[section] / this.numColumns); if (rows === 0) { section += 1; continue; } // Section header scrollTop += this.getSectionHeaderHeight(section); // Items if (this.uniform) { const uniformHeight = this.getItemHeight(section); if (section === targetSection) { scrollTop += uniformHeight * Math.ceil(targetIndex / this.numColumns); foundIndex = true; } else { scrollTop += uniformHeight * rows; } } else { for (let index = 0; index < rows; index++) { if (section < targetSection || section === targetSection && index < targetIndex) { scrollTop += this.getItemHeight(section, Math.ceil(index / this.numColumns)); } else if (section === targetSection && index === targetIndex) { foundIndex = true; break; } } } // Section footer if (!foundIndex) { scrollTop += this.getSectionFooterHeight(section); } section += 1; } this.scrollView.scrollTo({ x: 0, y: Math.max(0, scrollTop - this.getSectionHeaderHeight(targetSection)), animated }); return true; } } //# sourceMappingURL=BigListProcessor.js.map