I want to capture scroll event and perform some manipulation when list is scrolling.
I checked list-view api documentation, but it seems there isn't any scroll event emitted by list-view.
For android, I am able to capture scroll event by setting scrollListener on android native list using setOnScrollListener
method.
For iOS to capture scroll event we need to implement scrollViewDidScroll
method in UITableView
delegate. But nativescript's ios implementation of list-view is already setting delegate (without that method) and the delegate class is not exported, so I am not able to extend that class.
It will be great if we can have a common scroll event using setOnScrollListener
for android and scrollViewDidScroll
for iOS or any other better approach. If that is not possible, then at-least if UITableViewDelegateImpl
class is exported, then we can override that for iOS.
Let me know if there is already any solution for capturing scroll event on list-view and I am missing that.
Thanks.
Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.
Hi @shripalsoni04,
At the moment there is no common way to handle ListView Scroll event. In regard to that I could suggest you to use some of available gestures in our Gesture module. For example pan gesture. In addition you could also review our nativescript-telerik-ui and its functionality.
I hope this information would be useful. It would help if we could give me more info about your case and the need of this feature.
Regards,
@tsonevn
Hi @tsonevn,
Thanks for the suggestions.
We want to implement feature like list-view with sticky header. Check this example link: https://www.youtube.com/watch?v=eyWDkwVnYxo. Actual requirement is a bit complex than this, but having hook on scroll event, can help us to get started.
I have checked documentation of RadListView, but haven't found any scroll event there also. We cannot use gesture events also, because scroll can continue after we pull up our finger from the screen, so pan/touch events will not be triggered in that case and will not work for us.
Hope this information will be helpful.
Thanks.
Hi @shripalsoni04,
I am still looking for appropriate solution of your issue and how to handle the scroll event of the ListView. I will write you back as soon as I have possible workaround. However I review the attached video and found that maybe Header and Footler functionality of the RadListView is what you are looking for. You could also review the sample app here, where have been demonstrated the whole functionality of the plugin.
Let me know whether this helps.
Hi @tsonevn,
Thanks a lot for all this help.
I have checked Header and Footer of RadListView, but the issue with that is still the same, we need scroll position to make header part sticked at some scroll position and scroll event is not triggered by RadListView.
As a workaround, currently I have created custom list-view component which extends nativescript list-view and added header and footer functionality for both Android and iOS. To have scroll events for iOS, I have copied code of UITableViewDelegateImpl
into my custom list-view component and implemented scrollViewDidScroll method. And then I am assigning UITableView delegate to the instance of this new UITableViewDelegateImpl
class. It is working as expected now.
As currently I have copied the code of UITableViewDelegateImpl
it will increase maintenance work a bit for us. So a better workaround than this or provision of scroll event for list-view is a huge welcome.
Thanks again.
Hi @shripalsoni04 ,
Thank you for your suggestion.
I review your scenario, and found that we had had such an event in our previous NativeScirpt versions, however this event leads to slowing the framerate and some performance issue. In regard to that scroll event
has been deprecated in NS 2.1.0. In addition my suggestion is to create plugin, which is providing such a functionality for the ListView
. This would help you to use it in different projects. You could also share it with the community by publishing it in npm. How to create plugin you could found here.
I hope this helps.
Regards,
@tsonevn
@tsonevn - I'm going to comment here; because apparently you forgot the initial part of the bug report. Quoting: @shripalsoni04
But nativescript's ios implementation of list-view is already setting delegate (without that method) and the delegate class is not exported, so I am not able to extend that class...
You ask him to create a "plugin" to take care of this; but when You (Telerik) created the delegate and then mark it as private (or not exported); you eliminate us (the end users) from actually being able to extend and/or create a plugin like you suggest. We can't actually "mess" with the delegate because we can't actually get access to it. Either you (Telerik) need to re-expose the missing functionality that he needs; or you (Telerik) needs to expose the delegate class so that we can extend it. But you really can't expect him to create a plugin when you (Telerik) have just tied both his hands and then that will appear as if you just blew him off and will just totally discourage any contributions...
See #2471 -- I have ran into this So MANY times now, where the team has decided no one will need access to change this delegate and so you (Telerik) don't bother to export it. So then you basically create issues for us to even be able to extend it. If you want us to be able to extend it and/or create plugins without asking you to create additional support; you have to actually let us extend it, which means PLEASE export ALL the delegates on both the iOS and Android sides...
Will this ever be a feature? I've lost count how many times I've needed to watch a "scroll" event on a ListView...
I need to implement this type of self-resizing header based on list view scroll, just like the original author did. Is this possible now with NS 3.0.1?
Hi @sserdyuk @NordlingArt @shripalsoni04,
After a discussion with the NativeScript developer's team, we decided to reopen this feature request. we will research what is the best way to integrate this event into the component and this functionality could be available for some of the next NativeScript versions. Regarding that, you could keep track on the issue for further info.
Meanwhile, to be able to handle this event you could attach your own scrollEventListener for Android and to overwrite the component delegate for iOS, while including two more methods in it - scrollViewWillBeginDragging and scrollViewDidScroll
. You will found an example in the below-attached code.
XML
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page">
<Page.actionBar>
<ActionBar title="My App" icon="" class="action-bar">
</ActionBar>
</Page.actionBar>
<GridLayout>
<ListView items="{{ source }}" loaded="onLoaded" itemLoading="onItemLoading" itemTap="onItemTap">
<ListView.itemTemplate>
<StackLayout>
<Label text="{{title}}" textWrap="true" />
</StackLayout>
</ListView.itemTemplate>
</ListView>
</GridLayout>
</Page>
TypeScript
import { EventData, Observable } from 'data/observable';
import { Page } from 'ui/page';
import {ListView} from "ui/list-view"
import { HelloWorldModel } from './main-view-model';
import {isAndroid} from "platform"
declare var android:any;
// Event handler for Page "navigatingTo" event attached in main-page.xml
export function navigatingTo(args: EventData) {
let page = <Page>args.object;
var obs= new Observable();
var array=[];
for(var i=0; i<50; i++){
array.push({"title":"title"+i});
}
obs.set("source",array);
page.bindingContext = obs;
}
export function onLoaded(args){
console.log("lisrviewloaded");
var listview:ListView = <ListView>args.object;
if(isAndroid){
console.log("android.widget.AbsListView.OnScrollListener");
console.dir(android.widget.AbsListView.OnScrollListener);
var listener = android.widget.AbsListView.OnScrollListener.extend({
onScroll:function (view, firstVisibleItem, visibleItemCount, totalItemCount) {
console.log("onScroll")
},
onScrollStateChanged:function (view, scrollState) {
console.log("onScrollStateChanged");
}
})
console.log("scroll listener");
console.dir(listener);
listview.android.setOnScrollListener(new listener());
}
else{
let newDelegate = newUITableViewDelegateImpl.initWithOriginalDelegate((<any>listview)._delegate);
(<any>listview)._delegate = newDelegate;
}
}
class newUITableViewDelegateImpl extends NSObject implements UITableViewDelegate {
public static ObjCProtocols = [UITableViewDelegate];
private _originalDelegate: UITableViewDelegate;
public static initWithOriginalDelegate(originalDelegate: UITableViewDelegate): newUITableViewDelegateImpl {
console.log("initWithOwner")
let delegate = <newUITableViewDelegateImpl>newUITableViewDelegateImpl.new();
delegate._originalDelegate = originalDelegate;
console.log(delegate);
console.log(delegate._originalDelegate);
return delegate;
}
public tableViewWillDisplayCellForRowAtIndexPath(tableView: UITableView, cell: UITableViewCell, indexPath: NSIndexPath) {
console.log("tableViewWillDisplayCellForRowAtIndexPath");
return this._originalDelegate.tableViewWillDisplayCellForRowAtIndexPath(tableView, cell, indexPath);
}
public tableViewWillSelectRowAtIndexPath(tableView: UITableView, indexPath: NSIndexPath): NSIndexPath {
return this._originalDelegate.tableViewWillSelectRowAtIndexPath(tableView, indexPath);
}
public tableViewDidSelectRowAtIndexPath(tableView: UITableView, indexPath: NSIndexPath): NSIndexPath {
tableView.deselectRowAtIndexPathAnimated(indexPath, true);
return indexPath;
}
public tableViewHeightForRowAtIndexPath(tableView: UITableView, indexPath: NSIndexPath): number {
return this._originalDelegate.tableViewHeightForRowAtIndexPath(tableView, indexPath);
}
public scrollViewWillBeginDragging(scrollView:UIScrollView){
console.log("scrollViewWillBeginDragging");
}
public scrollViewDidScroll(scrollView:UIScrollView){
console.log("scrollViewDidScroll");
}
}
Hope thi helps
@tsonevn Thank you! I get Property 'extend' does not exist on type 'typeof OnScrollListener'
error trying to build the Android version. It's from this line:
var listener = android.widget.AbsListView.OnScrollListener.extend({
This seems to compile and work on or list view sitting on a stack layout, but if I put it inside a ScrollView, the list stops behaving normally. Somehow their scroll events handling gets tangled up.
function patchListViewWithEvents(listview: ListView) {
// console.log("event listviewloaded");
// console.log("android.widget.AbsListView.OnScrollListener");
// console.dir(android.widget.AbsListView.OnScrollListener);
const listener = new android.widget.AbsListView.OnScrollListener({
onScroll: function (view, firstVisibleItem, visibleItemCount, totalItemCount) {
console.log("onScroll");
},
onScrollStateChanged: function (view, scrollState) {
console.log("onScrollStateChanged");
}
})
// console.log("scroll listener");
// console.dir(listener);
listview.android.setOnScrollListener(listener);
}
Hi @shripalsoni04,
Regarding the first comment, try to cast android
to any and verify, whether you will have the same problem with the extend
.
About the second problem, you have mentioned. I tested on my side the case with setting the ListView inside the ScrollView, however, the event seems to be fired as expected. I am attaching the XML from the project.
It would help if you could provide some sample project, which could be tested locally.
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page">
<Page.actionBar>
<ActionBar title="My App" icon="" class="action-bar">
</ActionBar>
</Page.actionBar>
<GridLayout>
<ScrollView>
<ListView items="{{ source }}" loaded="onLoaded" itemLoading="onItemLoading" itemTap="onItemTap">
<ListView.itemTemplate>
<StackLayout>
<Label text="{{title}}" textWrap="true" />
</StackLayout>
</ListView.itemTemplate>
</ListView>
</ScrollView>
</GridLayout>
</Page>
Just bumping up this. Any eta or maybe an official won't do?
Thanks
@tsonevn , I like to work on this feature, got here from first-timers
@bmanojkumar I am also really need that feature :)
You have any updates about it?
Thanks!
@shabib3 it's already implemented in the UI pro components, and they're free.
@sserdyuk
Yes I saw that... but the performance on scrolling the RadListView are terrible...So I just going to wait until the ListView will have those events...
@sserdyuk - Yes, but Pro-UI tends to be a bit more on the buggier side; and does not have source code available. Which makes it a non-starter when you run into any of those issues. It is also generates a larger application.
So it would be very useful to have this event exposed in the actual open source version of the control as shipped with NativeScript.
@NathanaelA I agree it would be useful, I was just trying to point @shabib3 towards the existing solution in case it's needed immediately.
Is it possible to get offset? non of these is related to scroll offset : view, firstVisibleItem, visibleItemCount, totalItemCount
Finally could be able to get offset
:
let firstCell = listview.android.getChildAt(0);
let offset = (firstCell == null) ? 0 : listview.android.getFirstVisiblePosition() * firstCell.getHeight() - firstCell.getTop();
This works for android, and should search for a proper way for iOS. Still waiting for a solid solution in nativescript way.
@tsonevn
JS: android.widget.AbsListView.OnScrollListener
JS: ==== object dump start ====
JS: extend()
JS: SCROLL_STATE_FLING: "2"
JS: SCROLL_STATE_IDLE: "0"
JS: SCROLL_STATE_TOUCH_SCROLL: "1"
JS: null()
JS: class: interface android.widget.AbsListView$OnScrollListener
JS: valueOf()
JS: __activityExtend()
JS: call()
JS: apply()
JS: ==== object dump end ====
JS: Error: java.lang.ClassNotFoundException: com.tns.gen.android.widget.AbsListView_OnScrollListener_update_376_68_
JS: java.lang.Class.classForName(Native Method)
JS: java.lang.Class.forName(Class.java:453)
JS: java.lang.Class.forName(Class.java:378)
JS: com.tns.Runtime.getClassForName(Runtime.java:1123)
JS: com.tns.ClassResolver.resolveClass(ClassResolver.java:27)
JS: com.tns.Runtime.resolveClass(Runtime.java:679)
JS: com.tns.Runtime.callJSMethodNative(Native Method)
JS: com.tns.Runtime.dispatchCallJSMethodNative(Runtime.java:1203)
JS: com.tns.Runtime.callJSMethodImpl(Runtime.java:1083)
JS: com.tns.Runtime.callJSMethod(Runtime.java:1070)
JS: com.tns.Runtime.callJSMethod(Runtime.java:1050)
JS: com.tns.Runtime.callJSMethod(Runtime.java:1042)
JS: com.tns.FragmentClass.onCreateView(FragmentClass.java:53)
JS: android.support.v4.app.Fragment.performCreateView(Fragment.java:2439)
JS: android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1460)
JS: android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
JS: android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)
JS: android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:802)
JS: android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2625)
JS: android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2411)
JS: android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2366)
JS: android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2273)
JS: android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:733)
JS: android.os.Handler.handleCallback(Handler.java:873)
JS: android.os.Handler.dispatchMessage(Handler.java:99)
JS: android.os.Looper.loop(Looper.java:193)
JS: android.app.ActivityThread.main(ActivityThread.java:6863)
JS: java.lang.reflect.Method.invoke(Native Method)
JS: com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:...
I'm unable to extend android.widget.AbsListView.OnScrollListener
it says that the class is not found. Am I doing something wrong here?
console.log("android.widget.AbsListView.OnScrollListener");
console.dir(android.widget.AbsListView.OnScrollListener);
try {
const listener = android.widget.AbsListView.OnScrollListener.extend({
onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount) {
console.log("onScroll");
},
onScrollStateChanged(view, scrollState) {
console.log("onScrollStateChanged");
}
});
console.log("scroll listener");
console.dir(listener);
} catch (err) {
console.log(err);
}
EDIT:
listview.android.setOnScrollListener(new android.widget.AbsListView.OnScrollListener({
onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount) {
console.log("onScroll");
},
onScrollStateChanged(view, scrollState) {
console.log("onScrollStateChanged");
}
}));
this worked for me :)
Can anyone help with implementing the listener for ios in a Javascript project
will simply like to when the first item on the list goes out of view
Any one got an implementation for iOS
to work?
@vahidvdn
In order to get the offset, I slightly modified @tsonevn's solution (using any
because I don't want to add tns-platform-declarations
, you can use proper types):
private _callback: Function;
public static initWithOriginalDelegate(
originalDelegate: any,
callback: (x: number) => void
): newUITableViewDelegateImpl {
let delegate = <newUITableViewDelegateImpl>newUITableViewDelegateImpl.new();
delegate._originalDelegate = originalDelegate;
delegate._callback = (x: number) => callback(x);
return delegate;
}
public scrollViewDidScroll(scrollView: any) {
try {
const offsetY = scrollView.contentOffset.y;
this._callback(offsetY);
} catch (e) {
console.log(e);
}
}
const newDelegate = newUITableViewDelegateImpl.initWithOriginalDelegate(
(<any>listView)._delegate,
(offsetY: number) => {
// use offset here
}
);
(<any>listView)._delegate = newDelegate;
@tsonevn I have noticed that setting (<any>listView)._delegate
is not enough if it happens later than onLoaded
event of ListView itself (ListView
will keep using the delegate it was initiated with). For example, I have a component that optionally reacts to sibling ListView
's offset. If a ListView
ref is provided, it will add android listener and change iOS delegate to the given ListView
. I solved it like this:
(<any>listView)._delegate = newDelegate;
// NOTE: Without timeout I am having an issue related to ListView's height, but that might be layout specific for me
setTimeout(() => {
listView.ios.delegate = newDelegate;
});
I was wondering if it's safe to hot swap like this.
EDIT: As of NS 7.0.13, @NativeClass
annotation is required
Most helpful comment
@tsonevn - I'm going to comment here; because apparently you forgot the initial part of the bug report. Quoting: @shripalsoni04
You ask him to create a "plugin" to take care of this; but when You (Telerik) created the delegate and then mark it as private (or not exported); you eliminate us (the end users) from actually being able to extend and/or create a plugin like you suggest. We can't actually "mess" with the delegate because we can't actually get access to it. Either you (Telerik) need to re-expose the missing functionality that he needs; or you (Telerik) needs to expose the delegate class so that we can extend it. But you really can't expect him to create a plugin when you (Telerik) have just tied both his hands and then that will appear as if you just blew him off and will just totally discourage any contributions...
See #2471 -- I have ran into this So MANY times now, where the team has decided no one will need access to change this delegate and so you (Telerik) don't bother to export it. So then you basically create issues for us to even be able to extend it. If you want us to be able to extend it and/or create plugins without asking you to create additional support; you have to actually let us extend it, which means PLEASE export ALL the delegates on both the iOS and Android sides...