OnScrollListener called repeatedly - android

I have 2 listview controls that each display part of the data of a list. They are oriented above and below each other in a UI fragment. The data displayed is tire information for a car, so the top listview displays data for the front wheels and the lower listview displays data for the rear wheels (with a car image between them). The listviews are configured so that the data list in each view mirrors the data in the other. So the top line of the top list is of the same dataset as the bottom line in the bottom list. When the user scrolls either list, the other list scrolls in a mirrored fashion. This enables the user to scroll the data such that the data they are interested in is positioned directly above and below the car image (bottom row of top list, top row of bottom list)
To accomplish this, I'm responding to the OnScrollListener-OnScroll event on one listview, making position calculations and calling listview.setSelectionFromTop(position, sety) on the other listview to update its position.
This is working fine in Android 4.xx when I first released the app. Now in Android 7 and higher, it appears that a race condition is occurring where setting the position on one listview is triggering an OnScroll on the other and things go bad from there in a never ending series of OnScroll events.
I've tried adding a flag on the OnScroll function so that it won't trigger if its inside an OnScroll call from the other listview. This didn't work as it appears that the OnScroll events triggered from calling setSelectionFromTop are asynchronous so they occur outside the flagged area.
One thing I've noticed is that it appears that the sizes of the listviews is critical and that they both need to be the exact same size. In the original layout (Android 4.x) I've set their layoutHeight=120dp to fix their size. However, current android doesn't appear to respect the layoutHeight values and the resulting UI shows them as different sizes, this may be a key to the puzzle.
Here's the onScroll code for one of the listviews, the code for the other is the same, just replace lvTop with lvBottom
OnScrollListener lTop=new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (fInBottomScrollListener)
return;
fInTopScrollListener=true;
// Get position of this lv top
View cTop = lvTop.getChildAt(0);
View cBottom = lvBottom.getChildAt(0);
if (cTop==null || cBottom==null) return;
int topOffset = cTop.getTop();
int itemHeight = cTop.getHeight();
int firstVisPos = lvTop.getFirstVisiblePosition();
int setposition = totalItemCount - (visibleItemCount - (topOffset!=0 ? 1:0)) - firstVisPos-1;
int sety= -(itemHeight + (topOffset>0 ? 0:topOffset));
lvBottom.setSelectionFromTop(setposition, sety);
fInTopScrollListener=false;
}
};
I see in the log, a continuing list of onScroll events, when running on Android 4.X this list stops after scrolling is complete, whereas on 7.x and greater, it continues and scrolling is basically frozen on both listviews until I refresh the fragment

