@nstudio/ui-collectionview
Version:
Customized NativeScript CollectionView for high performance lists. Supports vertical and horizontal modes, templating, and more.
1,164 lines • 57.5 kB
JavaScript
var _a, _b;
/* eslint-disable no-redeclare */
import { ChangeType, ContentView, CoreTypes, Length, Property, ProxyViewContainer, Trace, Utils, View, booleanConverter, paddingBottomProperty, paddingLeftProperty, paddingRightProperty, paddingTopProperty, profile } from '@nativescript/core';
import { itemOverlapProperty, reorderLongPressEnabledProperty, reorderingEnabledProperty, reverseLayoutProperty, scrollBarIndicatorVisibleProperty } from '.';
import { CLog, CLogTypes, CollectionViewBase, ListViewViewTypes, isScrollEnabledProperty, orientationProperty } from './common';
import { createArrayBuffer } from '@nativescript-community/arraybuffers';
export * from './common';
let CollectionViewCellHolder;
let CollectionViewRecyclerView;
var SimpleCallback = /** @class */ (function (_super) {
__extends(SimpleCallback, _super);
function SimpleCallback(param1, param2) {
var _this = _super.call(this, param1, param2) || this;
_this.startPosition = -1;
_this.endPosition = -1;
return global.__native(_this);
}
SimpleCallback.prototype.onMove = function (recyclerview, viewHolder, target) {
var _a;
var startPosition = viewHolder.getAdapterPosition();
var endPosition = target.getAdapterPosition();
var owner = (_a = this.owner) === null || _a === void 0 ? void 0 : _a.get();
if (owner && !owner._canReorderToPosition(startPosition, endPosition, owner.getItemAtIndex(endPosition))) {
return false;
}
if (this.startPosition === -1) {
this.startPosition = startPosition;
}
this.endPosition = endPosition;
if (owner) {
owner._reorderItemInSource(startPosition, endPosition);
return true;
}
return false;
};
SimpleCallback.prototype.onSelectedChanged = function (viewHolder, state) {
var _a;
if (viewHolder) {
if (this.startPosition === -1) {
this.startPosition = viewHolder.getAdapterPosition();
}
}
if (!viewHolder) {
// this is where we identify the end of the drag and call the end event
var owner = (_a = this.owner) === null || _a === void 0 ? void 0 : _a.get();
if (this.endPosition === -1) {
this.endPosition = this.startPosition;
}
if (owner) {
var item = owner.getItemAtIndex(this.startPosition);
owner._callItemReorderedEvent(this.startPosition, this.endPosition, item);
}
this.startPosition = -1;
this.endPosition = -1;
owner.isDragging = false;
}
};
SimpleCallback.prototype.onSwiped = function (viewHolder, direction) { };
SimpleCallback.prototype.isItemViewSwipeEnabled = function () {
// disabled for now
return false;
};
SimpleCallback.prototype.isLongPressDragEnabled = function () {
// we use our custom longpress gesture handler
return false;
};
return SimpleCallback;
}(androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback));
var LongPressGestureListenerImpl = /** @class */ (function (_super) {
__extends(LongPressGestureListenerImpl, _super);
function LongPressGestureListenerImpl(_owner) {
var _this = _super.call(this) || this;
_this._owner = _owner;
return global.__native(_this);
}
LongPressGestureListenerImpl.prototype.onLongPress = function (motionEvent) {
var _a;
var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.get();
if (owner) {
owner.onReorderLongPress(motionEvent);
}
};
return LongPressGestureListenerImpl;
}(android.view.GestureDetector.SimpleOnGestureListener));
let LayoutParams;
const extraLayoutSpaceProperty = new Property({
name: 'extraLayoutSpace',
});
const itemViewCacheSizeProperty = new Property({
name: 'itemViewCacheSize',
});
const nestedScrollingEnabledProperty = new Property({
name: 'nestedScrollingEnabled',
defaultValue: true,
valueConverter: booleanConverter,
});
export var SnapPosition;
(function (SnapPosition) {
SnapPosition[SnapPosition["START"] = 0] = "START";
SnapPosition[SnapPosition["END"] = 1] = "END";
})(SnapPosition || (SnapPosition = {}));
export class CollectionView extends CollectionViewBase {
constructor() {
super(...arguments);
this.templateTypeNumberString = new Map();
this.templateStringTypeNumber = new Map();
this._currentNativeItemType = 0;
this.currentSpanCount = 1;
// used to store viewHolder and thus their corresponding Views
// used to "destroy" cells when possible
this._viewHolders = new Set();
this._scrollOrLoadMoreChangeCount = 0;
this.scrolling = false;
this.needsScrollStartEvent = false;
this.animateItemUpdate = false;
this.mInPropertiesSet = false;
this.mShouldUpdateInnerSize = false;
this.mShouldUpdateSpanCount = false;
this.mShouldRefresh = false;
this.defaultPoolSize = 10;
this.desiredPoolSize = new Map();
this.isDragging = false;
this._layedOut = false;
this.bindedViewHolders = new Set();
}
onResumeNativeUpdates() {
// {N} suspends properties update on `_suspendNativeUpdates`. So we only need to do this in onResumeNativeUpdates
this.mInPropertiesSet = true;
super.onResumeNativeUpdates();
this.mInPropertiesSet = false;
if (this.mShouldUpdateInnerSize) {
this.updateInnerSize();
}
if (this.mShouldUpdateSpanCount) {
this.updateSpanCount();
}
if (this.mShouldRefresh) {
this.refresh();
}
}
createNativeView() {
// storing the class in a property for reuse in the future cause a materializing which is pretty slow!
if (!CollectionViewRecyclerView) {
CollectionViewRecyclerView = com.nativescript.collectionview.RecyclerView;
}
const recyclerView = CollectionViewRecyclerView.createRecyclerView(this._context);
// const expMgr = new RecyclerViewExpandableItemManager(null);
// adapter.setDisplayHeadersAtStartUp(true).setStickyHeaders(true); //Make headers sticky
// Endless scroll with 1 item threshold
// .setLoadingMoreAtStartUp(true)
// .setEndlessScrollListener(this, new ProgressItem())
// .setEndlessScrollThreshold(1); //Default=1
// const fastScroller = new com.l4digital.fastscroll.FastScroller(this._context);
// fastScroller.setSectionIndexer(adapter);
// fastScroller.attachRecyclerView(recyclerView);
return recyclerView;
}
initNativeView() {
super.initNativeView();
const nativeView = this.nativeViewProtected;
if (!this.recycledViewPool) {
this.recycledViewPool = new com.nativescript.collectionview.RecycledViewPool();
this.recycledViewPoolDisposeListener = new com.nativescript.collectionview.RecycledViewPool.ViewPoolListener({
onViewHolderDisposed: (holder) => {
if (Trace.isEnabled()) {
CLog(CLogTypes.log, 'onViewHolderDisposed', holder);
}
if (this._viewHolders) {
this._viewHolders.delete(holder);
}
const isNonSync = holder['defaultItemView'] === true;
const view = isNonSync ? holder.view.content : holder.view;
this.notifyForItemAtIndex(CollectionViewBase.itemDisposingEvent, view, holder.getAdapterPosition(), view.bindingContext, holder);
if (view && view.isLoaded) {
view.callUnloaded();
}
view._isAddedToNativeVisualTree = false;
//@ts-ignore
view.parent = null;
view._tearDownUI();
},
});
this.recycledViewPool.mListener = this.recycledViewPoolDisposeListener;
}
if (Trace.isEnabled() || this.hasListeners(CollectionViewBase.itemRecyclingEvent)) {
const recyclerListener = (this.recyclerListener = new androidx.recyclerview.widget.RecyclerView.RecyclerListener({
onViewRecycled: (holder) => {
if (Trace.isEnabled()) {
CLog(CLogTypes.log, 'onViewRecycled', this, nativeView, holder);
}
const isNonSync = holder['defaultItemView'] === true;
const view = isNonSync ? holder.view.content : holder.view;
this.notifyForItemAtIndex(CollectionViewBase.itemRecyclingEvent, view, holder.getAdapterPosition(), view.bindingContext, holder);
},
}));
nativeView.setRecyclerListener(recyclerListener);
}
nativeView.setRecycledViewPool(this.recycledViewPool);
let layoutManager;
if (CollectionViewBase.layoutStyles[this.layoutStyle]) {
layoutManager = CollectionViewBase.layoutStyles[this.layoutStyle].createLayout(this);
}
else {
layoutManager = new com.nativescript.collectionview.PreCachingGridLayoutManager(this._context, 1);
if (this.hasListeners(CollectionView.layoutCompletedEvent)) {
layoutManager.layoutCompletedListener = new com.nativescript.collectionview.GridLayoutManager.LayoutCompletedListener({
onLayoutCompleted: () => {
this.notify({ eventName: CollectionView.layoutCompletedEvent });
},
});
}
// layoutManager = new PreCachingGridLayoutManager(this._context, 1);
// (layoutManager as any).owner = new WeakRef(this);
}
// this.spanSize
nativeView.setLayoutManager(layoutManager);
nativeView.layoutManager = layoutManager;
nativeView.sizeChangedListener = new com.nativescript.collectionview.SizeChangedListener({
onLayout: (changed, left, top, right, bottom) => changed && this.onLayout(left, top, right, bottom),
onMeasure: (widthMeasureSpec, heightMeasureSpec) => this.onMeasure(widthMeasureSpec, heightMeasureSpec),
});
this.spanSize = this._getSpanSize;
// const animator = new jp.wasabeef.recyclerview.animators.FadeInAnimator();
// // animator.setInterpolator(new android.view.animation.OvershootInterpolator());
// animator.setMoveDuration(200);
// Change animations are enabled by default since support-v7-recyclerview v22.
// Need to disable them when using animation indicator.
// animator.setSupportsChangeAnimations(false);
// nativeView.setItemAnimator(animator);
// enforce the first refresh for the collectionview to be ready as soon as possible
this.refresh(true);
}
disposeNativeView() {
// this.eachChildView((view) => {
// view.parent._removeView(view);
// return true;
// });
// this._realizedItems.clear();
const nativeView = this.nativeViewProtected;
nativeView.setRecyclerListener(null);
nativeView.setRecycledViewPool(null);
this.recycledViewPoolDisposeListener = null;
this.recycledViewPool = null;
this.detachScrollListenerIfNecessary(true);
nativeView.sizeChangedListener = null;
nativeView.layoutManager = null;
this._listViewAdapter = null;
this._itemTouchHelper = null;
this._simpleItemTouchCallback = null;
this.disposeViewHolderViews();
this.clearTemplateTypes();
super.disposeNativeView();
}
onLoaded() {
super.onLoaded();
this.attachScrollListenerIfNecessary();
}
// onUnloaded() {
// super.onUnloaded();
// this.detachScrollListenerIfNecessary();
// }
getViewForItemAtIndex(index) {
return this.enumerateViewHolders((v) => (v.getAdapterPosition() === index ? v.view : undefined));
}
//@ts-ignore
set spanSize(inter) {
if (!(typeof inter === 'function')) {
return;
}
this._getSpanSize = inter;
const layoutManager = this.layoutManager;
if (layoutManager && layoutManager['setSpanSizeLookup']) {
if (inter) {
layoutManager['setSpanSizeLookup'](new com.nativescript.collectionview.SpanSizeLookup(new com.nativescript.collectionview.SpanSizeLookup.Interface({
getSpanSize: (position) => {
const dataItem = this.getItemAtIndex(position);
return Math.min(inter(dataItem, position), this.currentSpanCount);
},
})));
}
else {
layoutManager['setSpanSizeLookup'](null);
}
}
}
get spanSize() {
return this._getSpanSize;
}
nativeGetItemOverlap(position) {
const item = this.getItemAtIndex(position);
const result = this.itemOverlap(item, position);
this.decoratorBuffer[0] = Length.toDevicePixels(result[0], 0);
this.decoratorBuffer[1] = Length.toDevicePixels(result[1], 0);
this.decoratorBuffer[2] = Length.toDevicePixels(result[2], 0);
this.decoratorBuffer[3] = Length.toDevicePixels(result[3], 0);
return this.decoratorBuffer;
}
[itemOverlapProperty.setNative](value) {
if (typeof value === 'function') {
if (!this.decorator) {
this.decoratorListener = new com.nativescript.collectionview.OverlapDecoration.OverlapDecorationListener({
getItemOverlap: this.nativeGetItemOverlap.bind(this),
});
this.decorator = new com.nativescript.collectionview.OverlapDecoration(this.decoratorListener);
this.nativeViewProtected.addItemDecoration(this.decorator);
}
if (!this.decoratorBuffer) {
this.decoratorBuffer = createArrayBuffer(4, false, true);
}
}
else {
if (this.decorator) {
this.nativeViewProtected.removeItemDecoration(this.decorator);
this.decorator = null;
}
this.decoratorListener = null;
}
}
attachScrollListenerIfNecessary() {
const nativeView = this.nativeViewProtected;
if (this._scrollOrLoadMoreChangeCount > 0 && nativeView && !nativeView.scrollListener) {
this._nScrollListener = new com.nativescript.collectionview.OnScrollListener.Listener({
onScrollStateChanged: this.onScrollStateChanged.bind(this),
onScrolled: this.onScrolled.bind(this),
});
const scrollListener = new com.nativescript.collectionview.OnScrollListener(this._nScrollListener);
nativeView.addOnScrollListener(scrollListener);
nativeView.scrollListener = scrollListener;
}
}
detachScrollListenerIfNecessary(force = false) {
const nativeView = this.nativeViewProtected;
if (force || (this._scrollOrLoadMoreChangeCount === 0 && nativeView)) {
if (nativeView.scrollListener) {
this.nativeView.removeOnScrollListener(nativeView.scrollListener);
nativeView.scrollListener = null;
}
this._nScrollListener = null;
}
}
computeScrollEventData(view, eventName, dx, dy) {
const horizontal = this.isHorizontal();
const offset = horizontal ? view.computeHorizontalScrollOffset() : view.computeVerticalScrollOffset();
const range = horizontal ? view.computeHorizontalScrollRange() : view.computeVerticalScrollRange();
const extent = horizontal ? view.computeHorizontalScrollExtent() : view.computeVerticalScrollExtent();
return {
object: this,
eventName,
scrollOffset: offset / Utils.layout.getDisplayDensity(),
scrollSize: (range - extent) / Utils.layout.getDisplayDensity(),
scrollOffsetPercentage: offset / (range - extent),
dx,
dy,
};
}
onScrolled(view, dx, dy) {
if (!this || !this.scrolling) {
return;
}
if (this.needsScrollStartEvent) {
this.needsScrollStartEvent = false;
if (this.hasListeners(CollectionViewBase.scrollStartEvent)) {
this.notify(this.computeScrollEventData(view, CollectionViewBase.scrollStartEvent, dx, dy));
}
}
if (this.hasListeners(CollectionViewBase.scrollEvent)) {
this.notify(this.computeScrollEventData(view, CollectionViewBase.scrollEvent, dx, dy));
}
if (this.hasListeners(CollectionViewBase.loadMoreItemsEvent) && this.items) {
const layoutManager = view.getLayoutManager();
if (layoutManager['findLastCompletelyVisibleItemPosition']) {
const lastVisibleItemPos = layoutManager['findLastCompletelyVisibleItemPosition']();
const loadMoreItemIndex = this.items.length - this.loadMoreThreshold;
if (lastVisibleItemPos === loadMoreItemIndex) {
this.notify({ eventName: CollectionViewBase.loadMoreItemsEvent });
}
}
else if (layoutManager['findLastCompletelyVisibleItemPositions'] && layoutManager['getSpanCount']) {
let positions = Array.create('int', layoutManager['getSpanCount']());
positions = layoutManager['findLastCompletelyVisibleItemPositions'](positions);
let lastVisibleItemPos = 0;
for (let i = 0; i < positions.length; i++) {
if (positions[i] > lastVisibleItemPos) {
lastVisibleItemPos = positions[i];
}
}
const loadMoreItemIndex = this.items.length - this.loadMoreThreshold;
if (lastVisibleItemPos >= loadMoreItemIndex) {
this.notify({ eventName: CollectionViewBase.loadMoreItemsEvent });
}
}
}
}
onScrollStateChanged(view, newState) {
if (this.scrolling && newState === 0) {
// SCROLL_STATE_IDLE
this.scrolling = false;
if (this.hasListeners(CollectionViewBase.scrollEndEvent)) {
this.notify(this.computeScrollEventData(view, CollectionViewBase.scrollEndEvent));
}
}
else if (!this.scrolling && newState === 1) {
//SCROLL_STATE_DRAGGING
this.needsScrollStartEvent = true;
this.scrolling = true;
}
}
addEventListener(arg, callback, thisArg) {
super.addEventListener(arg, callback, thisArg);
if (arg === CollectionViewBase.scrollEvent || arg === CollectionViewBase.scrollStartEvent || arg === CollectionViewBase.scrollEndEvent || arg === CollectionViewBase.loadMoreItemsEvent) {
this._scrollOrLoadMoreChangeCount++;
this.attachScrollListenerIfNecessary();
}
}
removeEventListener(arg, callback, thisArg) {
super.removeEventListener(arg, callback, thisArg);
if (arg === CollectionViewBase.scrollEvent || arg === CollectionViewBase.scrollStartEvent || arg === CollectionViewBase.scrollEndEvent || arg === CollectionViewBase.loadMoreItemsEvent) {
this._scrollOrLoadMoreChangeCount--;
this.detachScrollListenerIfNecessary();
}
}
//@ts-ignore
get android() {
return this.nativeView;
}
get layoutManager() {
return this.nativeViewProtected && this.nativeViewProtected.layoutManager;
}
_getViewLayoutParams() {
if (this.isHorizontal()) {
if (!this._hlayoutParams) {
LayoutParams = LayoutParams || android.view.ViewGroup.LayoutParams;
this._hlayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
}
return this._hlayoutParams;
}
else {
if (!this._vlayoutParams) {
LayoutParams = LayoutParams || android.view.ViewGroup.LayoutParams;
this._vlayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
return this._vlayoutParams;
}
}
setNativePoolSize(key, nativeIndex) {
if (this.desiredPoolSize.has(key)) {
this.nativeViewProtected.getRecycledViewPool().setMaxRecycledViews(nativeIndex, this.desiredPoolSize.get(key));
}
else {
if (this.defaultPoolSize >= 0) {
this.nativeViewProtected.getRecycledViewPool().setMaxRecycledViews(nativeIndex, this.defaultPoolSize);
}
}
}
setPoolSizes() {
if (!this.nativeViewProtected || !this.templateTypeNumberString) {
return;
}
this.desiredPoolSize.forEach((v, k) => {
if (this.templateTypeNumberString.has(k)) {
this.nativeViewProtected.getRecycledViewPool().setMaxRecycledViews(this.templateTypeNumberString.get(k), v);
}
});
}
setPoolSize(key, size) {
this.desiredPoolSize.set(key, size);
this.setPoolSizes();
}
[paddingTopProperty.getDefault]() {
return { value: this._defaultPaddingTop, unit: 'px' };
}
[paddingTopProperty.setNative](value) {
this._setPadding({ top: this.effectivePaddingTop });
}
[paddingRightProperty.getDefault]() {
return { value: this._defaultPaddingRight, unit: 'px' };
}
[paddingRightProperty.setNative](value) {
this._setPadding({ right: this.effectivePaddingRight });
}
[paddingBottomProperty.getDefault]() {
return { value: this._defaultPaddingBottom, unit: 'px' };
}
[paddingBottomProperty.setNative](value) {
this._setPadding({ bottom: this.effectivePaddingBottom });
}
[paddingLeftProperty.getDefault]() {
return { value: this._defaultPaddingLeft, unit: 'px' };
}
[paddingLeftProperty.setNative](value) {
this._setPadding({ left: this.effectivePaddingLeft });
}
[orientationProperty.getDefault]() {
return 'vertical';
}
[orientationProperty.setNative](value) {
const layoutManager = this.layoutManager;
if (!layoutManager || !layoutManager['setOrientation']) {
return;
}
if (this.isHorizontal()) {
layoutManager['setOrientation'](0);
}
else {
layoutManager['setOrientation'](1);
}
this.updateScrollBarVisibility(this.scrollBarIndicatorVisible);
}
[isScrollEnabledProperty.setNative](value) {
const layoutManager = this.layoutManager;
if (layoutManager && layoutManager.setScrollEnabled) {
layoutManager.setScrollEnabled(value);
}
}
[reverseLayoutProperty.setNative](value) {
const layoutManager = this.layoutManager;
if (layoutManager && layoutManager.setReverseLayout) {
layoutManager.setReverseLayout(value);
// layoutManager['setStackFromEnd'](value);
}
}
[nestedScrollingEnabledProperty.setNative](value) {
this.nativeViewProtected.setNestedScrollingEnabled(value);
}
[extraLayoutSpaceProperty.setNative](value) {
const layoutManager = this.layoutManager;
if (layoutManager && layoutManager['setExtraLayoutSpace']) {
layoutManager['setExtraLayoutSpace'](value);
}
}
[itemViewCacheSizeProperty.setNative](value) {
this.nativeViewProtected.setItemViewCacheSize(value);
}
[scrollBarIndicatorVisibleProperty.getDefault]() {
return true;
}
[scrollBarIndicatorVisibleProperty.setNative](value) {
this.updateScrollBarVisibility(value);
}
updateScrollBarVisibility(value) {
if (!this.nativeViewProtected) {
return;
}
if (this.orientation === 'horizontal') {
this.nativeViewProtected.setHorizontalScrollBarEnabled(value);
}
else {
this.nativeViewProtected.setVerticalScrollBarEnabled(value);
}
}
enumerateViewHolders(cb) {
let result, v;
for (let it = this._viewHolders.values(), cellItemView = null; (cellItemView = it.next().value);) {
if (cellItemView['position'] === undefined) {
continue;
}
result = cb(cellItemView);
if (result) {
return result;
}
}
return result;
}
async enumerateViewHoldersAsync(cb) {
let result, v;
for (let it = this._viewHolders.values(), cellItemView = null; (cellItemView = it.next().value);) {
if (cellItemView['position'] === undefined) {
continue;
}
result = await cb(cellItemView);
if (result) {
return result;
}
}
return result;
}
startDragging(index) {
if (this.reorderEnabled && this._itemTouchHelper) {
// let viewHolder: CollectionViewCellHolder;
const viewHolder = this.enumerateViewHolders((v) => (v.getAdapterPosition() === index ? v : undefined));
if (viewHolder) {
this.startViewHolderDragging(index, viewHolder);
}
}
}
startViewHolderDragging(index, viewHolder) {
// isDragging is to prevent longPress from triggering and starting a new drag
// when triggered manually
if (!this.isDragging && this.shouldMoveItemAtIndex(index)) {
this.isDragging = true;
this._itemTouchHelper.startDrag(viewHolder);
}
}
onReorderLongPress(motionEvent) {
const collectionView = this.nativeViewProtected;
if (!collectionView) {
return;
}
const view = collectionView.findChildViewUnder(motionEvent.getX(), motionEvent.getY());
const viewHolder = view != null ? collectionView.getChildViewHolder(view) : null;
if (viewHolder) {
this.startViewHolderDragging(viewHolder.getAdapterPosition(), viewHolder);
}
}
_reorderItemInSource(oldPosition, newPosition) {
const adapter = this._listViewAdapter;
// 3. Tell adapter to render the model update.
adapter.notifyItemMoved(oldPosition, newPosition);
// on android _reorderItemInSource is call on every "move" and needs to update the adapter/items
// we will call events only at then end
super._reorderItemInSource(oldPosition, newPosition, false);
}
[_a = reorderLongPressEnabledProperty.setNative](value) {
if (value) {
if (!this._longPressGesture) {
this._longPressGesture = new androidx.core.view.GestureDetectorCompat(this._context, new LongPressGestureListenerImpl(new WeakRef(this)));
this._itemTouchListerner = new androidx.recyclerview.widget.RecyclerView.OnItemTouchListener({
onInterceptTouchEvent: (view, event) => {
if (this.reorderEnabled && this._longPressGesture) {
this._longPressGesture.onTouchEvent(event);
}
return false;
},
onTouchEvent: (param0, param1) => { },
onRequestDisallowInterceptTouchEvent: (disallowIntercept) => { },
});
}
this.nativeViewProtected.addOnItemTouchListener(this._itemTouchListerner);
}
else {
if (this._itemTouchListerner) {
this.nativeViewProtected.removeOnItemTouchListener(this._itemTouchListerner);
}
}
}
[_b = reorderingEnabledProperty.setNative](value) {
if (value) {
if (!this._simpleItemTouchCallback) {
const ItemTouchHelper = androidx.recyclerview.widget.ItemTouchHelper;
this._simpleItemTouchCallback = new SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.START | ItemTouchHelper.END, 0);
this._simpleItemTouchCallback.owner = new WeakRef(this);
this._itemTouchHelper = new androidx.recyclerview.widget.ItemTouchHelper(this._simpleItemTouchCallback);
this._itemTouchHelper.attachToRecyclerView(this.nativeViewProtected);
}
}
}
onItemViewLoaderChanged() {
if (this.itemViewLoader) {
this.refresh();
}
}
onItemTemplateSelectorChanged(oldValue, newValue) {
super.onItemTemplateSelectorChanged(oldValue, newValue);
this.clearTemplateTypes();
this.refresh();
}
onItemTemplateChanged(oldValue, newValue) {
super.onItemTemplateChanged(oldValue, newValue); // TODO: update current template with the new one
this.refresh();
}
onItemTemplatesChanged(oldValue, newValue) {
super.onItemTemplatesChanged(oldValue, newValue); // TODO: update current template with the new one
this.refresh();
}
updateSpanCount(requestLayout = true) {
if (this.mInPropertiesSet) {
this.mShouldUpdateSpanCount = true;
return false;
}
this.mShouldUpdateSpanCount = false;
const layoutManager = this.layoutManager;
if (layoutManager && layoutManager['setSpanCount']) {
const newValue = (this.currentSpanCount = this.computeSpanCount());
if (newValue !== layoutManager['getSpanCount']()) {
layoutManager['setSpanCount'](newValue);
if (requestLayout) {
layoutManager.requestLayout();
}
return true;
}
}
return false;
}
updateInnerSize() {
if (this.mInPropertiesSet) {
this.mShouldUpdateInnerSize = true;
return false;
}
this.mShouldUpdateInnerSize = false;
const result = super.updateInnerSize();
if (result) {
this.updateSpanCount();
}
// there is no need to call refresh if it was triggered before with same size.
// this refresh is just to handle size change
const layoutKey = this._innerWidth + '_' + this._innerHeight;
if (this._isDataDirty || (this._lastLayoutKey && this._lastLayoutKey !== layoutKey)) {
// setTimeout(() => this.refresh(false), 0);
}
this._lastLayoutKey = layoutKey;
return result;
}
_onColWidthPropertyChanged(oldValue, newValue) {
this.updateSpanCount();
super._onColWidthPropertyChanged(oldValue, newValue);
}
_onRowHeightPropertyChanged(oldValue, newValue) {
this.updateSpanCount();
super._onRowHeightPropertyChanged(oldValue, newValue);
}
onLayout(left, top, right, bottom) {
this._layedOut = true;
// super.onLayout(left, top, right, bottom);
const p = CollectionViewBase.plugins[this.layoutStyle];
if (p && p.onLayout) {
p.onLayout(this, left, top, right, bottom);
}
this.plugins.forEach((k) => {
const p = CollectionViewBase.plugins[k];
p.onLayout && p.onLayout(this, left, top, right, bottom);
});
}
onMeasure(widthMeasureSpec, heightMeasureSpec) {
const lastLayoutKey = this._lastLayoutKey;
this.updateInnerSize();
if (lastLayoutKey !== this._lastLayoutKey) {
//we need to refresh visible cells so that they measure to the new size
// for some reason it gets animated even with setSupportsChangeAnimations(false)
// so we clear until it is done
const nativeView = this.nativeViewProtected;
const animator = nativeView.getItemAnimator();
nativeView.setItemAnimator(null);
this.refreshVisibleItems();
setTimeout(() => {
nativeView.setItemAnimator(animator);
}, 0);
}
const p = CollectionViewBase.plugins[this.layoutStyle];
if (p && p.onMeasure) {
p.onMeasure(this, widthMeasureSpec, heightMeasureSpec);
}
this.plugins.forEach((k) => {
const p = CollectionViewBase.plugins[k];
p.onMeasure && p.onMeasure(this, widthMeasureSpec, heightMeasureSpec);
});
}
onSourceCollectionChanged(event) {
if (!this._listViewAdapter || this._dataUpdatesSuspended) {
return;
}
if (Trace.isEnabled()) {
CLog(CLogTypes.log, 'onItemsChanged', event.action, event.index, event.addedCount, event.removed, event.removed && event.removed.length);
}
switch (event.action) {
case ChangeType.Delete: {
this._listViewAdapter.notifyItemRangeRemoved(event.index, event.removed.length);
return;
}
case ChangeType.Add: {
if (event.addedCount > 0) {
this._listViewAdapter.notifyItemRangeInserted(event.index, event.addedCount);
}
// Reload the items to avoid duplicate Load on Demand indicators:
return;
}
case ChangeType.Update: {
if (event.addedCount > 0) {
this._listViewAdapter.notifyItemRangeChanged(event.index, event.addedCount);
}
return;
}
case ChangeType.Splice: {
const added = event.addedCount;
const removed = (event.removed && event.removed.length) || 0;
if (added > 0 && added === removed) {
// notifyItemRangeChanged wont create a fade effect
if (!this.animateItemUpdate) {
this._listViewAdapter.notifyItemRangeChanged(event.index, added);
}
else {
this._listViewAdapter.notifyItemRangeRemoved(event.index, added);
this._listViewAdapter.notifyItemRangeInserted(event.index, added);
}
}
else {
if (!this.animateItemUpdate) {
if (added > removed) {
if (removed > 0) {
this._listViewAdapter.notifyItemRangeChanged(event.index, removed);
}
this._listViewAdapter.notifyItemRangeInserted(event.index + removed, added - removed);
}
else {
if (added > 0) {
this._listViewAdapter.notifyItemRangeChanged(event.index, added);
}
this._listViewAdapter.notifyItemRangeRemoved(event.index + added, removed - added);
}
}
else {
if (event.removed && event.removed.length > 0) {
this._listViewAdapter.notifyItemRangeRemoved(event.index, event.removed.length);
}
if (event.addedCount > 0) {
this._listViewAdapter.notifyItemRangeInserted(event.index, event.addedCount);
}
}
}
return;
}
}
this._listViewAdapter.notifyDataSetChanged();
}
eachChild(callback) {
// used for css updates (like theme change)
this.enumerateViewHolders((v) => {
const view = v.view;
if (view) {
if (view.parent instanceof CollectionView) {
callback(view);
}
else {
// in some cases (like item is unloaded from another place (like angular) view.parent becomes undefined)
if (view.parent) {
callback(view.parent);
}
}
}
});
}
async eachChildAsync(callback) {
return this.enumerateViewHoldersAsync(async (v) => {
const view = v.view;
if (view) {
if (view.parent instanceof CollectionView) {
await callback(view);
}
else {
// in some cases (like item is unloaded from another place (like angular) view.parent becomes undefined)
if (view.parent) {
await callback(view.parent);
}
}
}
});
}
refreshVisibleItems() {
const view = this.nativeViewProtected;
if (!view) {
return;
}
const ids = Array.from(this.bindedViewHolders).sort((a, b) => a - b);
this._listViewAdapter.notifyItemRangeChanged(ids[0], ids[ids.length - 1] - ids[0] + 1);
}
isItemAtIndexVisible(index) {
const view = this.nativeViewProtected;
if (!view) {
return false;
}
const layoutManager = this.layoutManager;
if (layoutManager['findFirstVisibleItemPosition']) {
const first = layoutManager.findFirstVisibleItemPosition();
const last = layoutManager.findLastVisibleItemPosition();
return index >= first && index <= last;
}
return false;
}
findFirstVisibleItemIndex() {
const view = this.nativeViewProtected;
if (!view) {
return -1;
}
const layoutManager = this.layoutManager;
if (layoutManager['findFirstVisibleItemPosition']) {
return layoutManager.findFirstVisibleItemPosition();
}
return -1;
}
findLastVisibleItemIndex() {
const view = this.nativeViewProtected;
if (!view) {
return -1;
}
const layoutManager = this.layoutManager;
if (layoutManager['findLastVisibleItemPosition']) {
return layoutManager.findLastVisibleItemPosition();
}
return -1;
}
refresh(forceRefresh = false, updateSpanCountRequestsLayout = false) {
if (this.mInPropertiesSet) {
this.mShouldRefresh = true;
return;
}
this.mShouldRefresh = false;
const view = this.nativeViewProtected;
if (!view) {
return;
}
// seems like we refresh sooner
// not sure why it was needed before and not now.
if (!forceRefresh && (!this.isLoaded || !this.nativeView)) {
this._isDataDirty = true;
return;
}
this._isDataDirty = false;
this._lastLayoutKey = this._innerWidth + '_' + this._innerHeight;
let adapter = this._listViewAdapter;
if (!adapter) {
adapter = this._listViewAdapter = this.createComposedAdapter(view);
adapter.setHasStableIds(!!this._itemIdGenerator);
view.setAdapter(adapter);
}
else if (!view.getAdapter()) {
view.setAdapter(adapter);
}
this.updateSpanCount(updateSpanCountRequestsLayout);
adapter.notifyDataSetChanged();
this.notify({ eventName: CollectionViewBase.dataPopulatedEvent });
}
//@ts-ignore
get scrollOffset() {
const view = this.nativeViewProtected;
if (!view) {
return 0;
}
return (this.isHorizontal() ? view.computeHorizontalScrollOffset() : view.computeVerticalScrollOffset()) / Utils.layout.getDisplayDensity();
}
get verticalOffsetX() {
const view = this.nativeViewProtected;
if (!view) {
return 0;
}
return view.computeHorizontalScrollOffset() / Utils.layout.getDisplayDensity();
}
get verticalOffsetY() {
const view = this.nativeViewProtected;
if (!view) {
return 0;
}
return view.computeVerticalScrollOffset() / Utils.layout.getDisplayDensity();
}
scrollToIndex(index, animated = true, snap = SnapPosition.START) {
const view = this.nativeViewProtected;
if (!view) {
return;
}
if (animated) {
view.smoothScrollToPosition(index, snap);
}
else {
view.scrollToPosition(index);
}
}
scrollToOffset(offSetValue) {
const view = this.nativeViewProtected;
if (view && this.isScrollEnabled) {
if (this.orientation === 'horizontal') {
view.scrollBy(offSetValue, 0);
}
else {
view.scrollBy(0, offSetValue);
}
}
}
_setPadding(newPadding) {
const nativeView = this.nativeViewProtected;
const padding = {
top: nativeView.getPaddingTop(),
right: nativeView.getPaddingRight(),
bottom: nativeView.getPaddingBottom(),
left: nativeView.getPaddingLeft(),
};
// tslint:disable-next-line:prefer-object-spread
const newValue = Object.assign(padding, newPadding);
nativeView.setClipToPadding(false);
nativeView.setPadding(newValue.left, newValue.top, newValue.right, newValue.bottom);
this.updateInnerSize();
}
createComposedAdapter(recyclerView) {
const adapter = new com.nativescript.collectionview.Adapter();
adapter.adapterInterface = new com.nativescript.collectionview.AdapterInterface({
getItemId: this.getItemId.bind(this),
getItemViewType: this.getItemViewType.bind(this),
getItemCount: this.getItemCount.bind(this),
onCreateViewHolder: this.onCreateViewHolder.bind(this),
onBindViewHolder: this.onBindViewHolder.bind(this),
onViewRecycled: this.onViewRecycled.bind(this),
});
// const composedAdapter = new com.h6ah4i.android.widget.advrecyclerview.composedadapter.ComposedAdapter();
// composedAdapter.addAdapter(new CollectionViewAdapter(new WeakRef(this)));
return adapter;
}
getItemCount() {
return this.items ? this.items.length : 0;
}
getItem(i) {
if (this.items && i < this.items.length) {
return this.getItemAtIndex(i);
}
return null;
}
getItemId(i) {
let id = -1;
if (this._itemIdGenerator && this.items) {
const item = this.getItemAtIndex(i);
id = this._itemIdGenerator(item, i, this.items);
}
return long(id);
}
onItemIdGeneratorChanged(oldValue, newValue) {
super.onItemIdGeneratorChanged(oldValue, newValue);
if (this._listViewAdapter) {
this._listViewAdapter.setHasStableIds(!!newValue);
}
}
clearTemplateTypes() {
this._currentNativeItemType = 0;
this.templateTypeNumberString.clear();
this.templateStringTypeNumber.clear();
}
getItemViewType(position) {
let selectorType = 'default';
if (this._itemTemplateSelector) {
const selector = this._itemTemplateSelector;
const dataItem = this.getItemAtIndex(position);
if (dataItem) {
selectorType = selector.call(this, dataItem, position, this.items);
}
}
return this.templateKeyToNativeItem(selectorType);
// if (!this.templateTypeNumberString.has(selectorType)) {
// resultType = this._currentNativeItemType;
// this.templateTypeNumberString.set(selectorType, resultType);
// this.templateStringTypeNumber.set(resultType, selectorType);
// this._currentNativeItemType++;
// } else {
// resultType = this.templateTypeNumberString.get(selectorType);
// }
// return resultType;
}
templateKeyToNativeItem(key) {
if (!this.templateTypeNumberString) {
this.templateTypeNumberString = new Map();
this._currentNativeItemType = 0;
this._itemTemplatesInternal.forEach((v, i) => {
this.templateTypeNumberString.set(v.key, this._currentNativeItemType);
this.templateStringTypeNumber.set(this._currentNativeItemType, v.key);
this.setNativePoolSize(v.key, this._currentNativeItemType);
this._currentNativeItemType++;
});
this._currentNativeItemType = Math.max(this._itemTemplatesInternal.size, 100);
// templates will be numbered 0,1,2,3... for named templates
// default/unnamed templates will be numbered 100, 101, 102, 103...
}
if (!this.templateTypeNumberString.has(key)) {
this.templateTypeNumberString.set(key, this._currentNativeItemType);
this.templateStringTypeNumber.set(this._currentNativeItemType, key);
this.setNativePoolSize(key, this._currentNativeItemType);
this._currentNativeItemType++;
}
return this.templateTypeNumberString.get(key);
}
// public nativeItemToTemplateKey(item: number): string {
// let result: string;
// this.templateTypeNumberString?.forEach((value, key, map) => {
// if (value === item) {
// result = key;
// }
// }, this);
// return result;
// }
disposeViewHolderViews() {
this.enumerateViewHolders((v) => {
const view = v.view;
if (view) {
this.notifyForItemAtIndex(CollectionViewBase.itemDisposingEvent, view, v.getAdapterPosition(), view.bindingContext, v);
if (view.isLoaded) {
view.callUnloaded();
}
view._isAddedToNativeVisualTree = false;
view._tearDownUI();
}
v.view = null;
v.clickListener = null;
});
this._viewHolders = new Set();
}
getKeyByValue(viewType) {
return this.templateStringTypeNumber.get(viewType);
}
onCreateViewHolder(parent, viewType) {
const start = Date.now();
let view = this.getViewForViewType(ListViewViewTypes.ItemView, this.getKeyByValue(viewType));
const isNonSync = view === undefined;
// dont create unecessary StackLayout if template.createView returns. Will happend when not using Vue or angular
if (isNonSync || view instanceof ProxyViewContainer) {
const parentView = new ContentView();
parentView.id = 'collectionViewHolder';
view = parentView;
}
//@ts-ignore
view.parent = this;
view._setupAsRootView(this._context);
view._isAddedToNativeVisualTree = true;
view.callLoaded();
if (!CollectionViewCellHolder) {
CollectionViewCellHolder = com.nativescript.collectionview.CollectionViewCellHolder;
}
const holder = new CollectionViewCellHolder(view.nativeView);
const collectionView = this;
const clickListener = new android.view.View.OnClickListener({
onClick: () => {
const position = holder.getAdapterPosition();
collectionView.notify({
eventName: CollectionViewBase.itemTapEvent,
object: collectionView,
index: position,
item: collectionView.getItem(position),
view: holder.view,
});
},
});
view.nativeView.setOnClickListener(clickListener);
holder.clickListener = clickListener;
holder.view = view;
const layoutParams = this._getViewLayoutParams();
view.nativeView.setLayoutParams(layoutParams);
if (isNonSync) {
holder['defaultItemView'] = true;
}
this._viewHolders.add(holder);
if (Trace.isEnabled()) {
CLog(CLogTypes.log, 'onCreateViewHolder', this, this.nativeView, viewType, this.getKeyByValue(viewType), holder, Date.now() - start, 'ms');
}
return holder;
}
notifyForItemAtIndex(eventName, view, index, bindingContext, native) {
const args = { eventName, object: this, index, view, ios: native, bindingContext };
this.notify(args);
return args;
}
onBindViewHolder(holder, position) {
const start = Date.now();
if (Trace.isEnabled()) {
CLog(CLogTypes.log, 'onBindViewHolder', this, this.nativeView, position, holder, holder.view);
}
let view = holder.view;
const isNonSync = holder['defaultItemView'] === true;
view = isNonSync ? view.content : view;
const bindingContext = this._prepareItem(view, position);
if (holder['position'] !== undefined) {
this.bindedViewHolders.delete(holder['position']);
}
holder['position'] = position;
this.bindedViewHolders.add(holder['position']);
const args = this.notifyForItemAtIndex(CollectionViewBase.itemLoadingEvent, view, position, bindingContext, holder);
if (isNonSync && args.view !== view) {
view = args.view;
// the view has been changed on the event handler
holder.view.content = args.view;
}
view.bindingContext = bindingContext;
view.n