Android listView find the amount of pixels scrolled - android

I have a listView. When I scroll and stops in a particular place.
How can I get the amount of pixels I scrolled(from top)?
I have tried using get listView.getScrollY(), but it returns 0.

I had the same problem.
I cannot use View.getScrollY() because it always returns 0 and I cannot use OnScrollListener.onScroll(...) because it works with positions not with pixels. I cannot subclass ListView and override onScrollChanged(...) because its parameter values are always 0. Meh.
All I want to know is the amount the children (i.e. content of listview) got scrolled up or down. So I came up with a solution. I track one of the children (or you can say one of the "rows") and follow its vertical position change.
Here is the code:
public class ObservableListView extends ListView {
public static interface ListViewObserver {
public void onScroll(float deltaY);
}
private ListViewObserver mObserver;
private View mTrackedChild;
private int mTrackedChildPrevPosition;
private int mTrackedChildPrevTop;
public ObservableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mTrackedChild == null) {
if (getChildCount() > 0) {
mTrackedChild = getChildInTheMiddle();
mTrackedChildPrevTop = mTrackedChild.getTop();
mTrackedChildPrevPosition = getPositionForView(mTrackedChild);
}
} else {
boolean childIsSafeToTrack = mTrackedChild.getParent() == this && getPositionForView(mTrackedChild) == mTrackedChildPrevPosition;
if (childIsSafeToTrack) {
int top = mTrackedChild.getTop();
if (mObserver != null) {
float deltaY = top - mTrackedChildPrevTop;
mObserver.onScroll(deltaY);
}
mTrackedChildPrevTop = top;
} else {
mTrackedChild = null;
}
}
}
private View getChildInTheMiddle() {
return getChildAt(getChildCount() / 2);
}
public void setObserver(ListViewObserver observer) {
mObserver = observer;
}
}
Couple of notes:
we override onScrollChanged(...) because it gets called when the listview is scrolled (just its parameters are useless)
then we choose a child (row) from the middle (doesn't have to be precisely the child in the middle)
every time scrolling happens we calculate vertical movement based on previous position (getTop()) of tracked child
we stop tracking a child when it is not safe to be tracked (e.g. in cases where it might got reused)

You cant get pixels from top of list (because then you need to layout all views from top of list - there can be a lot of items). But you can get pixels of first visible item: int pixels = listView.getChildAt(0).getTop(); it generally will be zero or negative number - shows difference between top of listView and top of first view in list

edit:
I've improved in this class to avoid some moments that the track was losing due to views being too big and not properly getting a getTop()
This new solution uses 4 tracking points:
first child, bottom
middle child, top
middle child, bottom
last child, top
that makes sure we always have a isSafeToTrack equals to true
import android.view.View;
import android.widget.AbsListView;
/**
* Created by budius on 16.05.14.
* This improves on Zsolt Safrany answer on stack-overflow (see link)
* by making it a detector that can be attached to any AbsListView.
* http://stackoverflow.com/questions/8471075/android-listview-find-the-amount-of-pixels-scrolled
*/
public class PixelScrollDetector implements AbsListView.OnScrollListener {
private final PixelScrollListener listener;
private TrackElement[] trackElements = {
new TrackElement(0), // top view, bottom Y
new TrackElement(1), // mid view, bottom Y
new TrackElement(2), // mid view, top Y
new TrackElement(3)};// bottom view, top Y
public PixelScrollDetector(PixelScrollListener listener) {
this.listener = listener;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// init the values every time the list is moving
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL ||
scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
for (TrackElement t : trackElements)
t.syncState(view);
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
boolean wasTracked = false;
for (TrackElement t : trackElements) {
if (!wasTracked) {
if (t.isSafeToTrack(view)) {
wasTracked = true;
if (listener != null)
listener.onScroll(view, t.getDeltaY());
t.syncState(view);
} else {
t.reset();
}
} else {
t.syncState(view);
}
}
}
public static interface PixelScrollListener {
public void onScroll(AbsListView view, float deltaY);
}
private static class TrackElement {
private final int position;
private TrackElement(int position) {
this.position = position;
}
void syncState(AbsListView view) {
if (view.getChildCount() > 0) {
trackedChild = getChild(view);
trackedChildPrevTop = getY();
trackedChildPrevPosition = view.getPositionForView(trackedChild);
}
}
void reset() {
trackedChild = null;
}
boolean isSafeToTrack(AbsListView view) {
return (trackedChild != null) &&
(trackedChild.getParent() == view) && (view.getPositionForView(trackedChild) == trackedChildPrevPosition);
}
int getDeltaY() {
return getY() - trackedChildPrevTop;
}
private View getChild(AbsListView view) {
switch (position) {
case 0:
return view.getChildAt(0);
case 1:
case 2:
return view.getChildAt(view.getChildCount() / 2);
case 3:
return view.getChildAt(view.getChildCount() - 1);
default:
return null;
}
}
private int getY() {
if (position <= 1) {
return trackedChild.getBottom();
} else {
return trackedChild.getTop();
}
}
View trackedChild;
int trackedChildPrevPosition;
int trackedChildPrevTop;
}
}
original answer:
First I want to thank #zsolt-safrany for his answer, that was great stuff, total kudos for him.
But then I want to present my improvement on his answer (still is pretty much his answer, just a few improvements)
Improvements:
It's a separate "gesture detector" type of class that can be added to any class that extends AbsListView by calling .setOnScrollListener(), so it's a more flexible approach.
It's using the change in scroll state to pre-allocate the tracked child, so it doesn't "waste" one onScroll pass to allocate its position.
It re-calculate the tracked child on every onScroll pass to avoiding missing random onScroll pass to recalculate child. (this could be make more efficient by caching some heights and only re-calculate after certain amount of scroll).
hope it helps
import android.view.View;
import android.widget.AbsListView;
/**
* Created by budius on 16.05.14.
* This improves on Zsolt Safrany answer on stack-overflow (see link)
* by making it a detector that can be attached to any AbsListView.
* http://stackoverflow.com/questions/8471075/android-listview-find-the-amount-of-pixels-scrolled
*/
public class PixelScrollDetector implements AbsListView.OnScrollListener {
private final PixelScrollListener listener;
private View mTrackedChild;
private int mTrackedChildPrevPosition;
private int mTrackedChildPrevTop;
public PixelScrollDetector(PixelScrollListener listener) {
this.listener = listener;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// init the values every time the list is moving
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL ||
scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
if (mTrackedChild == null) {
syncState(view);
}
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mTrackedChild == null) {
// case we don't have any reference yet, try again here
syncState(view);
} else {
boolean childIsSafeToTrack = (mTrackedChild.getParent() == view) && (view.getPositionForView(mTrackedChild) == mTrackedChildPrevPosition);
if (childIsSafeToTrack) {
int top = mTrackedChild.getTop();
if (listener != null) {
float deltaY = top - mTrackedChildPrevTop;
listener.onScroll(view, deltaY);
}
// re-syncing the state make the tracked child change as the list scrolls,
// and that gives a much higher true state for `childIsSafeToTrack`
syncState(view);
} else {
mTrackedChild = null;
}
}
}
private void syncState(AbsListView view) {
if (view.getChildCount() > 0) {
mTrackedChild = getChildInTheMiddle(view);
mTrackedChildPrevTop = mTrackedChild.getTop();
mTrackedChildPrevPosition = view.getPositionForView(mTrackedChild);
}
}
private View getChildInTheMiddle(AbsListView view) {
return view.getChildAt(view.getChildCount() / 2);
}
public static interface PixelScrollListener {
public void onScroll(AbsListView view, float deltaY);
}
}

