mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 19:26:42 +08:00

* chore: move tns-core-modules to nativescript-core * chore: preparing compat generate script * chore: add missing definitions * chore: no need for http-request to be private * chore: packages chore * test: generate tests for tns-core-modules * chore: add anroid module for consistency * chore: add .npmignore * chore: added privateModulesWhitelist * chore(webpack): added bundle-entry-points * chore: scripts * chore: tests changed to use @ns/core * test: add scoped-packages test project * test: fix types * test: update test project * chore: build scripts * chore: update build script * chore: npm scripts cleanup * chore: make the compat pgk work with old wp config * test: generate diff friendly tests * chore: create barrel exports * chore: move files after rebase * chore: typedoc config * chore: compat mode * chore: review of barrels * chore: remove tns-core-modules import after rebase * chore: dev workflow setup * chore: update developer-workflow * docs: experiment with API extractor * chore: api-extractor and barrel exports * chore: api-extractor configs * chore: generate d.ts rollup with api-extractor * refactor: move methods inside Frame * chore: fic tests to use Frame static methods * refactor: create Builder class * refactor: use Builder class in tests * refactor: include Style in ui barrel * chore: separate compat build script * chore: fix tslint errors * chore: update NATIVESCRIPT_CORE_ARGS * chore: fix compat pack * chore: fix ui-test-app build with linked modules * chore: Application, ApplicationSettings, Connectivity and Http * chore: export Trace, Profiling and Utils * refactor: Static create methods for ImageSource * chore: fix deprecated usages of ImageSource * chore: move Span and FormattedString to ui * chore: add events-args and ImageSource to index files * chore: check for CLI >= 6.2 when building for IOS * chore: update travis build * chore: copy Pod file to compat package * chore: update error msg ui-tests-app * refactor: Apply suggestions from code review Co-Authored-By: Martin Yankov <m.i.yankov@gmail.com> * chore: typings and refs * chore: add missing d.ts files for public API * chore: adress code review FB * chore: update api-report * chore: dev-workflow for other apps * chore: api update * chore: update api-report
347 lines
12 KiB
TypeScript
347 lines
12 KiB
TypeScript
import { ItemEventData, ItemsSource } from ".";
|
|
import {
|
|
ListViewBase, View, KeyedTemplate, Length, unsetValue, Observable, Color,
|
|
separatorColorProperty, itemTemplatesProperty
|
|
} from "./list-view-common";
|
|
import { StackLayout } from "../layouts/stack-layout";
|
|
import { ProxyViewContainer } from "../proxy-view-container";
|
|
import { LayoutBase } from "../layouts/layout-base";
|
|
import { profile } from "../../profiling";
|
|
|
|
export * from "./list-view-common";
|
|
|
|
const ITEMLOADING = ListViewBase.itemLoadingEvent;
|
|
const LOADMOREITEMS = ListViewBase.loadMoreItemsEvent;
|
|
const ITEMTAP = ListViewBase.itemTapEvent;
|
|
|
|
interface ItemClickListener {
|
|
new(owner: ListView): android.widget.AdapterView.OnItemClickListener;
|
|
}
|
|
|
|
let ItemClickListener: ItemClickListener;
|
|
|
|
function initializeItemClickListener(): void {
|
|
if (ItemClickListener) {
|
|
return;
|
|
}
|
|
|
|
@Interfaces([android.widget.AdapterView.OnItemClickListener])
|
|
class ItemClickListenerImpl extends java.lang.Object implements android.widget.AdapterView.OnItemClickListener {
|
|
constructor(public owner: ListView) {
|
|
super();
|
|
|
|
return global.__native(this);
|
|
}
|
|
|
|
onItemClick<T extends android.widget.Adapter>(parent: android.widget.AdapterView<T>, convertView: android.view.View, index: number, id: number) {
|
|
const owner = this.owner;
|
|
const view = owner._realizedTemplates.get(owner._getItemTemplate(index).key).get(convertView);
|
|
owner.notify({ eventName: ITEMTAP, object: owner, index: index, view: view });
|
|
}
|
|
}
|
|
|
|
ItemClickListener = ItemClickListenerImpl;
|
|
}
|
|
|
|
export class ListView extends ListViewBase {
|
|
nativeViewProtected: android.widget.ListView;
|
|
private _androidViewId: number = -1;
|
|
|
|
public _realizedItems = new Map<android.view.View, View>();
|
|
public _realizedTemplates = new Map<string, Map<android.view.View, View>>();
|
|
|
|
@profile
|
|
public createNativeView() {
|
|
const listView = new android.widget.ListView(this._context);
|
|
listView.setDescendantFocusability(android.view.ViewGroup.FOCUS_AFTER_DESCENDANTS);
|
|
|
|
// Fixes issue with black random black items when scrolling
|
|
listView.setCacheColorHint(android.graphics.Color.TRANSPARENT);
|
|
|
|
return listView;
|
|
}
|
|
|
|
public initNativeView(): void {
|
|
super.initNativeView();
|
|
this.updateEffectiveRowHeight();
|
|
|
|
const nativeView = this.nativeViewProtected;
|
|
initializeItemClickListener();
|
|
ensureListViewAdapterClass();
|
|
const adapter = new ListViewAdapterClass(this);
|
|
nativeView.setAdapter(adapter);
|
|
(<any>nativeView).adapter = adapter;
|
|
|
|
const itemClickListener = new ItemClickListener(this);
|
|
nativeView.setOnItemClickListener(itemClickListener);
|
|
(<any>nativeView).itemClickListener = itemClickListener;
|
|
|
|
if (this._androidViewId < 0) {
|
|
this._androidViewId = android.view.View.generateViewId();
|
|
}
|
|
nativeView.setId(this._androidViewId);
|
|
}
|
|
|
|
public disposeNativeView() {
|
|
const nativeView = this.nativeViewProtected;
|
|
nativeView.setAdapter(null);
|
|
(<any>nativeView).itemClickListener.owner = null;
|
|
(<any>nativeView).adapter.owner = null;
|
|
this.clearRealizedCells();
|
|
super.disposeNativeView();
|
|
}
|
|
|
|
public onLoaded() {
|
|
super.onLoaded();
|
|
// Without this call itemClick won't be fired... :(
|
|
this.requestLayout();
|
|
}
|
|
|
|
public refresh() {
|
|
const nativeView = this.nativeViewProtected;
|
|
if (!nativeView || !nativeView.getAdapter()) {
|
|
return;
|
|
}
|
|
|
|
// clear bindingContext when it is not observable because otherwise bindings to items won't reevaluate
|
|
this._realizedItems.forEach((view, nativeView) => {
|
|
if (!(view.bindingContext instanceof Observable)) {
|
|
view.bindingContext = null;
|
|
}
|
|
});
|
|
|
|
(<android.widget.BaseAdapter>nativeView.getAdapter()).notifyDataSetChanged();
|
|
}
|
|
|
|
public scrollToIndex(index: number) {
|
|
const nativeView = this.nativeViewProtected;
|
|
if (nativeView) {
|
|
nativeView.setSelection(index);
|
|
}
|
|
}
|
|
|
|
public scrollToIndexAnimated(index: number) {
|
|
const nativeView = this.nativeViewProtected;
|
|
if (nativeView) {
|
|
nativeView.smoothScrollToPosition(index);
|
|
}
|
|
}
|
|
|
|
get _childrenCount(): number {
|
|
return this._realizedItems.size;
|
|
}
|
|
|
|
public eachChildView(callback: (child: View) => boolean): void {
|
|
this._realizedItems.forEach((view, nativeView) => {
|
|
if (view.parent instanceof ListView) {
|
|
callback(view);
|
|
}
|
|
else {
|
|
// in some cases (like item is unloaded from another place (like angular) view.parent becomes undefined)
|
|
if (view.parent) {
|
|
callback(<View>view.parent);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
public _dumpRealizedTemplates() {
|
|
console.log(`Realized Templates:`);
|
|
this._realizedTemplates.forEach((value, index) => {
|
|
console.log(`\t${index}:`);
|
|
value.forEach((value, index) => {
|
|
console.log(`\t\t${index.hashCode()}: ${value}`);
|
|
});
|
|
});
|
|
console.log(`Realized Items Size: ${this._realizedItems.size}`);
|
|
}
|
|
|
|
private clearRealizedCells(): void {
|
|
// clear the cache
|
|
this._realizedItems.forEach((view, nativeView) => {
|
|
if (view.parent) {
|
|
// This is to clear the StackLayout that is used to wrap non LayoutBase & ProxyViewContainer instances.
|
|
if (!(view.parent instanceof ListView)) {
|
|
this._removeView(view.parent);
|
|
}
|
|
view.parent._removeView(view);
|
|
}
|
|
});
|
|
|
|
this._realizedItems.clear();
|
|
this._realizedTemplates.clear();
|
|
}
|
|
|
|
public isItemAtIndexVisible(index: number): boolean {
|
|
let nativeView = this.nativeViewProtected;
|
|
const start = nativeView.getFirstVisiblePosition();
|
|
const end = nativeView.getLastVisiblePosition();
|
|
|
|
return (index >= start && index <= end);
|
|
}
|
|
|
|
[separatorColorProperty.getDefault](): { dividerHeight: number, divider: android.graphics.drawable.Drawable } {
|
|
let nativeView = this.nativeViewProtected;
|
|
|
|
return {
|
|
dividerHeight: nativeView.getDividerHeight(),
|
|
divider: nativeView.getDivider()
|
|
};
|
|
}
|
|
[separatorColorProperty.setNative](value: Color | { dividerHeight: number, divider: android.graphics.drawable.Drawable }) {
|
|
let nativeView = this.nativeViewProtected;
|
|
if (value instanceof Color) {
|
|
nativeView.setDivider(new android.graphics.drawable.ColorDrawable(value.android));
|
|
nativeView.setDividerHeight(1);
|
|
} else {
|
|
nativeView.setDivider(value.divider);
|
|
nativeView.setDividerHeight(value.dividerHeight);
|
|
}
|
|
}
|
|
|
|
[itemTemplatesProperty.getDefault](): KeyedTemplate[] {
|
|
return null;
|
|
}
|
|
[itemTemplatesProperty.setNative](value: KeyedTemplate[]) {
|
|
this._itemTemplatesInternal = new Array<KeyedTemplate>(this._defaultTemplate);
|
|
if (value) {
|
|
this._itemTemplatesInternal = this._itemTemplatesInternal.concat(value);
|
|
}
|
|
|
|
this.nativeViewProtected.setAdapter(new ListViewAdapterClass(this));
|
|
this.refresh();
|
|
}
|
|
}
|
|
|
|
let ListViewAdapterClass;
|
|
function ensureListViewAdapterClass() {
|
|
if (ListViewAdapterClass) {
|
|
return;
|
|
}
|
|
|
|
class ListViewAdapter extends android.widget.BaseAdapter {
|
|
constructor(public owner: ListView) {
|
|
super();
|
|
|
|
return global.__native(this);
|
|
}
|
|
|
|
public getCount() {
|
|
return this.owner && this.owner.items && this.owner.items.length ? this.owner.items.length : 0;
|
|
}
|
|
|
|
public getItem(i: number) {
|
|
if (this.owner && this.owner.items && i < this.owner.items.length) {
|
|
let getItem = (<ItemsSource>this.owner.items).getItem;
|
|
|
|
return getItem ? getItem.call(this.owner.items, i) : this.owner.items[i];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public getItemId(i: number) {
|
|
let item = this.getItem(i);
|
|
let id = i;
|
|
if (this.owner && item && this.owner.items) {
|
|
id = this.owner.itemIdGenerator(item, i, this.owner.items);
|
|
}
|
|
|
|
return long(id);
|
|
}
|
|
|
|
public hasStableIds(): boolean {
|
|
return true;
|
|
}
|
|
|
|
public getViewTypeCount() {
|
|
return this.owner._itemTemplatesInternal.length;
|
|
}
|
|
|
|
public getItemViewType(index: number) {
|
|
let template = this.owner._getItemTemplate(index);
|
|
let itemViewType = this.owner._itemTemplatesInternal.indexOf(template);
|
|
|
|
return itemViewType;
|
|
}
|
|
|
|
@profile
|
|
public getView(index: number, convertView: android.view.View, parent: android.view.ViewGroup): android.view.View {
|
|
//this.owner._dumpRealizedTemplates();
|
|
|
|
if (!this.owner) {
|
|
return null;
|
|
}
|
|
|
|
let totalItemCount = this.owner.items ? this.owner.items.length : 0;
|
|
if (index === (totalItemCount - 1)) {
|
|
this.owner.notify({ eventName: LOADMOREITEMS, object: this.owner });
|
|
}
|
|
|
|
// Recycle an existing view or create a new one if needed.
|
|
let template = this.owner._getItemTemplate(index);
|
|
let view: View;
|
|
if (convertView) {
|
|
view = this.owner._realizedTemplates.get(template.key).get(convertView);
|
|
if (!view) {
|
|
throw new Error(`There is no entry with key '${convertView}' in the realized views cache for template with key'${template.key}'.`);
|
|
}
|
|
}
|
|
else {
|
|
view = template.createView();
|
|
}
|
|
|
|
let args: ItemEventData = {
|
|
eventName: ITEMLOADING, object: this.owner, index: index, view: view,
|
|
android: parent,
|
|
ios: undefined
|
|
};
|
|
|
|
this.owner.notify(args);
|
|
|
|
if (!args.view) {
|
|
args.view = this.owner._getDefaultItemContent(index);
|
|
}
|
|
|
|
if (args.view) {
|
|
if (this.owner._effectiveRowHeight > -1) {
|
|
args.view.height = this.owner.rowHeight;
|
|
}
|
|
else {
|
|
args.view.height = <Length>unsetValue;
|
|
}
|
|
|
|
this.owner._prepareItem(args.view, index);
|
|
if (!args.view.parent) {
|
|
// Proxy containers should not get treated as layouts.
|
|
// Wrap them in a real layout as well.
|
|
if (args.view instanceof LayoutBase &&
|
|
!(args.view instanceof ProxyViewContainer)) {
|
|
this.owner._addView(args.view);
|
|
convertView = args.view.nativeViewProtected;
|
|
} else {
|
|
let sp = new StackLayout();
|
|
sp.addChild(args.view);
|
|
this.owner._addView(sp);
|
|
|
|
convertView = sp.nativeViewProtected;
|
|
}
|
|
}
|
|
|
|
// Cache the view for recycling
|
|
let realizedItemsForTemplateKey = this.owner._realizedTemplates.get(template.key);
|
|
if (!realizedItemsForTemplateKey) {
|
|
realizedItemsForTemplateKey = new Map<android.view.View, View>();
|
|
this.owner._realizedTemplates.set(template.key, realizedItemsForTemplateKey);
|
|
}
|
|
realizedItemsForTemplateKey.set(convertView, args.view);
|
|
this.owner._realizedItems.set(convertView, args.view);
|
|
}
|
|
|
|
return convertView;
|
|
}
|
|
}
|
|
|
|
ListViewAdapterClass = ListViewAdapter;
|
|
}
|