Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: shared view pools [wip] #68

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/collectionview/index-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ export abstract class CollectionViewBase extends View implements CollectionViewD
protected _dataUpdatesSuspended = false;
public scrollBarIndicatorVisible: boolean;

public sharedPool: string;

public layoutStyle: string = 'grid';
public plugins: string[] = [];
public static plugins: { [k: string]: Plugin } = {};
Expand Down Expand Up @@ -743,3 +745,8 @@ export const autoReloadItemOnLayoutProperty = new Property<CollectionViewBase, b
valueConverter: booleanConverter
});
autoReloadItemOnLayoutProperty.register(CollectionViewBase);

export const sharedPoolProperty = new Property<CollectionViewBase, string>({
name: 'sharedPool'
});
sharedPoolProperty.register(CollectionViewBase);
114 changes: 62 additions & 52 deletions src/collectionview/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import {
paddingTopProperty,
profile
} from '@nativescript/core';
import { CollectionViewItemEventData, Orientation, reorderLongPressEnabledProperty, reorderingEnabledProperty, reverseLayoutProperty, scrollBarIndicatorVisibleProperty } from '.';
import { CollectionViewItemEventData, Orientation, reorderLongPressEnabledProperty, reorderingEnabledProperty, reverseLayoutProperty, scrollBarIndicatorVisibleProperty, sharedPoolProperty } from '.';
import { CLog, CLogTypes, CollectionViewBase, ListViewViewTypes, isScrollEnabledProperty, orientationProperty } from './index-common';
import { SharedCollectionViewPool, getSharedCollectionViewPool } from './shared-pool';

export * from './index-common';

Expand Down Expand Up @@ -129,18 +130,12 @@ export class CollectionView extends CollectionViewBase {
owner?: WeakRef<CollectionView>;
};

private recyclerListener: androidx.recyclerview.widget.RecyclerView.RecyclerListener;

private templateTypeNumberString = new Map<string, number>();
private templateStringTypeNumber = new Map<number, string>();
private _currentNativeItemType = 0;

private currentSpanCount = 1;

// used to store viewHolder and thus their corresponding Views
// used to "destroy" cells when possible
private _viewHolders = new Set<CollectionViewCellHolder>();