Try to implement OnScrollListener:
list.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
int last = view.getLastVisiblePosition();
break;
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
});

Related

New way to detect if WebView has scrolled to the bottom - getScale depreciated

After doing a lot of research on stackoverflow and looking for answers I found that I needed to create a subclass of WebView and then do an override on OnScrollChanged etc.. I have the following code...
SearchResultsWebView.setOnScrollChangedCallback(
new Sub_WebView_Results.OnScrollChangedCallback() {
#Override
public void onScroll(int l, int t) {
int tek = (int) Math.floor(SearchResultsWebView.getContentHeight() * SearchResultsWebView.getScale());
if (tek - SearchResultsWebView.getScrollY() == SearchResultsWebView.getHeight())
Toast.makeText(getActivity(), "End", Toast.LENGTH_SHORT).show();
}
});
HOWEVER the problem is that .getScale has been depreciated. I haven't found another way that works.
I tried using ..
SearchResultsWebView.setWebViewClient(new WebViewClient() {
#Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
super.onScaleChanged(view, oldScale, newScale);
currentScale = newScale;
}
});
And then just passing the [currentScale] but it seems this never gets called so I'm at a loss on how to do this.
Content height for web view is returned dp and thus we need to multiply with the devices density multiplier to get the actual height of the content
with the actual height i deduct the webview's height to calculate when the webview will be visible and compare with scroll y every time user picks up their finger.
This is what i did, working perfectly for me.
webView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
if (Math.floor((webView.getContentHeight() * getResources().getDisplayMetrics().density) - webView.getHeight()) == webView.getScrollY()) {
// Bottom Reached , it is necessary to calculate content height because
// it changes showAgreeButton();
return true;
}
return false;
}
});
}
In order to tell if the user has scrolled to the bottom of a web view, I extended the web view and had an interface callback when the user has got to the bottom of the view onScrollChanged. Here is the code:
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.webkit.WebView;
public class EULAWebView extends WebView {
//declare needed constants
private final String TAG = getClass().getSimpleName();
//declare needed variables
private EULAWebInterface eulaWebInteface;
private int paddingOffset = 200;
private boolean bottomReached;
public EULAWebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public EULAWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public EULAWebView(Context context) {
super(context);
}
public void setEULAScrollListener(Context context) {
try {
eulaWebInteface = (EULAWebInterface)context;
} catch (ClassCastException ex) {
Log.e(TAG, "UNABLE TO CAST CONTEXT TO EULAWebInterface");
ex.printStackTrace();
throw new ClassCastException();
}
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
if(this.computeVerticalScrollRange() <= (this.computeVerticalScrollOffset() +
this.computeVerticalScrollExtent() + this.paddingOffset)) {
if(!bottomReached) {
bottomReached = true;
if(eulaWebInteface != null)
eulaWebInteface.atBottomOfScrollView(true);
}
} else {
if(bottomReached) {
bottomReached = false;
if(eulaWebInteface != null)
eulaWebInteface.atBottomOfScrollView(false);
}
}
super.onScrollChanged(l, t, oldl, oldt);
}
}
Here is the interface that is used to let the activity know that the bottom of the scroll view has changed:
public interface EULAWebInterface {
void atBottomOfScrollView(boolean atBottom);
}
And here is the interface implementation in the activity:
#Override
public void atBottomOfScrollView(boolean atBottom) {
findViewById(R.id.eula_action_layout).setVisibility(atBottom ? View.VISIBLE : View.GONE);
findViewById(R.id.eula_instruction_textview).setVisibility(atBottom ? View.GONE : View.VISIBLE);
}
Apparently I found the answer from: How can i get the current scale of a webView(android)
Instead of WebView.getScale()
You can use: getResources().getDisplayMetrics().density
Try this:
#Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
View view = (View) getChildAt(getChildCount()-1);
int diff = (view.getBottom()-(getHeight()+getScrollY()));// Calculate the difference in scrolling
if( diff == 0 ){ // The bottom has been reached if the difference is 0
Log.d(ScrollTest.LOG_TAG, "WebView: Bottom has been reached" );
// DO SOMETHING HERE WHEN THE WEBVIEW HAS REACHED THE BOTTOM!
}
super.onScrollChanged(x, y, oldx, oldy);
}
Btw, why use the scaling method when the above method may work better (it's easier to implement I think)

Detect Scroll Up & Scroll down in ListView

I have the following requirement:
At first, data for page no: 2 is fetched from the server & the items
are populated in a ListView.
Considering that both the prev page & next page are available in a scenario, the following code has been added:
if(prevPageNo > 0){
mListViewActual.setOnScrollListener(this);
}
if(nextPageNo > 0){
mListViewActual.setOnScrollListener(this);
}
What conditions should I put to detect scroll up & scroll down on the following methods:
void onScroll(AbsListView view, int firstVisibleItem, int
visibleItemCount, int totalItemCount)
void onScrollStateChanged(AbsListView view, int scrollState)
After the action: scroll up & scroll down is detected , accordingly a service will be called with either the prev page no or next page no , to fetch the items to be populated in the Listview.
Any inputs will be helpful.
Gone through the following links but its not returning the correct scroll up / scroll down action:
link 1
link 2
try using the setOnScrollListener and implement the onScrollStateChanged with scrollState
setOnScrollListener(new OnScrollListener(){
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
final ListView lw = getListView();
if(scrollState == 0)
Log.i("a", "scrolling stopped...");
if (view.getId() == lw.getId()) {
final int currentFirstVisibleItem = lw.getFirstVisiblePosition();
if (currentFirstVisibleItem > mLastFirstVisibleItem) {
mIsScrollingUp = false;
Log.i("a", "scrolling down...");
} else if (currentFirstVisibleItem < mLastFirstVisibleItem) {
mIsScrollingUp = true;
Log.i("a", "scrolling up...");
}
mLastFirstVisibleItem = currentFirstVisibleItem;
}
}
});
Here is a working modified version from some of the above-indicated solutions.
Add another class ListView:
package com.example.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AbsListView;
public class ListView extends android.widget.ListView {
private OnScrollListener onScrollListener;
private OnDetectScrollListener onDetectScrollListener;
public ListView(Context context) {
super(context);
onCreate(context, null, null);
}
public ListView(Context context, AttributeSet attrs) {
super(context, attrs);
onCreate(context, attrs, null);
}
public ListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
onCreate(context, attrs, defStyle);
}
#SuppressWarnings("UnusedParameters")
private void onCreate(Context context, AttributeSet attrs, Integer defStyle) {
setListeners();
}
private void setListeners() {
super.setOnScrollListener(new OnScrollListener() {
private int oldTop;
private int oldFirstVisibleItem;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (onScrollListener != null) {
onScrollListener.onScrollStateChanged(view, scrollState);
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (onScrollListener != null) {
onScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
if (onDetectScrollListener != null) {
onDetectedListScroll(view, firstVisibleItem);
}
}
private void onDetectedListScroll(AbsListView absListView, int firstVisibleItem) {
View view = absListView.getChildAt(0);
int top = (view == null) ? 0 : view.getTop();
if (firstVisibleItem == oldFirstVisibleItem) {
if (top > oldTop) {
onDetectScrollListener.onUpScrolling();
} else if (top < oldTop) {
onDetectScrollListener.onDownScrolling();
}
} else {
if (firstVisibleItem < oldFirstVisibleItem) {
onDetectScrollListener.onUpScrolling();
} else {
onDetectScrollListener.onDownScrolling();
}
}
oldTop = top;
oldFirstVisibleItem = firstVisibleItem;
}
});
}
#Override
public void setOnScrollListener(OnScrollListener onScrollListener) {
this.onScrollListener = onScrollListener;
}
public void setOnDetectScrollListener(OnDetectScrollListener onDetectScrollListener) {
this.onDetectScrollListener = onDetectScrollListener;
}
}
And an interface:
public interface OnDetectScrollListener {
void onUpScrolling();
void onDownScrolling();
}
And finally how to use:
com.example.view.ListView listView = (com.example.view.ListView) findViewById(R.id.list);
listView.setOnDetectScrollListener(new OnDetectScrollListener() {
#Override
public void onUpScrolling() {
/* do something */
}
#Override
public void onDownScrolling() {
/* do something */
}
});
In your XML layout:
<com.example.view.ListView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
This is my first topic, do not judge me harshly. =)
this is a simple implementation:
lv.setOnScrollListener(new OnScrollListener() {
private int mLastFirstVisibleItem;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if(mLastFirstVisibleItem<firstVisibleItem)
{
Log.i("SCROLLING DOWN","TRUE");
}
if(mLastFirstVisibleItem>firstVisibleItem)
{
Log.i("SCROLLING UP","TRUE");
}
mLastFirstVisibleItem=firstVisibleItem;
}
});
and if you need more precision, you can use this custom ListView class:
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ListView;
/**
* Created by root on 26/05/15.
*/
public class ScrollInterfacedListView extends ListView {
private OnScrollListener onScrollListener;
private OnDetectScrollListener onDetectScrollListener;
public ScrollInterfacedListView(Context context) {
super(context);
onCreate(context, null, null);
}
public ScrollInterfacedListView(Context context, AttributeSet attrs) {
super(context, attrs);
onCreate(context, attrs, null);
}
public ScrollInterfacedListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
onCreate(context, attrs, defStyle);
}
#SuppressWarnings("UnusedParameters")
private void onCreate(Context context, AttributeSet attrs, Integer defStyle) {
setListeners();
}
private void setListeners() {
super.setOnScrollListener(new OnScrollListener() {
private int oldTop;
private int oldFirstVisibleItem;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (onScrollListener != null) {
onScrollListener.onScrollStateChanged(view, scrollState);
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (onScrollListener != null) {
onScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
if (onDetectScrollListener != null) {
onDetectedListScroll(view, firstVisibleItem);
}
}
private void onDetectedListScroll(AbsListView absListView, int firstVisibleItem) {
View view = absListView.getChildAt(0);
int top = (view == null) ? 0 : view.getTop();
if (firstVisibleItem == oldFirstVisibleItem) {
if (top > oldTop) {
onDetectScrollListener.onUpScrolling();
} else if (top < oldTop) {
onDetectScrollListener.onDownScrolling();
}
} else {
if (firstVisibleItem < oldFirstVisibleItem) {
onDetectScrollListener.onUpScrolling();
} else {
onDetectScrollListener.onDownScrolling();
}
}
oldTop = top;
oldFirstVisibleItem = firstVisibleItem;
}
});
}
#Override
public void setOnScrollListener(OnScrollListener onScrollListener) {
this.onScrollListener = onScrollListener;
}
public void setOnDetectScrollListener(OnDetectScrollListener onDetectScrollListener) {
this.onDetectScrollListener = onDetectScrollListener;
}
public interface OnDetectScrollListener {
void onUpScrolling();
void onDownScrolling();
}
}
an example for use:
(don't forget to add it as an Xml Tag in your layout.xml)
scrollInterfacedListView.setOnDetectScrollListener(new ScrollInterfacedListView.OnDetectScrollListener() {
#Override
public void onUpScrolling() {
//Do your thing
}
#Override
public void onDownScrolling() {
//Do your thing
}
});
With all the method posted, there are problems recognizing when the user is scrolling up from the first element or down from the last.
Here is another approach to detect scroll up/down:
listView.setOnTouchListener(new View.OnTouchListener() {
float height;
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
float height = event.getY();
if(action == MotionEvent.ACTION_DOWN){
this.height = height;
}else if(action == MotionEvent.ACTION_UP){
if(this.height < height){
Log.v(TAG, "Scrolled up");
}else if(this.height > height){
Log.v(TAG, "Scrolled down");
}
}
return false;
}
});
ListView listView = getListView();
listView.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
view.setOnTouchListener(new OnTouchListener() {
private float mInitialX;
private float mInitialY;
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mInitialX = event.getX();
mInitialY = event.getY();
return true;
case MotionEvent.ACTION_MOVE:
final float x = event.getX();
final float y = event.getY();
final float yDiff = y - mInitialY;
if (yDiff > 0.0) {
Log.d(tag, "SCROLL DOWN");
scrollDown = true;
break;
} else if (yDiff < 0.0) {
Log.d(tag, "SCROLL up");
scrollDown = true;
break;
}
break;
}
return false;
}
});
My solution works perfectly giving the exact value for each scroll direction.
distanceFromFirstCellToTop contains the exact distance from the first cell to the top of the parent View. I save this value in previousDistanceFromFirstCellToTop and as I scroll I compare it with the new value. If it's lower then I scrolled up, else, I scrolled down.
private int previousDistanceFromFirstCellToTop;
listview.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
View firstCell = listview.getChildAt(0);
int distanceFromFirstCellToTop = listview.getFirstVisiblePosition() * firstCell.getHeight() - firstCell.getTop();
if(distanceFromFirstCellToTop < previousDistanceFromFirstCellToTop)
{
//Scroll Up
}
else if(distanceFromFirstCellToTop > previousDistanceFromFirstCellToTop)
{
//Scroll Down
}
previousDistanceFromFirstCellToTop = distanceFromFirstCellToTop;
}
});
For Xamarin developers, the solution is the following:
Note: don't forget to run on UI thread
listView.Scroll += (o, e) =>
{
View firstCell = listView.GetChildAt(0);
int distanceFromFirstCellToTop = listView.FirstVisiblePosition * firstCell.Height - firstCell.Top;
if (distanceFromFirstCellToTop < previousDistanceFromFirstCellToTop)
{
//Scroll Up
}
else if (distanceFromFirstCellToTop > previousDistanceFromFirstCellToTop)
{
//Scroll Down
}
previousDistanceFromFirstCellToTop = distanceFromFirstCellToTop;
};
Just set scroll listener to your listview.
If you have a header or footer you should check the visible count too. If it increases it means you are scrolling down. (Reverse it if there is a footer instead of header)
If you don't have any header or footer in your listview you can remove the lines which cheks the visible item count.
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mLastFirstVisibleItem > firstVisibleItem) {
Log.e(getClass().toString(), "scrolling up");
} else if (mLastFirstVisibleItem < firstVisibleItem) {
Log.e(getClass().toString(), "scrolling down");
} else if (mLastVisibleItemCount < visibleItemCount) {
Log.e(getClass().toString(), "scrolling down");
} else if (mLastVisibleItemCount > visibleItemCount) {
Log.e(getClass().toString(), "scrolling up");
}
mLastFirstVisibleItem = firstVisibleItem;
mLastVisibleItemCount = visibleItemCount;
}
public void onScrollStateChanged(AbsListView listView, int scrollState) {
}
});
and have this variables
int mLastFirstVisibleItem;
int mLastVisibleItemCount;
I've used this much simpler solution:
setOnScrollListener( new OnScrollListener()
{
private int mInitialScroll = 0;
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount)
{
int scrolledOffset = computeVerticalScrollOffset();
boolean scrollUp = scrolledOffset > mInitialScroll;
mInitialScroll = scrolledOffset;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
}
To also detect scrolling with larger elements, I prefere an onTouch Listener:
listview.setOnTouchListener(new View.OnTouchListener() {
int scrollEventListSize = 5;
float lastY;
// Used to correct for occasions when user scrolls down(/up) but the onTouchListener detects it incorrectly. We will store detected up-/down-scrolls with -1/1 in this list and evaluate later which occured more often
List<Integer> downScrolledEventsHappened;
#Override
public boolean onTouch(View v, MotionEvent event) {
float diff = 0;
if(event.getAction() == event.ACTION_DOWN){
lastY = event.getY();
downScrolledEventsHappened = new LinkedList<Integer>();
}
else if(event.getAction() == event.ACTION_MOVE){
diff = event.getY() - lastY;
lastY = event.getY();
if(diff>0)
downScrolledEventsHappened.add(1);
else
downScrolledEventsHappened.add(-1);
//List needs to be filled with some events, will happen very quickly
if(downScrolledEventsHappened.size() == scrollEventListSize+1){
downScrolledEventsHappened.remove(0);
int res=0;
for(int i=0; i<downScrolledEventsHappened.size(); i++){
res+=downScrolledEventsHappened.get(i);
}
if (res > 0)
Log.i("INFO", "Scrolled up");
else
Log.i("INFO", "Scrolled down");
}
}
return false; // don't interrupt the event-chain
}
});
Store the firstVisibleItem and on the next onScroll check if the new firstVisibleItem is smaller or greater than the previous one.
Example pseudocode (not tested):
int lastVisibleItem = 0;
boolean isScrollingDown = false;
void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem > lastVisibleItem) {
isScrollingDown = true;
}
else {
isScrollingDown = false;
}
lastVisibleItem = firstVisibleItem;
}
For some reason the Android doc doesnt cover this, and the method used isnt even in the docs... took me a while to find it.
To detect if your scroll is at the top you would use this.
public boolean checkAtTop()
{
if(listView.getChildCount() == 0) return true;
return listView.getChildAt(0).getTop() == 0;
}
This will check if your scroller is at the top. Now, in order to do it for the bottom, you would have to pass it the number of children that you have, and check against that number. You might have to figure out how many are on the screen at one time, and subtract that from your number of children. I've never had to do that. Hope this helps
Those methods cannot be used to detect scrolling directions directly. There are many ways of getting the direction. A simple code(untested) for one such method is explained below :
public class ScrollTrackingListView extends ListView {
private boolean readyForMeasurement = false;
private Boolean isScrollable = null;
private float prevDistanceToEnd = -1.0;
private ScrollDirectionListener listener = null;
public ScrollTrackingListView(Context context) {
super(context);
init();
}
public ScrollTrackingListView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ScrollTrackingListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
ViewTreeObserver observer = getViewTreeObserver();
observer.addOnGlobalLayoutListener(globalLayoutListener);
setOnScrollListener(scrollListener);
}
private ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener
= new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
readyForMeasurement = true;
calculateDistanceToEnd();
}
};
public void registerScrollDirectionListener(ScrollDirectionListener listener) {
scrollDirectionListener = listener;
}
public void unregisterScrollDirectionListener() {
scrollDirectionListener = null;
}
private OnScrollListener scrollListener
= new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView absListView, int i) {
calculateDistanceToEnd();
}
#Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
// Do nothing
}
};
private void calculateDistanceToEnd() {
if (readyForMeasurement) {
// I'm using the height of the layout, horizontal scrollbar and
// content along with scroll down offset
// computeVerticalScrollExtent is used to compute the length of the thumb within the scrollbar's track.
// The length of the thumb is a function of the view height and the content length.
int verticalScrollExtent = computeVerticalScrollExtent();
int verticalScrollOffset = computeVerticalScrollOffset();
int verticalScrollRange = computeVerticalScrollRange();
int horizontalScrollBarHeight = getHorizontalScrollbarHeight();
/**
* 1. Let "R" represent the range of the vertical scrollbar. This corresponds to the length of the content
* in the view.
* 2. Let "E" represent the extent of the vertical scrollbar. The extent is a constant value and is
* (probably) equal to a value proportional to the height of the view.
* 3. Offset "o" represents the current position in the range that is visible to the user. It can take
* values from "0 to E".
*
* Now the DistanceToEnd is calculated using these three values as follows :
*
* DistanceToEnd = (R - o) / E
*
* DistanceToEnd will hold the value in NumberOfScreenToEnd units.
*
*/
float distanceToEnd =
((float)(verticalScrollRange - verticalScrollOffset))/((float)(verticalScrollExtent));
if(prevDistanceToEnd == -1) {
prevDistanceToEnd = distanceToEnd;
} else {
if(listener != null) {
if(distanceToEnd > prevDistanceToEnd) {
// User is scrolling up
listener.onScrollingUp();
} else {
// User is scrolling up
listener.onScrollingDown();
}
}
prevDistanceToEnd = distanceToEnd;
}
if(isScrollable == null) {
// Check if the view height is less than a screen (i.e., no scrolling is enabled)
if((horizontalScrollBarHeight + verticalScrollExtent) >= verticalScrollRange) {
isScrollable = Boolean.FALSE;
} else {
isScrollable = Boolean.TRUE;
}
}
}
}
public interface ScrollDirectionListener {
public void onScrollingUp();
public void onScrollingDown();
}
}
The idea is to calculate the distanceToEnd. If distanceToEnd increases, the user is scrolling up and if it decreases, the user is scrolling down. That will also give you the exact distance to the end of the list.
If you are just trying to know whether the user is scrolling up or down you can override the onInterceptTouchEvent to detect the scrolling direction like below :
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mInitialX = event.getX();
mInitialY = event.getY();
return true;
case MotionEvent.ACTION_MOVE:
final float x = event.getX();
final float y = event.getY();
final float yDiff = y - mInitialY; // yDiff less than 0.0 implies scrolling down while yDiff greater than 0.0 implies scrolling up. If I try to add the less than or greater than symbols, the preview refuses to display it.
if(yDiff less than 0.0) listener.onScrollingDown();
else if(yDiff greater than 0.0) listener.onScrollingUp();
break;
}
return super.onInterceptTouchEvent(event);
}
Trick about detect scroll up or down in listview, you just call this function on onScroll function in OnScrollListener of ListView.
private int oldFirstVisibleItem = -1;
private protected int oldTop = -1;
// you can change this value (pixel)
private static final int MAX_SCROLL_DIFF = 5;
private void calculateListScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem == oldFirstVisibleItem) {
int top = view.getChildAt(0).getTop();
// range between new top and old top must greater than MAX_SCROLL_DIFF
if (top > oldTop && Math.abs(top - oldTop) > MAX_SCROLL_DIFF) {
// scroll up
} else if (top < oldTop && Math.abs(top - oldTop) > MAX_SCROLL_DIFF) {
// scroll down
}
oldTop = top;
} else {
View child = view.getChildAt(0);
if (child != null) {
oldFirstVisibleItem = firstVisibleItem;
oldTop = child.getTop();
}
}
}
Simple way to detect scroll up/down on android listview
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount){
if(prevVisibleItem != firstVisibleItem){
if(prevVisibleItem < firstVisibleItem)
//ScrollDown
else
//ScrollUp
prevVisibleItem = firstVisibleItem;
}
dont forget
yourListView.setOnScrollListener(yourScrollListener);
Simple way to load more items on scroll up/down event in android GridView
grid.setOnScrollListener(new AbsListView.OnScrollListener() {
private int mLastFirstVisibleItem;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
Log.d("state",String.valueOf(scrollState));
if(scrollState == 0)
Log.i("a", "scrolling stopped...");
if (view.getId() == grid.getId()) {
final int currentFirstVisibleItem = grid.getLastVisiblePosition();
mLastFirstVisibleItem = grid.getFirstVisiblePosition();
if (currentFirstVisibleItem > mLastFirstVisibleItem) {
mIsScrollingUp = false;
if(!next.contains("null")){
//Call api to get products from server
}
Log.i("a", "scrolling down...");
} else if (currentFirstVisibleItem < mLastFirstVisibleItem) {
mIsScrollingUp = true;
Log.i("a", "scrolling up...");
}
mLastFirstVisibleItem = currentFirstVisibleItem;
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Log.d("on scroll","");
}
});
Here's what I would try first:
1) Create an interface (let's call it OnScrollTopOrBottomListener) with these methods:
void onScrollTop();
void onScrollBottom();
2) In your list's adapter, add a member instance, typed as the interface you created and supply a setter and getter.
3) In the getView() implementation of your adapter, check if the position parameter is either 0 or getCount() - 1. Also check that your OnScrollTopOrBottomListener instance is not null.
4) If the position is 0, call onScrollTopOrBottomListener.onScrollTop(). If position is getCount() - 1, call onScrollTopOrBottomListener.onScrollBottom().
5) In your OnScrollTopOrBottomListener implementation, call the appropriate methods to get the desired data.
Hope that helps in some way.
-Brandon
I have encountered problems using some example where the cell size of ListView is great. So I have found a solution to my problem which detects the slightest movement of your finger . I've simplified to the minimum possible and is as follows:
private int oldScrolly;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
View view = absListView.getChildAt(0);
int scrolly = (view == null) ? 0 : -view.getTop() + absListView.getFirstVisiblePosition() * view.getHeight();
int margin = 10;
Log.e(TAG, "Scroll y: " + scrolly + " - Item: " + firstVisibleItem);
if (scrolly > oldScrolly + margin) {
Log.d(TAG, "SCROLL_UP");
oldScrolly = scrolly;
} else if (scrolly < oldScrolly - margin) {
Log.d(TAG, "SCROLL_DOWN");
oldScrolly = scrolly;
}
}
});
PD: I use the MARGIN to not detect the scroll until you meet that margin . This avoids problems when I show or hide views and avoid blinking of them.

