From c85d2a7afc385e022edfbc47738991fbd40cb09b Mon Sep 17 00:00:00 2001 From: Vladimir Enchev Date: Mon, 9 Nov 2015 11:57:32 +0200 Subject: [PATCH 1/4] initial commit --- ui/scroll-view/scroll-view.android.ts | 11 +++++++++ ui/scroll-view/scroll-view.d.ts | 13 +++++++++++ ui/scroll-view/scroll-view.ios.ts | 32 +++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/ui/scroll-view/scroll-view.android.ts b/ui/scroll-view/scroll-view.android.ts index 76ee084f7..db5a36cb6 100644 --- a/ui/scroll-view/scroll-view.android.ts +++ b/ui/scroll-view/scroll-view.android.ts @@ -101,6 +101,17 @@ export class ScrollView extends contentView.ContentView implements definition.Sc } this._android.setId(this._androidViewId); + + var that = new WeakRef(this); + this._android.getViewTreeObserver().addOnScrollChangedListener(new android.view.ViewTreeObserver.OnScrollChangedListener({ + onScrollChanged: function () { + var rootScrollView = that.get(); + if (rootScrollView && rootScrollView.android) { + var scrollX = rootScrollView.android.getScrollX(); //for horizontalScrollView + var scrollY = rootScrollView.android.getScrollY(); //for verticalScrollView + } + } + }); } public _onOrientationChanged(oldValue: string, newValue: string) { diff --git a/ui/scroll-view/scroll-view.d.ts b/ui/scroll-view/scroll-view.d.ts index c6b2542a7..a69d750f5 100644 --- a/ui/scroll-view/scroll-view.d.ts +++ b/ui/scroll-view/scroll-view.d.ts @@ -46,5 +46,18 @@ 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 tap event occurs. + */ + on(event: "scroll", callback: (args: observable.EventData) => void, thisArg?: any); } } \ 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..dfe97584d 100644 --- a/ui/scroll-view/scroll-view.ios.ts +++ b/ui/scroll-view/scroll-view.ios.ts @@ -7,14 +7,46 @@ import utils = require("utils/utils"); global.moduleMerge(common, exports); +class UIScrollViewDelegateImpl extends NSObject implements UIScrollViewDelegate { + public static ObjCProtocols = [UIScrollViewDelegate]; + + static new(): UIScrollViewDelegateImpl { + return super.new(); + } + + private _owner: ScrollView; + + public initWithOwner(owner: ScrollView): UIScrollViewDelegateImpl { + this._owner = owner; + return this; + } + + public scrollViewDidScroll(textView: UIScrollView): void { + + } +} + export class ScrollView extends contentView.ContentView implements definition.ScrollView { private _scroll: UIScrollView; private _contentMeasuredWidth: number = 0; private _contentMeasuredHeight: number = 0; + private _delegate: UIScrollViewDelegateImpl; constructor() { super(); this._scroll = new UIScrollView(); + + this._delegate = UIScrollViewDelegateImpl.new().initWithOwner(this); + } + + public onLoaded() { + super.onLoaded(); + this._scroll.delegate = this._delegate; + } + + public onUnloaded() { + this._scroll.delegate = null; + super.onUnloaded(); } get orientation(): string { From 77852c96722b0a3778b59f442abd96fc5ec47a17 Mon Sep 17 00:00:00 2001 From: Vladimir Enchev Date: Tue, 10 Nov 2015 09:19:54 +0200 Subject: [PATCH 2/4] scroll event implemented --- .../tests/ui/scroll-view/scroll-view-tests.ts | 44 +++++++++ ui/scroll-view/scroll-view-common.ts | 92 ++++++++++++++++++- ui/scroll-view/scroll-view.android.ts | 56 +++++------ ui/scroll-view/scroll-view.d.ts | 20 +++- ui/scroll-view/scroll-view.ios.ts | 28 +++--- 5 files changed, 195 insertions(+), 45 deletions(-) diff --git a/apps/tests/ui/scroll-view/scroll-view-tests.ts b/apps/tests/ui/scroll-view/scroll-view-tests.ts index 1c359919a..3446ac9fb 100644 --- a/apps/tests/ui/scroll-view/scroll-view-tests.ts +++ b/apps/tests/ui/scroll-view/scroll-view-tests.ts @@ -265,6 +265,50 @@ 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(this.testView.verticalOffset, scrollY); + } + + 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(this.testView.horizontalOffset, scrollX); + } } export function createTestCase(): ScrollLayoutTest { diff --git a/ui/scroll-view/scroll-view-common.ts b/ui/scroll-view/scroll-view-common.ts index fe3c04b17..9d0836cca 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,92 @@ 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 _scrollEventAttached: boolean; + 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.attach(); + } + } + + public removeEventListener(arg: string, callback: any, thisArg?: any) { + super.addEventListener(arg, callback, thisArg); + + if (arg === definition.ScrollView.scrollEvent) { + this.dettach(); + } + } + + public onLoaded() { + super.onLoaded(); + + this.attach(); + } + + public onUnloaded() { + super.onUnloaded(); + + this.dettach(); + } + + private attach() { + if (!this._scrollEventAttached && this.isLoaded) { + this._scrollEventAttached = true; + + this.attachNative(); + } + } + + private dettach() { + if (this._scrollEventAttached && this.isLoaded) { + this._scrollEventAttached = false; + + 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 db5a36cb6..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); } @@ -101,17 +92,6 @@ export class ScrollView extends contentView.ContentView implements definition.Sc } this._android.setId(this._androidViewId); - - var that = new WeakRef(this); - this._android.getViewTreeObserver().addOnScrollChangedListener(new android.view.ViewTreeObserver.OnScrollChangedListener({ - onScrollChanged: function () { - var rootScrollView = that.get(); - if (rootScrollView && rootScrollView.android) { - var scrollX = rootScrollView.android.getScrollX(); //for horizontalScrollView - var scrollY = rootScrollView.android.getScrollY(); //for verticalScrollView - } - } - }); } public _onOrientationChanged(oldValue: string, newValue: string) { @@ -127,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 a69d750f5..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. */ @@ -56,8 +67,13 @@ declare module "ui/scroll-view" { on(eventNames: string, callback: (data: observable.EventData) => void, thisArg?: any); /** - * Raised when a tap event occurs. + * Raised when a scroll event occurs. */ - on(event: "scroll", callback: (args: observable.EventData) => void, thisArg?: any); + 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 dfe97584d..a3e1c1d8b 100644 --- a/ui/scroll-view/scroll-view.ios.ts +++ b/ui/scroll-view/scroll-view.ios.ts @@ -1,6 +1,5 @@ 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"); @@ -22,11 +21,18 @@ class UIScrollViewDelegateImpl extends NSObject implements UIScrollViewDelegate } public scrollViewDidScroll(textView: UIScrollView): void { - + if (this._owner) { + this._owner.notify({ + object: this._owner, + eventName: definition.ScrollView.scrollEvent, + scrollX: this._owner.horizontalOffset, + scrollY: this._owner.verticalOffset + }); + } } } -export class ScrollView extends contentView.ContentView implements definition.ScrollView { +export class ScrollView extends common.ScrollView implements definition.ScrollView { private _scroll: UIScrollView; private _contentMeasuredWidth: number = 0; private _contentMeasuredHeight: number = 0; @@ -35,25 +41,15 @@ export class ScrollView extends contentView.ContentView implements definition.Sc constructor() { super(); this._scroll = new UIScrollView(); - - this._delegate = UIScrollViewDelegateImpl.new().initWithOwner(this); } - public onLoaded() { - super.onLoaded(); + protected attachNative() { + this._delegate = UIScrollViewDelegateImpl.new().initWithOwner(this); this._scroll.delegate = this._delegate; } - public onUnloaded() { + protected dettachNative() { this._scroll.delegate = null; - super.onUnloaded(); - } - - get orientation(): string { - return this._getValue(common.orientationProperty); - } - set orientation(value: string) { - this._setValue(common.orientationProperty, value); } get horizontalOffset(): number { From cea78d679f8245a1efd9cd85425535cb695a22e7 Mon Sep 17 00:00:00 2001 From: Vladimir Enchev Date: Tue, 10 Nov 2015 10:35:16 +0200 Subject: [PATCH 3/4] UIScrollViewDelegateImpl improved --- ui/scroll-view/scroll-view.ios.ts | 34 ++++++++++++++++--------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/ui/scroll-view/scroll-view.ios.ts b/ui/scroll-view/scroll-view.ios.ts index a3e1c1d8b..700f6aca8 100644 --- a/ui/scroll-view/scroll-view.ios.ts +++ b/ui/scroll-view/scroll-view.ios.ts @@ -7,29 +7,31 @@ import utils = require("utils/utils"); global.moduleMerge(common, exports); class UIScrollViewDelegateImpl extends NSObject implements UIScrollViewDelegate { - public static ObjCProtocols = [UIScrollViewDelegate]; + private _owner: WeakRef; - static new(): UIScrollViewDelegateImpl { - return super.new(); + public static initWithOwner(owner: WeakRef): UIScrollViewDelegateImpl { + let impl = UIScrollViewDelegateImpl.new(); + impl._owner = owner; + return impl; } - private _owner: ScrollView; + public scrollViewDidScroll(sv: UIScrollView): void { + let owner = this._owner.get(); + if (!owner) { + return; + } - public initWithOwner(owner: ScrollView): UIScrollViewDelegateImpl { - this._owner = owner; - return this; - } - - public scrollViewDidScroll(textView: UIScrollView): void { - if (this._owner) { - this._owner.notify({ - object: this._owner, + if (owner) { + owner.notify({ + object: owner, eventName: definition.ScrollView.scrollEvent, - scrollX: this._owner.horizontalOffset, - scrollY: this._owner.verticalOffset + scrollX: owner.horizontalOffset, + scrollY: owner.verticalOffset }); } } + + public static ObjCProtocols = [UIScrollViewDelegate]; } export class ScrollView extends common.ScrollView implements definition.ScrollView { @@ -44,7 +46,7 @@ export class ScrollView extends common.ScrollView implements definition.ScrollVi } protected attachNative() { - this._delegate = UIScrollViewDelegateImpl.new().initWithOwner(this); + this._delegate = UIScrollViewDelegateImpl.initWithOwner(new WeakRef(this)); this._scroll.delegate = this._delegate; } From f1abec42b5404ee5afc100957689de7430fb4e60 Mon Sep 17 00:00:00 2001 From: Vladimir Enchev Date: Tue, 10 Nov 2015 15:20:21 +0200 Subject: [PATCH 4/4] more code + tests updates --- .../tests/ui/scroll-view/scroll-view-tests.ts | 52 ++++++++++++++++++- ui/scroll-view/scroll-view-common.ts | 14 +++-- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/apps/tests/ui/scroll-view/scroll-view-tests.ts b/apps/tests/ui/scroll-view/scroll-view-tests.ts index 3446ac9fb..ccd0e7407 100644 --- a/apps/tests/ui/scroll-view/scroll-view-tests.ts +++ b/apps/tests/ui/scroll-view/scroll-view-tests.ts @@ -285,7 +285,7 @@ class ScrollLayoutTest extends testModule.UITest { this.testView.scrollToVerticalOffset(100, false); TKUnit.waitUntilReady(function () { return scrollY > 0; }); - TKUnit.assertEqual(this.testView.verticalOffset, scrollY); + TKUnit.assertEqual(scrollY, this.testView.verticalOffset); } public test_scrollView_horizontal_raised_scroll_event() { @@ -307,7 +307,55 @@ class ScrollLayoutTest extends testModule.UITest { this.testView.scrollToHorizontalOffset(100, false); TKUnit.waitUntilReady(function () { return scrollX > 0; }); - TKUnit.assertEqual(this.testView.horizontalOffset, scrollX); + 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); } } diff --git a/ui/scroll-view/scroll-view-common.ts b/ui/scroll-view/scroll-view-common.ts index 9d0836cca..f9074e34a 100644 --- a/ui/scroll-view/scroll-view-common.ts +++ b/ui/scroll-view/scroll-view-common.ts @@ -18,7 +18,7 @@ export var orientationProperty = new dependencyObservable.Property( ); export class ScrollView extends contentView.ContentView implements definition.ScrollView { - private _scrollEventAttached: boolean; + private _scrollChangeCount: number = 0; public static scrollEvent = "scroll"; get orientation(): string { @@ -32,6 +32,7 @@ export class ScrollView extends contentView.ContentView implements definition.Sc super.addEventListener(arg, callback, thisArg); if (arg === definition.ScrollView.scrollEvent) { + this._scrollChangeCount++; this.attach(); } } @@ -40,6 +41,7 @@ export class ScrollView extends contentView.ContentView implements definition.Sc super.addEventListener(arg, callback, thisArg); if (arg === definition.ScrollView.scrollEvent) { + this._scrollChangeCount--; this.dettach(); } } @@ -57,23 +59,19 @@ export class ScrollView extends contentView.ContentView implements definition.Sc } private attach() { - if (!this._scrollEventAttached && this.isLoaded) { - this._scrollEventAttached = true; - + if (this._scrollChangeCount > 0 && this.isLoaded) { this.attachNative(); } } private dettach() { - if (this._scrollEventAttached && this.isLoaded) { - this._scrollEventAttached = false; - + if (this._scrollChangeCount === 0 && this.isLoaded) { this.dettachNative(); } } protected attachNative() { - // + // } protected dettachNative() {