diff --git a/apps/tests/ui/scroll-view/scroll-view-tests.ts b/apps/tests/ui/scroll-view/scroll-view-tests.ts index 1c359919a..ccd0e7407 100644 --- a/apps/tests/ui/scroll-view/scroll-view-tests.ts +++ b/apps/tests/ui/scroll-view/scroll-view-tests.ts @@ -265,6 +265,98 @@ class ScrollLayoutTest extends testModule.UITest { // Check verticalOffset after navigation TKUnit.assertAreClose(this.testView.horizontalOffset, 100, 0.1, "this.testView.horizontalOffset after navigation"); } + + public test_scrollView_vertical_raised_scroll_event() { + this.testView.orientation = enums.Orientation.vertical; + + var scrollY: number; + this.testView.on(scrollViewModule.ScrollView.scrollEvent, (args: scrollViewModule.ScrollEventData) => { + scrollY = args.scrollY; + }); + + this.testView.width = 200; + this.testView.height = 300; + + var btn = new button.Button(); + btn.text = "test"; + btn.height = 500; + this.testView.content = btn; + this.waitUntilTestElementLayoutIsValid(); + + this.testView.scrollToVerticalOffset(100, false); + TKUnit.waitUntilReady(function () { return scrollY > 0; }); + TKUnit.assertEqual(scrollY, this.testView.verticalOffset); + } + + public test_scrollView_horizontal_raised_scroll_event() { + this.testView.orientation = enums.Orientation.horizontal; + + var scrollX: number; + this.testView.on(scrollViewModule.ScrollView.scrollEvent, (args: scrollViewModule.ScrollEventData) => { + scrollX = args.scrollX; + }); + + this.testView.width = 200; + this.testView.height = 300; + + var btn = new button.Button(); + btn.text = "test"; + btn.width = 500; + this.testView.content = btn; + this.waitUntilTestElementLayoutIsValid(); + + this.testView.scrollToHorizontalOffset(100, false); + TKUnit.waitUntilReady(function () { return scrollX > 0; }); + TKUnit.assertEqual(scrollX, this.testView.horizontalOffset); + } + + public test_scrollView_vertical_raised_scroll_event_after_loaded() { + this.testView.orientation = enums.Orientation.vertical; + + this.waitUntilTestElementIsLoaded(); + + var scrollY: number; + this.testView.on(scrollViewModule.ScrollView.scrollEvent, (args: scrollViewModule.ScrollEventData) => { + scrollY = args.scrollY; + }); + + this.testView.width = 200; + this.testView.height = 300; + + var btn = new button.Button(); + btn.text = "test"; + btn.height = 500; + this.testView.content = btn; + this.waitUntilTestElementLayoutIsValid(); + + this.testView.scrollToVerticalOffset(100, false); + TKUnit.waitUntilReady(function () { return scrollY > 0; }); + TKUnit.assertEqual(scrollY, this.testView.verticalOffset); + } + + public test_scrollView_horizontal_raised_scroll_event_after_loaded() { + this.testView.orientation = enums.Orientation.horizontal; + + this.waitUntilTestElementIsLoaded(); + + var scrollX: number; + this.testView.on(scrollViewModule.ScrollView.scrollEvent, (args: scrollViewModule.ScrollEventData) => { + scrollX = args.scrollX; + }); + + this.testView.width = 200; + this.testView.height = 300; + + var btn = new button.Button(); + btn.text = "test"; + btn.width = 500; + this.testView.content = btn; + this.waitUntilTestElementLayoutIsValid(); + + this.testView.scrollToHorizontalOffset(100, false); + TKUnit.waitUntilReady(function () { return scrollX > 0; }); + TKUnit.assertEqual(scrollX, this.testView.horizontalOffset); + } } export function createTestCase(): ScrollLayoutTest { diff --git a/ui/scroll-view/scroll-view-common.ts b/ui/scroll-view/scroll-view-common.ts index fe3c04b17..f9074e34a 100644 --- a/ui/scroll-view/scroll-view-common.ts +++ b/ui/scroll-view/scroll-view-common.ts @@ -1,6 +1,8 @@ import dependencyObservable = require("ui/core/dependency-observable"); import proxy = require("ui/core/proxy"); import enums = require("ui/enums"); +import definition = require("ui/scroll-view"); +import contentView = require("ui/content-view"); function isValidOrientation(value: any): boolean { return value === enums.Orientation.vertical || value === enums.Orientation.horizontal; @@ -13,4 +15,90 @@ export var orientationProperty = new dependencyObservable.Property( dependencyObservable.PropertyMetadataSettings.AffectsLayout, undefined, isValidOrientation) - ); \ No newline at end of file +); + +export class ScrollView extends contentView.ContentView implements definition.ScrollView { + private _scrollChangeCount: number = 0; + public static scrollEvent = "scroll"; + + get orientation(): string { + return this._getValue(orientationProperty); + } + set orientation(value: string) { + this._setValue(orientationProperty, value); + } + + public addEventListener(arg: string, callback: any, thisArg?: any) { + super.addEventListener(arg, callback, thisArg); + + if (arg === definition.ScrollView.scrollEvent) { + this._scrollChangeCount++; + this.attach(); + } + } + + public removeEventListener(arg: string, callback: any, thisArg?: any) { + super.addEventListener(arg, callback, thisArg); + + if (arg === definition.ScrollView.scrollEvent) { + this._scrollChangeCount--; + this.dettach(); + } + } + + public onLoaded() { + super.onLoaded(); + + this.attach(); + } + + public onUnloaded() { + super.onUnloaded(); + + this.dettach(); + } + + private attach() { + if (this._scrollChangeCount > 0 && this.isLoaded) { + this.attachNative(); + } + } + + private dettach() { + if (this._scrollChangeCount === 0 && this.isLoaded) { + this.dettachNative(); + } + } + + protected attachNative() { + // + } + + protected dettachNative() { + // + } + + get horizontalOffset(): number { + return 0; + } + + get verticalOffset(): number { + return 0; + } + + get scrollableWidth(): number { + return 0; + } + + get scrollableHeight(): number { + return 0; + } + + public scrollToVerticalOffset(value: number, animated: boolean) { + // + } + + public scrollToHorizontalOffset(value: number, animated: boolean) { + // + } +} \ No newline at end of file diff --git a/ui/scroll-view/scroll-view.android.ts b/ui/scroll-view/scroll-view.android.ts index 76ee084f7..d66ccea5a 100644 --- a/ui/scroll-view/scroll-view.android.ts +++ b/ui/scroll-view/scroll-view.android.ts @@ -1,9 +1,9 @@ import dependencyObservable = require("ui/core/dependency-observable"); import definition = require("ui/scroll-view"); -import contentView = require("ui/content-view"); import common = require("./scroll-view-common"); import utils = require("utils/utils"); import enums = require("ui/enums"); +import proxy = require("ui/core/proxy"); global.moduleMerge(common, exports); @@ -11,9 +11,10 @@ common.orientationProperty.metadata.onValueChanged = function scrollViewOrientat (data.object)._onOrientationChanged(data.oldValue, data.newValue); } -export class ScrollView extends contentView.ContentView implements definition.ScrollView { +export class ScrollView extends common.ScrollView implements definition.ScrollView { private _android: org.nativescript.widgets.VerticalScrollView | org.nativescript.widgets.HorizontalScrollView; private _androidViewId: number; + private handler: android.view.ViewTreeObserver.OnScrollChangedListener; get android(): android.view.ViewGroup { return this._android; @@ -23,13 +24,6 @@ export class ScrollView extends contentView.ContentView implements definition.Sc return this._android; } - get orientation(): string { - return this._getValue(common.orientationProperty); - } - set orientation(value: string) { - this._setValue(common.orientationProperty, value); - } - get horizontalOffset(): number { if (!this._android) { return 0; @@ -68,8 +62,7 @@ export class ScrollView extends contentView.ContentView implements definition.Sc if (animated) { this._android.smoothScrollTo(0, value); - } - else { + } else { this._android.scrollTo(0, value); } } @@ -81,8 +74,7 @@ export class ScrollView extends contentView.ContentView implements definition.Sc if (animated) { this._android.smoothScrollTo(value, 0); - } - else { + } else { this._android.scrollTo(value, 0); } } @@ -91,8 +83,7 @@ export class ScrollView extends contentView.ContentView implements definition.Sc public _createUI() { if (this.orientation === enums.Orientation.horizontal) { this._android = new org.nativescript.widgets.HorizontalScrollView(this._context); - } - else { + } else { this._android = new org.nativescript.widgets.VerticalScrollView(this._context); } @@ -116,4 +107,28 @@ export class ScrollView extends contentView.ContentView implements definition.Sc } } } + + protected attachNative() { + var that = new WeakRef(this); + this.handler = new android.view.ViewTreeObserver.OnScrollChangedListener({ + onScrollChanged: function () { + var rootScrollView = that.get(); + if (rootScrollView && rootScrollView.android) { + rootScrollView.notify({ + object: rootScrollView, + eventName: definition.ScrollView.scrollEvent, + scrollX: rootScrollView.android.getScrollX() / utils.layout.getDisplayDensity(), + scrollY: rootScrollView.android.getScrollY() / utils.layout.getDisplayDensity() + }); + } + } + }); + + this._android.getViewTreeObserver().addOnScrollChangedListener(this.handler); + } + + protected dettachNative() { + this._android.getViewTreeObserver().removeOnScrollChangedListener(this.handler); + this.handler = null; + } } diff --git a/ui/scroll-view/scroll-view.d.ts b/ui/scroll-view/scroll-view.d.ts index c6b2542a7..169f49854 100644 --- a/ui/scroll-view/scroll-view.d.ts +++ b/ui/scroll-view/scroll-view.d.ts @@ -3,11 +3,22 @@ */ declare module "ui/scroll-view" { import contentView = require("ui/content-view"); + import observable = require("data/observable"); + import dependencyObservable = require("ui/core/dependency-observable"); + + export var orientationProperty: dependencyObservable.Property; /** * Represents a scrollable area that can have content that is larger than its bounds. */ class ScrollView extends contentView.ContentView { + public static orientationProperty: dependencyObservable.Property; + + /** + * String value used when hooking to scroll event. + */ + public static scrollEvent: string; + /** * Gets a value that contains the vertical offset of the scrolled content. */ @@ -46,5 +57,23 @@ declare module "ui/scroll-view" { * Gets or sets direction in which the content can be scrolled. */ orientation: string; + + /** + * A basic method signature to hook an event listener (shortcut alias to the addEventListener method). + * @param eventNames - String corresponding to events (e.g. "propertyChange"). Optionally could be used more events separated by `,` (e.g. "propertyChange", "change"). + * @param callback - Callback function which will be executed when event is raised. + * @param thisArg - An optional parameter which will be used as `this` context for callback execution. + */ + on(eventNames: string, callback: (data: observable.EventData) => void, thisArg?: any); + + /** + * Raised when a scroll event occurs. + */ + on(event: "scroll", callback: (args: ScrollEventData) => void, thisArg?: any); + } + + interface ScrollEventData extends observable.EventData { + scrollX: number; + scrollY: number; } } \ No newline at end of file diff --git a/ui/scroll-view/scroll-view.ios.ts b/ui/scroll-view/scroll-view.ios.ts index 43e3385de..700f6aca8 100644 --- a/ui/scroll-view/scroll-view.ios.ts +++ b/ui/scroll-view/scroll-view.ios.ts @@ -1,27 +1,57 @@ import view = require("ui/core/view"); import definition = require("ui/scroll-view"); -import contentView = require("ui/content-view"); import common = require("./scroll-view-common"); import enums = require("ui/enums"); import utils = require("utils/utils"); global.moduleMerge(common, exports); -export class ScrollView extends contentView.ContentView implements definition.ScrollView { +class UIScrollViewDelegateImpl extends NSObject implements UIScrollViewDelegate { + private _owner: WeakRef; + + public static initWithOwner(owner: WeakRef): UIScrollViewDelegateImpl { + let impl = UIScrollViewDelegateImpl.new(); + impl._owner = owner; + return impl; + } + + public scrollViewDidScroll(sv: UIScrollView): void { + let owner = this._owner.get(); + if (!owner) { + return; + } + + if (owner) { + owner.notify({ + object: owner, + eventName: definition.ScrollView.scrollEvent, + scrollX: owner.horizontalOffset, + scrollY: owner.verticalOffset + }); + } + } + + public static ObjCProtocols = [UIScrollViewDelegate]; +} + +export class ScrollView extends common.ScrollView implements definition.ScrollView { private _scroll: UIScrollView; private _contentMeasuredWidth: number = 0; private _contentMeasuredHeight: number = 0; + private _delegate: UIScrollViewDelegateImpl; constructor() { super(); this._scroll = new UIScrollView(); } - get orientation(): string { - return this._getValue(common.orientationProperty); + protected attachNative() { + this._delegate = UIScrollViewDelegateImpl.initWithOwner(new WeakRef(this)); + this._scroll.delegate = this._delegate; } - set orientation(value: string) { - this._setValue(common.orientationProperty, value); + + protected dettachNative() { + this._scroll.delegate = null; } get horizontalOffset(): number {