Android: Making a button visible once webview is done scrolling

I have a webview which shows an html file. When the user scrolls to the bottom of this file in webview, I want a button that was previously hidden to show up, which the user can then press to do some activity
I did something similar in iOS, where I just set the delegate to the ViewController and just set the button as visible. How do I do something similar on Android? I noticed there isn't a callback method like in iOS.
Edit: Right now, I have an activity with 2 objects: a webview containing my text, and a button which is currently invisible. I want my activity to receive a message when the webview text scrolls to the bottom, and make the button visible
I had to do this myself, in order to display an "I Agree" button once the user has scrolled to the bottom of a EULA. Lawyers, huh?
In fact when you override the WebView (rather than the ScrollView as in the answer from #JackTurky) you can call computeVerticalScrollRange() to get the height of the content, rather than getBottom() which returns the visible bottom and is not useful.
This is my comprehensive solution. As far as I can see this is all API Level 1 stuff, so it should work anywhere.
public class EulaWebView extends WebView {
public EulaWebView(Context context)
{
this(context, null);
}
public EulaWebView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public EulaWebView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
public OnBottomReachedListener mOnBottomReachedListener = null;
private int mMinDistance = 0;
/**
* Set the listener which will be called when the WebView is scrolled to within some
* margin of the bottom.
* #param bottomReachedListener
* #param allowedDifference
*/
public void setOnBottomReachedListener(OnBottomReachedListener bottomReachedListener, int allowedDifference ) {
mOnBottomReachedListener = bottomReachedListener;
mMinDistance = allowedDifference;
}
/**
* Implement this interface if you want to be notified when the WebView has scrolled to the bottom.
*/
public interface OnBottomReachedListener {
void onBottomReached(View v);
}
#Override
protected void onScrollChanged(int left, int top, int oldLeft, int oldTop) {
if ( mOnBottomReachedListener != null ) {
if ( (computeVerticalScrollRange() - (top + getHeight())) <= mMinDistance )
mOnBottomReachedListener.onBottomReached(this);
}
super.onScrollChanged(left, top, oldLeft, oldTop);
}
}
I use this to display an "I Agree" button once the user has scrolled to the bottom of the WebView, where I call it like this (in a class which "implements OnBottomReachedListener":
EulaWebView mEulaContent;
Button mEulaAgreed;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.eula);
mEulaContent = (EulaWebView) findViewById(R.id.eula_content);
StaticHelpers.loadWebView(this, mEulaContent, R.raw.stylesheet, StaticHelpers.readRawTextFile(this, R.raw.eula), null);
mEulaContent.setVerticalScrollBarEnabled(true);
mEulaContent.setOnBottomReachedListener(this, 50);
mEulaAgreed = (Button) findViewById(R.id.eula_agreed);
mEulaAgreed.setOnClickListener(this);
mEulaAgreed.setVisibility(View.GONE);
}
#Override
public void onBottomReached(View v) {
mEulaAgreed.setVisibility(View.VISIBLE);
}
So when the bottom is reached (or in this case, when they get within 50 pixels of it) the "I Agree" button appears.
[I can't comment on an answer, so leaving my comment here as a new answer]
karora's answer (the first) works very well, except that in the
protected void onScrollChanged(int left, int top, int oldLeft, int oldTop)
method, calling
getContentHeight()
was wildly inaccurate for me. It reported a value much too small, so my listener was called when the user had only scrolled maybe a third of the way down the WebView. I used
computeVerticalScrollRange()
instead, and that is perfect. Thanks to this post for that helpful hint.
try this:
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
View view = (View) getChildAt(getChildCount()-1);
int diff = (view.getBottom()-(getHeight()+getScrollY()));// Calculate the scrolldiff
if( diff == 0 ){ // if diff is zero, then the bottom has been reached
Log.d(ScrollTest.LOG_TAG, "MyScrollView: Bottom has been reached" );
yourButton.setVisible(true);
}
super.onScrollChanged(l, t, oldl, oldt);
}
To implement this, extend ScrollView and then override the onScrollChanged method (inherited from View).
Loading / Visible button only when webview reached / scrolled to bottom.
Create JavaScript class :
public class JavaScriptInterface {
#android.webkit.JavascriptInterface
public void didScrollToBottom() {
Log.d(TAG, "Scroll to Bottom");
myHandler.post(new Runnable() {
#Override
public void run() {
btnAccept.setVisibility(View.VISIBLE);
}
});
}
}
In onCreate() :
final JavaScriptInterface jsInterface = new JavaScriptInterface();
myWebView.addJavascriptInterface(jsInterface, "AndroidFunction");
Solutions above didn't fully work for me for the similar issue (hide button while webView is being scrolled, show after scrolling is over). The reason I wanted it to hide while scrolling is because button I want to hide is for jumping to the very bottom of webview, and when it only worked for me when webview is static, but didn't jump to bottom while view is still being scrolled.
So I did the following:
added a onScrollChanged callback to overridden webView, like suggested nearby:
private OnScrollChangedCallback mOnScrollChangedCallback;
public OnScrollChangedCallback getOnScrollChangedCallback() {
return mOnScrollChangedCallback;
}
public void setOnScrollChangedCallback(
final OnScrollChangedCallback onScrollChangedCallback) {
mOnScrollChangedCallback = onScrollChangedCallback;
}
#Override
protected void onScrollChanged(final int l, final int t, final int oldl,
final int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mOnScrollChangedCallback != null){
mOnScrollChangedCallback.onScrollChanged(l, t);
}
}
/**
* Implement in the activity/fragment/view that you want to listen to the
* webview
*/
public static interface OnScrollChangedCallback {
public void onScrollChanged(int l, int t);
}
and in my activity class which implements OnScrollChangedCallback
UPDATED:
Timer timer2showJumpButton;
private long lastScrollEventTimestamp;
public final static int HIDING_JUMP_BUTTON_ON_SCROLL_DELAY = 500;
public void onScrollChanged(int l, int t) {
// showing button when scrolling starts
if (btnJumpToBottom != null) {
btnJumpToBottom.setVisibility(View.VISIBLE);
}
if (btnJumpToTop!= null) {
btnJumpToTop.setVisibility(View.VISIBLE);
}
if (timer2showJumpButton == null) {
final Runnable r2 = new Runnable() {
#Override
public void run() {
if (btnJumpToBottom != null) {
btnJumpToBottom.setVisibility(View.GONE);
}
if (btnJumpToTop!= null) {
btnJumpToTop.setVisibility(View.GONE);
}
}
};
TimerTask timerTask = new TimerTask() {
#Override
public void run() {
if (btnJumpToTop.getVisibility() == View.VISIBLE || btnJumpToBottom.getVisibility() == View.VISIBLE){
long currentTimestamp = System.currentTimeMillis();
if (currentTimestamp - lastScrollEventTimestamp > HIDING_JUMP_BUTTON_ON_SCROLL_DELAY1 ){
webView.postDelayed(r2, HIDING_JUMP_BUTTON_ON_SCROLL_DELAY);
}else{
//too soon
}
}
}
};
try {
timer2showJumpButton = new Timer();
timer2showJumpButton.schedule(timerTask, 500, 500);
} catch (IllegalStateException e) {
logger.warn(TAG + "/onScrollChanged/" + e.getMessage());
}
}
// adding runnable which will hide button back
long currentTimestamp = System.currentTimeMillis();
lastScrollEventTimestamp = currentTimestamp;
}