private _scrollOrLoadMoreChangeCount = 0;
private _nScrollListener: com.nativescript.collectionview.OnScrollListener.Listener;
scrolling = false;
Expand All @@ -161,8 +156,24 @@ export class CollectionView extends CollectionViewBase {
public nestedScrollingEnabled: boolean;
public itemViewCacheSize: number;
public extraLayoutSpace: number;
recycledViewPool: com.nativescript.collectionview.RecycledViewPool;
recycledViewPoolDisposeListener: com.nativescript.collectionview.RecycledViewPool.ViewPoolListener;

private _sharedPool: SharedCollectionViewPool;

get templateTarget(): SharedCollectionViewPool | this {
if (this.sharedPool) {
return this._sharedPool;
}
return this;
}

// for simplicity we always store view holders in a shared pool, even if it's only used by this collectionview
private get viewHolders(): Set<CollectionViewCellHolder> {
return this._sharedPool._viewHolders;
}

private set viewHolders(value: Set<CollectionViewCellHolder>) {
this._sharedPool._viewHolders = value;
}

@profile
public createNativeView() {
Expand Down Expand Up @@ -190,40 +201,9 @@ export class CollectionView extends CollectionViewBase {
this.setOnLayoutChangeListener();
super.initNativeView();
const nativeView = this.nativeViewProtected;
this.recycledViewPool = new com.nativescript.collectionview.RecycledViewPool();
this.recycledViewPoolDisposeListener = new com.nativescript.collectionview.RecycledViewPool.ViewPoolListener({
onViewHolderDisposed: (holder: CollectionViewCellHolder) => {
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 as ContentView).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 as any).mListener = this.recycledViewPoolDisposeListener;
const recyclerListener = (this.recyclerListener = new androidx.recyclerview.widget.RecyclerView.RecyclerListener({
onViewRecycled: (holder: CollectionViewCellHolder) => {
if (Trace.isEnabled()) {
CLog(CLogTypes.log, 'onViewRecycled', holder);
}
const isNonSync = holder['defaultItemView'] === true;
const view = isNonSync ? (holder.view as ContentView).content : holder.view;
this.notifyForItemAtIndex(CollectionViewBase.itemRecyclingEvent, view, holder.getAdapterPosition(), view.bindingContext, holder);
}
}));
nativeView.setRecyclerListener(recyclerListener);
nativeView.setRecycledViewPool(this.recycledViewPool);

this.attachSharedPool();

// nativeView.owner = new WeakRef(this);
// nativeView.sizeChangedListener = new com.nativescript.collectionview.SizeChangedListener({
// onSizeChanged: (w, h, oldW, oldH) => this.onSizeChanged(w, h),
Expand Down Expand Up @@ -267,10 +247,7 @@ export class CollectionView extends CollectionViewBase {
// this._realizedItems.clear();

const nativeView = this.nativeViewProtected;
nativeView.setRecyclerListener(null);
nativeView.setRecycledViewPool(null);
this.recycledViewPoolDisposeListener = null;
this.recycledViewPool = null;
this._sharedPool?.detachFromCollectionView(this);
if (nativeView.scrollListener) {
this.nativeView.removeOnScrollListener(nativeView.scrollListener);
nativeView.scrollListener = null;
Expand Down Expand Up @@ -490,6 +467,13 @@ export class CollectionView extends CollectionViewBase {
this.setPoolSizes();
}

getTemplateFromSelector(templateKey) {
if (this.templateTarget !== this) {
return this.templateTarget.getTemplateFromSelector(templateKey);
}
return super.getTemplateFromSelector(templateKey);
}

[paddingTopProperty.getDefault](): CoreTypes.LengthType {
return { value: this._defaultPaddingTop, unit: 'px' };
}
Expand Down Expand Up @@ -576,7 +560,7 @@ export class CollectionView extends CollectionViewBase {
}
private enumerateViewHolders<T = any>(cb: (v: CollectionViewCellHolder) => T) {
let result: T, v: CollectionViewCellHolder;
for (let it = this._viewHolders.values(), cellItemView: CollectionViewCellHolder = null; (cellItemView = it.next().value); ) {
for (let it = this.viewHolders.values(), cellItemView: CollectionViewCellHolder = null; (cellItemView = it.next().value); ) {
result = cb(cellItemView);
if (result) {
return result;
Expand Down Expand Up @@ -826,7 +810,7 @@ export class CollectionView extends CollectionViewBase {
if (!view) {
return;
}
const ids = Array.from(this._viewHolders)
const ids = Array.from(this.viewHolders)
.map((s) => s['position'])
.filter((s) => s !== null)
.sort((a, b) => a - b);
Expand Down Expand Up @@ -1046,7 +1030,7 @@ export class CollectionView extends CollectionViewBase {
v.view = null;
v.clickListener = null;
});
this._viewHolders = new Set();
this.viewHolders = new Set();
}

getKeyByValue(viewType: number) {
Expand Down Expand Up @@ -1095,7 +1079,7 @@ export class CollectionView extends CollectionViewBase {
if (isNonSync) {
holder['defaultItemView'] = true;
}
this._viewHolders.add(holder);
this.viewHolders.add(holder);

if (Trace.isEnabled()) {
CLog(CLogTypes.log, 'onCreateViewHolder', viewType, this.getKeyByValue(viewType));
Expand All @@ -1120,7 +1104,7 @@ export class CollectionView extends CollectionViewBase {
const bindingContext = this._prepareItem(view, position);
holder['position'] = position;

const args = this.notifyForItemAtIndex(CollectionViewBase.itemLoadingEvent, view, position, bindingContext, holder);
const args = this.templateTarget.notifyForItemAtIndex(CollectionViewBase.itemLoadingEvent, view, position, bindingContext, holder);

if (isNonSync && args.view !== view) {
view = args.view;
Expand Down Expand Up @@ -1162,6 +1146,32 @@ export class CollectionView extends CollectionViewBase {
onViewRecycled(holder) {
holder['position'] = null;
}

[sharedPoolProperty.setNative](value: string) {
this.attachSharedPool(value);
}

private attachSharedPool(pool?: SharedCollectionViewPool | string) {
let sharedPool: SharedCollectionViewPool;

if (typeof pool === 'string') {
sharedPool = getSharedCollectionViewPool(pool);
} else if (pool instanceof SharedCollectionViewPool) {
sharedPool = pool;
} else {
sharedPool = getSharedCollectionViewPool(this.sharedPool);
}

if (!sharedPool) {
sharedPool = new SharedCollectionViewPool();
}

if (this._sharedPool !== sharedPool) {
// this._sharedPool?.detachFromCollectionView(this);
}
sharedPool.attachToCollectionView(this);
this._sharedPool = sharedPool;
}
}

interface CollectionViewCellHolder extends com.nativescript.collectionview.CollectionViewCellHolder {
Expand Down
72 changes: 72 additions & 0 deletions src/collectionview/shared-pool-common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Trace, View } from '@nativescript/core';
import { CLog, CLogTypes, CollectionViewBase } from './index-common';

const poolMap = new Map<string, SharedCollectionViewPoolBase>();
export function getSharedCollectionViewPool(name: string): SharedCollectionViewPoolBase | undefined {
return poolMap.get(name);
}

export class SharedCollectionViewPoolBase extends CollectionViewBase {
protected collectionViews = new Set<CollectionViewBase>();
protected _name: string;

set name(value: string) {
if (this._name !== value) {
poolMap.delete(this._name);
}
this._name = value;
poolMap.set(this._name, this);
}

get name(): string {
return this._name;
}

public onLoaded(): void {
super.onLoaded();
poolMap.set(this.name, this);
}

public onUnloaded(): void {
super.onUnloaded();
poolMap.delete(this.name);
}

public attachToCollectionView(collectionView: CollectionViewBase): void {
if (Trace.isEnabled()) {
CLog(CLogTypes.log, 'attachToCollectionView', collectionView);
}

this.collectionViews.add(collectionView);
}

public detachFromCollectionView(collectionView: CollectionViewBase): void {
if (Trace.isEnabled()) {
CLog(CLogTypes.log, 'detachFromCollectionView', collectionView);
}

this.collectionViews.delete(collectionView);
}

public refresh() {
throw new Error('Method not implemented.');
}
public refreshVisibleItems() {
throw new Error('Method not implemented.');
}
public isItemAtIndexVisible(index: number) {
throw new Error('Method not implemented.');
}
public scrollToIndex(index: number, animated: boolean) {
throw new Error('Method not implemented.');
}
public scrollToOffset(value: number, animated?: boolean) {
throw new Error('Method not implemented.');
}
getViewForItemAtIndex(index: number): View {
throw new Error('Method not implemented.');
}
startDragging(index: number) {
throw new Error('Method not implemented.');
}
}
96 changes: 96 additions & 0 deletions src/collectionview/shared-pool.android.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { ContentView, Trace, View } from '@nativescript/core';
import { CLog, CLogTypes, CollectionViewBase } from './index-common';
import { SharedCollectionViewPoolBase } from './shared-pool-common';

interface CollectionViewCellHolder extends com.nativescript.collectionview.CollectionViewCellHolder {
// tslint:disable-next-line:no-misused-new
new (androidView: android.view.View): CollectionViewCellHolder;
view: View;
clickListener: android.view.View.OnClickListener;
}

export class SharedCollectionViewPool extends SharedCollectionViewPoolBase {
private recyclerListener: androidx.recyclerview.widget.RecyclerView.RecyclerListener;
private recycledViewPool: com.nativescript.collectionview.RecycledViewPool;
private recycledViewPoolDisposeListener: com.nativescript.collectionview.RecycledViewPool.ViewPoolListener;

// used to store viewHolder and thus their corresponding Views
// used to "destroy" cells when possible
public _viewHolders = new Set<CollectionViewCellHolder>();

public createNativeView() {
// no need to create anything
return null;
}

public initNativeView(): void {
// const nativeView = this.nativeViewProtected;
this.recycledViewPool = new com.nativescript.collectionview.RecycledViewPool();
this.recycledViewPoolDisposeListener = new com.nativescript.collectionview.RecycledViewPool.ViewPoolListener({
onViewHolderDisposed: (holder: CollectionViewCellHolder) => {
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 as ContentView).content : holder.view;
this.notifyForItemAtIndex(CollectionViewBase.itemDisposingEvent, view, holder.getAdapterPosition(), view.bindingContext, holder);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should likely be fired on the CollectionView instances as well?

if (view && view.isLoaded) {
view.callUnloaded();
}
view._isAddedToNativeVisualTree = false;
//@ts-ignore
view.parent = null;
view._tearDownUI();
}
});
(this.recycledViewPool as any).mListener = this.recycledViewPoolDisposeListener;

this.recyclerListener = new androidx.recyclerview.widget.RecyclerView.RecyclerListener({
onViewRecycled: (holder: CollectionViewCellHolder) => {
if (Trace.isEnabled()) {
CLog(CLogTypes.log, 'onViewRecycled', holder);
}
const isNonSync = holder['defaultItemView'] === true;
const view = isNonSync ? (holder.view as ContentView).content : holder.view;
this.notifyForItemAtIndex(CollectionViewBase.itemRecyclingEvent, view, holder.getAdapterPosition(), view.bindingContext, holder);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also likely be fired for all CollectionView instances as well (perhaps this.notifyForItemAtIndex would just notify self + all attached collectionView instances.

}
});
}

public disposeNativeView() {
this.collectionViews.forEach((collectionView) => {
const nativeView = collectionView.nativeViewProtected;
nativeView.setRecyclerListener(null);
nativeView.setRecycledViewPool(null);
});
this.collectionViews.clear();
this.recycledViewPoolDisposeListener = null;
this.recycledViewPool = null;
super.disposeNativeView();
}

public attachToCollectionView(collectionView: CollectionViewBase): void {
super.attachToCollectionView(collectionView);

const nativeView = collectionView.nativeViewProtected;
nativeView.setRecyclerListener(this.recyclerListener);
nativeView.setRecycledViewPool(this.recycledViewPool);
}

public detachFromCollectionView(collectionView: CollectionViewBase): void {
super.detachFromCollectionView(collectionView);

const nativeView = collectionView.nativeViewProtected;
nativeView.setRecyclerListener(null);
nativeView.setRecycledViewPool(null);
}

notifyForItemAtIndex(eventName: string, view: View, index: number, bindingContext?, native?: any) {
const args = { eventName, object: this, index, view, ios: native, bindingContext };
this.notify(args);
return args as any;
}
}
Loading