I think that your calculations for the position and offset are incorrect, so the 2 ListViews are bouncing beacuse of a small offset error.
For ListViews with same heights:
AbsListView.OnScrollListener lTop = new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (!fInBottomScrollListener) {
fInTopScrollListener = true;
View cTop = lvTop.getChildAt(visibleItemCount - 1);
if (cTop == null) return;
int setposition = totalItemCount - visibleItemCount - firstVisibleItem;
int sety = view.getHeight() - cTop.getBottom();
lvBottom.setSelectionFromTop(setposition, sety);
fInTopScrollListener = false;
}
}
};
AbsListView.OnScrollListener lBottom = new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (!fInTopScrollListener) {
fInBottomScrollListener = true;
View cBottom = lvBottom.getChildAt(visibleItemCount - 1);
if (cBottom == null) return;
int setposition = totalItemCount - visibleItemCount - firstVisibleItem;
int sety = view.getHeight() - cBottom.getBottom();
lvTop.setSelectionFromTop(setposition, sety);
fInBottomScrollListener = false;
}
}
};
Updated:
This is another approach, no matter the listviews' heights are same or not:
final private static int SCROLL_DELAY = 100;
long timestampScroll;
AbsListView.OnScrollListener lTop = new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (!fInBottomScrollListener) {
fInTopScrollListener = true;
View cTop = lvTop.getChildAt(0);
View cBot = lvBottom.getChildAt(0);
if (cTop == null || cBot == null) return;
int lvTopMovableRange = (cTop.getHeight() + lvTop.getDividerHeight()) * totalItemCount
- lvTop.getDividerHeight() - lvTop.getHeight();
int lvBotMovableRange = (cBot.getHeight() + lvBottom.getDividerHeight()) * totalItemCount
- lvBottom.getDividerHeight() - lvBottom.getHeight();
int lvTopPointer = (cTop.getHeight() + lvTop.getDividerHeight()) * firstVisibleItem
- cTop.getTop();
int lvBotPointer = (int)((float)(lvTopMovableRange - lvTopPointer)/lvTopMovableRange*lvBotMovableRange);
int setposition = lvBotPointer / (cBot.getHeight() + lvBottom.getDividerHeight());
int sety = -lvBotPointer + setposition * (cBot.getHeight() + lvBottom.getDividerHeight());
timestampScroll = System.currentTimeMillis();
lvBottom.setSelectionFromTop(setposition, sety);
lvTop.postDelayed(new Runnable() {
#Override
public void run() {
if (System.currentTimeMillis() - timestampScroll > (SCROLL_DELAY - 10)) fInTopScrollListener = false;
}
}, SCROLL_DELAY);
}
}
};
AbsListView.OnScrollListener lBottom = new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (!fInTopScrollListener) {
fInBottomScrollListener = true;
View cTop = lvTop.getChildAt(0);
View cBot = lvBottom.getChildAt(0);
if (cTop == null || cBot == null) return;
int lvTopMovableRange = (cTop.getHeight() + lvTop.getDividerHeight()) * totalItemCount
- lvTop.getDividerHeight() - lvTop.getHeight();
int lvBotMovableRange = (cBot.getHeight() + lvBottom.getDividerHeight()) * totalItemCount
- lvBottom.getDividerHeight() - lvBottom.getHeight();
int lvBotPointer = (cBot.getHeight() + lvBottom.getDividerHeight()) * firstVisibleItem
- cBot.getTop();
int lvTopPointer = (int)((float)(lvBotMovableRange - lvBotPointer)/lvBotMovableRange*lvTopMovableRange);
int setposition = lvTopPointer / (cTop.getHeight() + lvTop.getDividerHeight());
int sety = -lvTopPointer + setposition * (cTop.getHeight() + lvTop.getDividerHeight());
timestampScroll = System.currentTimeMillis();
lvTop.setSelectionFromTop(setposition, sety);
lvBottom.postDelayed(new Runnable() {
#Override
public void run() {
if (System.currentTimeMillis() - timestampScroll > (SCROLL_DELAY - 10)) fInBottomScrollListener = false;
}
}, SCROLL_DELAY);
}
}
};
Hope that helps!

Related

Show Button On Scrolling Down ListView

I have added a Button at the bottom of screen. It is a "Go To Top" button.
I want to make it hidden when user is at top of the list i.e on first row, and show it when he scrolls down to beyond second row.
I can add onscroll listner to the ListView. But I am not sure how to check for row number
Can anyone provide an example how to achieve it?
you can use setOnScrollListener
setOnScrollListener
Set the listener that will receive notifications every time the list scrolls.
sample code
listview.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// add here your logic like this
int lastItem = firstVisibleItem + visibleItemCount;
if (firstVisibleItem < 2) {
fab.setVisibility(View.INVISIBLE);
}else {
fab.setVisibility(View.VISIBLE);
}
}
});
You can try something like this
Find out if ListView is scrolled to the bottom?
private int preLast;
// Initialization stuff.
yourListView.setOnScrollListener(this);
// ... ... ...
#Override
public void onScroll(AbsListView lw, final int firstVisibleItem,
final int visibleItemCount, final int totalItemCount)
{
switch(lw.getId())
{
case R.id.your_list_id:
// Make your calculation stuff here. You have all your
// needed info from the parameters of this function.
// Sample calculation to determine if the last
// item is fully visible.
final int lastItem = firstVisibleItem + visibleItemCount;
if(lastItem == totalItemCount)
{
if(preLast!=lastItem)
{
//to avoid multiple calls for last item
Log.d("Last", "Last");
preLast = lastItem;
}
}
}
}

Resize top view on listview scrolling