How to make a ViewPager loop?

I have a ViewPager with some views. I'd like to go to the first one after right swiping on the last one.
I tried
#Override
public Fragment getItem(int arg0) {
int i = arg0 % fragmentList.size();
return fragmentList.get(i);
}
#Override
public int getCount() {
return fragmentList.size()+1;
}
But I got an error
E/AndroidRuntime(22912): java.lang.IllegalStateException: Fragment already added: RubricFragment{4136cd80 #1 id=0x7f06000c android:switcher:2131099660:0}
One possibility is setting up the screens like this:
C' A B C A'
C' looks just like C, but when you scroll to there, it switches you to the real C.
A' looks just like A, but when you scroll to there, it switches you to the real A.
I would do this by implementing onPageScrollStateChanged like so:
#Override
public void onPageScrollStateChanged (int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
int curr = viewPager.getCurrentItem();
int lastReal = viewPager.getAdapter().getCount() - 2;
if (curr == 0) {
viewPager.setCurrentItem(lastReal, false);
} else if (curr > lastReal) {
viewPager.setCurrentItem(1, false);
}
}
}
Note that this calls the alternate form of setCurrentItem and passes false to cause the jump to happen instantly rather than as a smooth scroll.
There are two main drawbacks I see to this. Firstly, upon reaching either end the user has to let the scrolling settle before they can go further. Secondly, it means having a second copy of all of the views in your first and last page. Depending on how resource-heavy your screens are, that may rule out this technique as a possible solution.
Note also that since the view pager doesn't let clicks go through to underlying controls until after the scrolling has settled, it's probably fine to not set up clicklisteners and the like for the A' and C' fragments.
Edit: Having now implemented this myself, there's another pretty major drawback. When it switches from A' to A or C' to C, the screen flickers for a moment, at least on my current test device.
I would create a dummy page at the end of the ViewPager.
Then I use this code to go to the first page when the user scroll to the dummy page. I know it's far from perfect :D
#Override
public void onPageScrolled(int position, float arg1, int arg2) {
if (position >= NUM_PAGE-1) {
mViewPager.setCurrentItem(0, true);
}
}
My solution is based on benkc, but first and last page scroll animation are disabled, and when pages "scrolled" to real page, scroll animation is enable again, this scheme can solve the first drawback.
but my ViewPager.setCurrentItem(position, false) result is still have scroll animation, so i implements animation which is too fast to seen.
the fast scrolling animation like this, don't mind the comment, just my code didn't use these method:
public class FixedSpeedScroller extends Scroller {
private int mDuration = 0;
public FixedSpeedScroller(Context context) {
super(context);
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, mDuration);
}
#Override
public void startScroll(int startX, int startY, int dx, int dy) {
super.startScroll(startX, startY, dx, dy, mDuration);
}
}
and use this method to viewpager's activity
private Scroller scroller;
private void setViewPagerScroll(boolean instant) {
try {
Field mScroller = null;
mScroller = ViewPager.class.getDeclaredField("mScroller");
mScroller.setAccessible(true);
if (scroller == null) {
scroller = (Scroller) mScroller.get(mViewPager);
}
FixedSpeedScroller fss = new FixedSpeedScroller(mViewPager.getContext());
mScroller.set(mViewPager, instant ? fss : scroller);
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
and modify onPageScrollStateChanged like this, only first page or last page (i have 5 pages) would change animation to fast scrolling, otherwise has normal scrolling:
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
if (position == 0) {
setViewPagerScroll(true);
mViewPager.setCurrentItem(3);
} else if (position == 4) {
setViewPagerScroll(true);
mViewPager.setCurrentItem(1);
} else {
setViewPagerScroll(false);
}
}
}
FixedSpeedScroller references is here: http://blog.csdn.net/ekeuy/article/details/12841409
Kotlin Version:
Initailize the variables
private var mCurrentPosition = 0
private var mScrollState = 0
private lateinit var mImageViewPager: ViewPager
onCreate:
mImageViewPager = findViewById<View>(R.id.pager) as ViewPager
mImageViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
handleScrollState(state)
mScrollState = state
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
mCurrentPosition = position
}
})
functions outside onCreate:
private fun handleScrollState(state: Int) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
if (!isScrollStateSettling()){
val lastPosition: Int = mImageViewPager.getAdapter()?.getCount()!! - 1
if (mCurrentPosition == 0) {
mImageViewPager.setCurrentItem(lastPosition , false)
} else if (mCurrentPosition == lastPosition) {
mImageViewPager.setCurrentItem(0 , false)
}
}
}
}
private fun isScrollStateSettling(): Boolean {
return mScrollState == ViewPager.SCROLL_STATE_SETTLING
}
this should do the job without dummy pages:
private boolean isFirstOrLastPage;
private int currentPageIndex = 0;
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
if(currentPageIndex!=arg0){
isFirstOrLastPage = false;
return;
}
if((arg0==0 || arg0==PAGES.size()-1) && arg1 == 0 && arg2 == 0){
if(isFirstOrLastPage){
//DO SOMETHING
}else{
isFirstOrLastPage = true;
}
}
}
#Override
public void onPageSelected(int arg0) {
currentPageIndex = arg0;
}
this works, the accepted answer no good because there is a lag when the loop happens:
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
#Override
public CharSequence getPageTitle(int position) {
String title = mTitleList.get(position % mActualTitleListSize);
return title;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
int virtualPosition = position % mActualTitleListSize;
return super.instantiateItem(container, virtualPosition);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
int virtualPosition = position % mActualTitleListSize;
super.destroyItem(container, virtualPosition, object);
}
answer taken from here : ViewPager as a circular queue / wrapping

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