I have a layout above a list view which is at the bottom of the main layout.
When i scroll the list, i want to make the top layout smaller. i am trying now to do that using on list view scroll listener and change the layout parameters width and height.
The problem is that this procedure its not smoothly at all, eats a lot of resources.
How can i re-size the top view without using layout parameters (eating a lot of Resources) ?
that the scroll listener I set for the listview:
public class EndLessScrolling implements AbsListView.OnScrollListener {
private int amountViewInitDimension = 0;
public EndLessScrolling() {}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//amountView on scroll resize effect
if (firstVisibleItem < 5) {
View amountView = getActivity().findViewById(R.id.select_receiver_amount_parent);
if (amountView != null) {
ViewGroup.LayoutParams params = amountView.getLayoutParams();
if (amountViewInitDimension == 0) {
amountViewInitDimension = params.width;
}
params.width = amountViewInitDimension - firstVisibleItem * amountViewInitDimension / 10;
params.height = amountViewInitDimension - firstVisibleItem * amountViewInitDimension / 10;
receiverAmount.setTextSize(TypedValue.COMPLEX_UNIT_SP, amountTextSize - firstVisibleItem * zoomStep);
dollarSign.setTextSize(TypedValue.COMPLEX_UNIT_SP, dollarSignTextSize - firstVisibleItem * zoomStep / 2f);
amountView.setLayoutParams(params);
}
}
}
}
Have you seen this library? I think is what you want.
https://github.com/carlonzo/StikkyHeader

Find if a ListView is scrolled "fully" to the bottom?

What I Want
I want to detect if the ListView have been scrolled "fully" to the bottom. By the word "fully", I mean that the last element of the list should be fully visible.
What I Did
This is what I did.
list.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
switch (view.getId()) {
case R.id.encList:
final int lastItem = firstVisibleItem + visibleItemCount;
if (lastItem == totalItemCount) {
if (preLast != lastItem) { //to avoid multiple calls for last item
Log.d("Log", "Last reached");
preLast = lastItem;
}
} else
preLast = lastItem;
}
}
});
The Problem
My code works to some extent but doesn't provide the exact result I wish for. The log gets printed when the last element is visible, but I want it to be printed only when the last element is fully visible.
What modifications do I need in this code?
Put this
if (list.getLastVisiblePosition() ==
list.getAdapter().getCount() -1 &&
list.getChildAt(list.getChildCount() - 1).getBottom() <= list.getHeight()){
//Scrolled end
}
in your onScroll

Quick Return Pattern for FAB and ListView

I have a simple ListView inside a ViewPager and a Floating Action Bar at the bottom right corner of the screen.
I want to implement the Quick Return pattern to it so that when I scroll the ListView the FAB goes down the screen and comes up on opposite scroll.
Thanks in advance.
EDIT:
list.setAdapter(new QuickReturnAdapter(adapter));
quickReturnAttacher = QuickReturnAttacher.forView(list);
quickReturnAttacher.addTargetView(floatMenu, AbsListViewScrollTarget.POSITION_BOTTOM, dpToPx(context, 400));
if (quickReturnAttacher instanceof AbsListViewQuickReturnAttacher) {
// This is the correct way to register an OnScrollListener.
// You have to add it on the QuickReturnAttacher, instead
// of on the viewGroup directly.
final AbsListViewQuickReturnAttacher attacher = (AbsListViewQuickReturnAttacher) quickReturnAttacher;
attacher.addOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
}
Then I am using quickReturnAttacher.setOnItemClickListener and this ain't working.
There is already an open source library for this feature.
https://github.com/felipecsl/QuickReturn
Try this:
final AbsListViewQuickReturnAttacher attacher = (AbsListViewQuickReturnAttacher) quickReturnAttacher;
attacher.addOnScrollListener(this);
attacher.setOnItemClickListener(this);
});
For me this is working, the position passed in the callback is correct.
You have to register your click listener to the attacher, not to the list.
How do you cannot find the method setOnItemClickListener in the class AbsListViewQuickReturnAttacher?
You can try this solution for Quick Return Pattern for ListViews:
yourListView.setOnScrollListener(new AbsListView.OnScrollListener() {
#SuppressLint("NewApi")
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
int scrollOffset = 0;
float transitionY;
if (firstVisibleItem > 0) {
scrollOffset += headerHeight;
if (firstVisibleItem > 1) {
scrollOffset += (firstVisibleItem - 1) * cellHeight;
}
}
if (yourListView.getChildCount() > 0) {
scrollOffset += -yourListView.getChildAt(0).getTop();
scrollOffset = -scrollOffset;
}
float scrollDelta = scrollOffset - prevOffset;
float nextY = mQuickReturnView.getY() + scrollDelta;
if (nextY < minRawY) {
transitionY = minRawY;
}
else if (nextY > qReturnDelta) {
transitionY = qReturnDelta;
}
else {
transitionY = nextY;
}
mQuickReturnView.setY(transitionY);
prevOffset = scrollOffset;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
});

Find out if ListView is scrolled to the bottom?

Can I find out if my ListView is scrolled to the bottom? By that I mean that the last item is fully visible.
Edited:
Since I have been investigating in this particular subject in one of my applications, I can write an extended answer for future readers of this question.
Implement an OnScrollListener, set your ListView's onScrollListener and then you should be able to handle things correctly.
For example:
private int preLast;
// Initialization stuff.
yourListView.setOnScrollListener(this);
// ... ... ...
#Override
public void onScroll(AbsListView lw, final int firstVisibleItem,
final int visibleItemCount, final int totalItemCount)
{
switch(lw.getId())
{
case R.id.your_list_id:
// Make your calculation stuff here. You have all your
// needed info from the parameters of this function.
// Sample calculation to determine if the last
// item is fully visible.
final int lastItem = firstVisibleItem + visibleItemCount;
if(lastItem == totalItemCount)
{
if(preLast!=lastItem)
{
//to avoid multiple calls for last item
Log.d("Last", "Last");
preLast = lastItem;
}
}
}
}
Late answer, but if you simply wish to check whether your ListView is scrolled all the way down or not, without creating an event listener, you can use this if-statement:
if (yourListView.getLastVisiblePosition() == yourListView.getAdapter().getCount() -1 &&
yourListView.getChildAt(yourListView.getChildCount() - 1).getBottom() <= yourListView.getHeight())
{
//It is scrolled all the way down here
}
First it checks if the last possible position is in view. Then it checks if the bottom of the last button aligns with the bottom of the ListView. You can do something similar to know if it's all the way at the top:
if (yourListView.getFirstVisiblePosition() == 0 &&
yourListView.getChildAt(0).getTop() >= 0)
{
//It is scrolled all the way up here
}
The way I did it:
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE
&& (listView.getLastVisiblePosition() - listView.getHeaderViewsCount() -
listView.getFooterViewsCount()) >= (adapter.getCount() - 1)) {
// Now your listview has hit the bottom
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
Something to the effect of:
if (getListView().getLastVisiblePosition() == (adapter.items.size() - 1))
public void onScrollStateChanged(AbsListView view, int scrollState)
{
if (!view.canScrollList(View.SCROLL_AXIS_VERTICAL) && scrollState == SCROLL_STATE_IDLE)
{
//When List reaches bottom and the list isn't moving (is idle)
}
}
This worked for me.
This can be
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
if (scrollState == 2)
flag = true;
Log.i("Scroll State", "" + scrollState);
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
if ((visibleItemCount == (totalItemCount - firstVisibleItem))
&& flag) {
flag = false;
Log.i("Scroll", "Ended");
}
}
canScrollVertically(int direction) works for all Views, and seems to do what you asked, with less code than most of the other answers. Plug in a positive number, and if the result is false, you're at the bottom.
ie:
if (!yourView.canScrollVertically(1)) {
//you've reached bottom
}
It was pretty painful to deal with scrolling, detecting when it is finished and it is indeed at the bottom of the list (not bottom of the visible screen), and triggers my service only once, to fetch data from the web. However it is working fine now. The code is as follows for the benefit of anybody who faces the same situation.
NOTE: I had to move my adapter related code into onViewCreated instead of onCreate and detect scrolling primarily like this:
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {}
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (getListView().getLastVisiblePosition() == (adapter.getCount() - 1))
if (RideListSimpleCursorAdapter.REACHED_THE_END) {
Log.v(TAG, "Loading more data");
RideListSimpleCursorAdapter.REACHED_THE_END = false;
Intent intent = new Intent(getActivity().getApplicationContext(), FindRideService.class);
getActivity().getApplicationContext().startService(intent);
}
}
Here RideListSimpleCursorAdapter.REACHED_THE_END is an additional variable in my SimpleCustomAdapter which is set like this:
if (position == getCount() - 1) {
REACHED_THE_END = true;
} else {
REACHED_THE_END = false;
}
Only when both of these conditions meet, it means that I am indeed at the bottom of the list, and that my service will run only once. If I don't catch the REACHED_THE_END, even scrolling backwards triggers the service again, as long as the last item is in view.
To expand a bit on one of the above answers, this is what I had to do to get it working completely. There seems to be about 6dp of built-in padding inside of ListViews, and onScroll() was being called when the list was empty. This handles both of those things. It could probably be optimized a bit, but is written more for clarity.
Side note: I've tried several different dp to pixel conversion techniques, and this dp2px() one has been the best.
myListView.setOnScrollListener(new OnScrollListener() {
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (visibleItemCount > 0) {
boolean atStart = true;
boolean atEnd = true;
View firstView = view.getChildAt(0);
if ((firstVisibleItem > 0) ||
((firstVisibleItem == 0) && (firstView.getTop() < (dp2px(6) - 1)))) {
// not at start
atStart = false;
}
int lastVisibleItem = firstVisibleItem + visibleItemCount;
View lastView = view.getChildAt(visibleItemCount - 1);
if ((lastVisibleItem < totalItemCount) ||
((lastVisibleItem == totalItemCount) &&
((view.getHeight() - (dp2px(6) - 1)) < lastView.getBottom()))
) {
// not at end
atEnd = false;
}
// now use atStart and atEnd to do whatever you need to do
// ...
}
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
});
private int dp2px(int dp) {
return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
I can't comment yet because I haven't got enough reputation, but in #Ali Imran and #Wroclai 's answer I think something is missing. With that piece of code, once you update preLast, it will never execute the Log again.
In my specific problem, I want to execute some operation every time I scroll to the bottom, but once preLast is updated to LastItem, that operation is never executed again.
private int preLast;
// Initialization stuff.
yourListView.setOnScrollListener(this);
// ... ... ...
#Override
public void onScroll(AbsListView lw, final int firstVisibleItem,
final int visibleItemCount, final int totalItemCount) {
switch(lw.getId()) {
case android.R.id.list:
// Make your calculation stuff here. You have all your
// needed info from the parameters of this function.
// Sample calculation to determine if the last
// item is fully visible.
final int lastItem = firstVisibleItem + visibleItemCount;
if(lastItem == totalItemCount) {
if(preLast!=lastItem){ //to avoid multiple calls for last item
Log.d("Last", "Last");
preLast = lastItem;
}
} else {
preLast = lastItem;
}
}
With that "else" you're now able to execute your code (Log, in this case) every time you scroll to the bottom once again.
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
int lastindex = view.getLastVisiblePosition() + 1;
if (lastindex == totalItemCount) { //showing last row
if ((view.getChildAt(visibleItemCount - 1)).getTop() == view.getHeight()) {
//Last row fully visible
}
}
}
For your list to call when the list reach last and if an error happens, then this will not call the endoflistview again. This code will help this scenario as well.
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
final int lastPosition = firstVisibleItem + visibleItemCount;
if (lastPosition == totalItemCount) {
if (previousLastPosition != lastPosition) {
//APPLY YOUR LOGIC HERE
}
previousLastPosition = lastPosition;
}
else if(lastPosition < previousLastPosition - LIST_UP_THRESHOLD_VALUE){
resetLastIndex();
}
}
public void resetLastIndex(){
previousLastPosition = 0;
}
where the LIST_UP_THRESHOLD_VALUE can be any integer value(I have used 5) where your list is scrolled up and while returning to the end, this will call the end of list view again.
I found a very nice way to automatically load the next page set in a way that doesn't require your own ScrollView (like the accepted answer requires).
On ParseQueryAdapter there is a method called getNextPageView that is there to allow you to supply your own custom view that appears at the end of the list when there is more data to load so it will only trigger when you have reached the end of you current page set (it's the "load more.." view by default). This method is only called when there is more data to load so it's a great place to call loadNextPage(); This way the adapter does all the hard work for you in determining when new data should be loaded and it won't be called at all if you have reached the end of the data set.
public class YourAdapter extends ParseQueryAdapter<ParseObject> {
..
#Override
public View getNextPageView(View v, ViewGroup parent) {
loadNextPage();
return super.getNextPageView(v, parent);
}
}
Then inside your activity/fragment you just have to set the adapter and new data will be automatically updated for you like magic.
adapter = new YourAdapter(getActivity().getApplicationContext());
adapter.setObjectsPerPage(15);
adapter.setPaginationEnabled(true);
yourList.setAdapter(adapter);
To detect whether the last item is fully visible, you can simple add calculation on the view's last visible item's bottom by lastItem.getBottom().
yourListView.setOnScrollListener(this);
#Override
public void onScroll(AbsListView view, final int firstVisibleItem,
final int visibleItemCount, final int totalItemCount) {
int vH = view.getHeight();
int topPos = view.getChildAt(0).getTop();
int bottomPos = view.getChildAt(visibleItemCount - 1).getBottom();
switch(view.getId()) {
case R.id.your_list_view_id:
if(firstVisibleItem == 0 && topPos == 0) {
//TODO things to do when the list view scroll to the top
}
if(firstVisibleItem + visibleItemCount == totalItemCount
&& vH >= bottomPos) {
//TODO things to do when the list view scroll to the bottom
}
break;
}
}
I went with:
#Override
public void onScroll(AbsListView listView, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
if(totalItemCount - 1 == favoriteContactsListView.getLastVisiblePosition())
{
int pos = totalItemCount - favoriteContactsListView.getFirstVisiblePosition() - 1;
View last_item = favoriteContactsListView.getChildAt(pos);
//do stuff
}
}
In the method getView() (of a BaseAdapter-derived class) one can check if position of the current view is equal to the list of items in the Adapter. If that is the case, then it means we've reached the end/bottom of the list:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// ...
// detect if the adapter (of the ListView/GridView) has reached the end
if (position == getCount() - 1) {
// ... end of list reached
}
}
I find a better way to detect listview scroll end the bottom, first detect scoll end by this
Implementation of onScrollListener to detect the end of scrolling in a ListView
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
this.currentFirstVisibleItem = firstVisibleItem;
this.currentVisibleItemCount = visibleItemCount;
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
this.currentScrollState = scrollState;
this.isScrollCompleted();
}
private void isScrollCompleted() {
if (this.currentVisibleItemCount > 0 && this.currentScrollState == SCROLL_STATE_IDLE) {
/*** In this way I detect if there's been a scroll which has completed ***/
/*** do the work! ***/
}
}
finally combine Martijn's answer
OnScrollListener onScrollListener_listview = new OnScrollListener() {
private int currentScrollState;
private int currentVisibleItemCount;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
this.currentScrollState = scrollState;
this.isScrollCompleted();
}
#Override
public void onScroll(AbsListView lw, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
this.currentVisibleItemCount = visibleItemCount;
}
private void isScrollCompleted() {
if (this.currentVisibleItemCount > 0 && this.currentScrollState == SCROLL_STATE_IDLE) {
/*** In this way I detect if there's been a scroll which has completed ***/
/*** do the work! ***/
if (listview.getLastVisiblePosition() == listview.getAdapter().getCount() - 1
&& listview.getChildAt(listview.getChildCount() - 1).getBottom() <= listview.getHeight()) {
// It is scrolled all the way down here
Log.d("henrytest", "hit bottom");
}
}
}
};
Big thanks to posters in stackoverflow! I combined some ideas and created class listener for activities and fragments (so this code is more reusable making code faster to write and much cleaner).
All you have to do when you got my class is to implement interface (and of course create method for it) which is in declared in my class and create object of this class passing arguments.
/**
* Listener for getting call when ListView gets scrolled to bottom
*/
public class ListViewScrolledToBottomListener implements AbsListView.OnScrollListener {
ListViewScrolledToBottomCallback scrolledToBottomCallback;
private int currentFirstVisibleItem;
private int currentVisibleItemCount;
private int totalItemCount;
private int currentScrollState;
public interface ListViewScrolledToBottomCallback {
public void onScrolledToBottom();
}
public ListViewScrolledToBottomListener(Fragment fragment, ListView listView) {
try {
scrolledToBottomCallback = (ListViewScrolledToBottomCallback) fragment;
listView.setOnScrollListener(this);
} catch (ClassCastException e) {
throw new ClassCastException(fragment.toString()
+ " must implement ListViewScrolledToBottomCallback");
}
}
public ListViewScrolledToBottomListener(Activity activity, ListView listView) {
try {
scrolledToBottomCallback = (ListViewScrolledToBottomCallback) activity;
listView.setOnScrollListener(this);
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement ListViewScrolledToBottomCallback");
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
this.currentFirstVisibleItem = firstVisibleItem;
this.currentVisibleItemCount = visibleItemCount;
this.totalItemCount = totalItemCount;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
this.currentScrollState = scrollState;
if (isScrollCompleted()) {
if (isScrolledToBottom()) {
scrolledToBottomCallback.onScrolledToBottom();
}
}
}
private boolean isScrollCompleted() {
if (this.currentVisibleItemCount > 0 && this.currentScrollState == SCROLL_STATE_IDLE) {
return true;
} else {
return false;
}
}
private boolean isScrolledToBottom() {
System.out.println("First:" + currentFirstVisibleItem);
System.out.println("Current count:" + currentVisibleItemCount);
System.out.println("Total count:" + totalItemCount);
int lastItem = currentFirstVisibleItem + currentVisibleItemCount;
if (lastItem == totalItemCount) {
return true;
} else {
return false;
}
}
}
You need to add a empty xml footer resource to your listView and detect if this footer is visible.
private View listViewFooter;
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_newsfeed, container, false);
listView = (CardListView) rootView.findViewById(R.id.newsfeed_list);
footer = inflater.inflate(R.layout.newsfeed_listview_footer, null);
listView.addFooterView(footer);
return rootView;
}
Then in your listView scroll listener you do this
#
Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem == 0) {
mSwipyRefreshLayout.setDirection(SwipyRefreshLayoutDirection.TOP);
mSwipyRefreshLayout.setEnabled(true);
} else if (firstVisibleItem + visibleItemCount == totalItemCount) //If last row is visible. In this case, the last row is the footer.
{
if (footer != null) //footer is a variable referencing the footer view of the ListView. You need to initialize this onCreate
{
if (listView.getHeight() == footer.getBottom()) { //Check if the whole footer is visible.
mSwipyRefreshLayout.setDirection(SwipyRefreshLayoutDirection.BOTTOM);
mSwipyRefreshLayout.setEnabled(true);
}
}
} else
mSwipyRefreshLayout.setEnabled(false);
}
If you set a tag on a view of the last item of the listview, later you can retrieve the view with the tag, if the view is null it's because the view is not loaded anymore. Like this:
private class YourAdapter extends CursorAdapter {
public void bindView(View view, Context context, Cursor cursor) {
if (cursor.isLast()) {
viewInYourList.setTag("last");
}
else{
viewInYourList.setTag("notLast");
}
}
}
then if you need to know if the last item is loaded
View last = yourListView.findViewWithTag("last");
if (last != null) {
// do what you want to do
}
Janwilx72 is right,but it's min sdk is 21,so i create this method:
private boolean canScrollList(#ScrollOrientation int direction, AbsListView listView) {
final int childCount = listView.getChildCount();
if (childCount == 0) {
return false;
}
final int firstPos = listView.getFirstVisiblePosition();
final int paddingBottom = listView.getListPaddingBottom();
final int paddingTop = listView.getListPaddingTop();
if (direction > 0) {
final int lastBottom = listView.getChildAt(childCount - 1).getBottom();
final int lastPos = firstPos + childCount;
return lastPos < listView.getChildCount() || lastBottom > listView.getHeight() - paddingBottom;
} else {
final int firstTop = listView.getChildAt(0).getTop();
return firstPos > 0 || firstTop < paddingTop;
}
}
for ScrollOrientation:
protected static final int SCROLL_UP = -1;
protected static final int SCROLL_DOWN = 1;
#Retention(RetentionPolicy.SOURCE)
#IntDef({SCROLL_UP, SCROLL_DOWN})
protected #interface Scroll_Orientation{}
Maybe late, just for latecommers。
If you are using a custom adapter with your listview(most people do!) a beautiful solution is given here!
https://stackoverflow.com/a/55350409/1845404
The adapter's getView method detects when the list has been scrolled to the last item. It also adds correction for the rare times when some earlier position is called even after the adapter has already rendered the last view.
I did that and works for me :
private void YourListView_Scrolled(object sender, ScrolledEventArgs e)
{
double itemheight = YourListView.RowHeight;
double fullHeight = YourListView.Count * itemheight;
double ViewHeight = YourListView.Height;
if ((fullHeight - e.ScrollY) < ViewHeight )
{
DisplayAlert("Reached", "We got to the end", "OK");
}
}
This will scroll down your list to last entry.
ListView listView = new ListView(this);
listView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));
listView.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
listView.setStackFromBottom(true);

Categories

Resources