Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
ScrollView with a OnBottomReachedListener for Android. MIT license (https://opensource.org/licenses/MIT).
package se.marteinn.ui;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ScrollView;
/**
* Triggers a event when scrolling reaches bottom.
*
* Created by martinsandstrom on 2010-05-12.
* Updated by martinsandstrom on 2014-07-22.
*
* Usage:
*
* scrollView.setOnBottomReachedListener(
* new InteractiveScrollView.OnBottomReachedListener() {
* @Override
* public void onBottomReached() {
* // do something
* }
* }
* );
*
*
* Include in layout:
*
* <se.marteinn.ui.InteractiveScrollView
* android:layout_width="match_parent"
* android:layout_height="match_parent" />
*
*/
public class InteractiveScrollView extends ScrollView {
OnBottomReachedListener mListener;
public InteractiveScrollView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public InteractiveScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public InteractiveScrollView(Context context) {
super(context);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
View view = (View) getChildAt(getChildCount()-1);
int diff = (view.getBottom()-(getHeight()+getScrollY()));
if (diff == 0 && mListener != null) {
mListener.onBottomReached();
}
super.onScrollChanged(l, t, oldl, oldt);
}
// Getters & Setters
public OnBottomReachedListener getOnBottomReachedListener() {
return mListener;
}
public void setOnBottomReachedListener(
OnBottomReachedListener onBottomReachedListener) {
mListener = onBottomReachedListener;
}
/**
* Event listener.
*/
public interface OnBottomReachedListener{
public void onBottomReached();
}
}
@dimitardanailov

This comment has been minimized.

Show comment
Hide comment
@dimitardanailov

dimitardanailov commented May 31, 2015

Thank you.

@davideas

This comment has been minimized.

Show comment
Hide comment
@davideas

davideas Jun 18, 2015

Hi Marteein

  • if (mListener != null) should contain all the code but the super;
  • diff check, should be <= 0;
  • paddingBottom is missing from the calculation, so you should add it. You can look the original code from ScrollView methods.
  • Also remove the useless cast to View

Kind Regards.
Davide

davideas commented Jun 18, 2015

Hi Marteein

  • if (mListener != null) should contain all the code but the super;
  • diff check, should be <= 0;
  • paddingBottom is missing from the calculation, so you should add it. You can look the original code from ScrollView methods.
  • Also remove the useless cast to View

Kind Regards.
Davide

@leonardpiltz

This comment has been minimized.

Show comment
Hide comment
@leonardpiltz

leonardpiltz Aug 21, 2015

Hi Marteein,
this is a great solution, but how do I trigger the Keyboard state? Because if it fades in, the new bottom is the top of the keyboard

leonardpiltz commented Aug 21, 2015

Hi Marteein,
this is a great solution, but how do I trigger the Keyboard state? Because if it fades in, the new bottom is the top of the keyboard

@XSparter

This comment has been minimized.

Show comment
Hide comment
@XSparter

XSparter Oct 25, 2015

Hi,i have updated your code. My version support "OnTopReachedListener".
Thanks for your help <3

package org.altervista.xsparter.www.updatelayout;

import android.view.View;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ScrollView;

public class InteractiveScrollView extends ScrollView {
OnBottomReachedListener mListener;
OnTopReachedListener mListener2;

public InteractiveScrollView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

public InteractiveScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public InteractiveScrollView(Context context) {
    super(context);
}

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    View view = (View) getChildAt(getChildCount()-1);
    int diff = (view.getBottom()-(getHeight()+getScrollY()));

    if (diff == 0 && mListener != null) {
        mListener.onBottomReached();
    }
    else if (getScrollY() == 0 && mListener2 != null) {
        mListener2.onTopReached();
    }

    super.onScrollChanged(l, t, oldl, oldt);
}


// Getters & Setters

public OnBottomReachedListener getOnBottomReachedListener() {
    return mListener;
}

public void setOnBottomReachedListener(
OnBottomReachedListener onBottomReachedListener) {
    mListener = onBottomReachedListener;
}
public OnTopReachedListener getOnTopReachedListener() {
    return mListener2;
}

public void setOnTopReachedListener(
OnTopReachedListener onTopReachedListener) {
    mListener2 = onTopReachedListener;
}


/**
* Event listener.
*/
public interface OnBottomReachedListener{
    public void onBottomReached();
}
public interface OnTopReachedListener{
    public void onTopReached();
}

}

XSparter commented Oct 25, 2015

Hi,i have updated your code. My version support "OnTopReachedListener".
Thanks for your help <3

package org.altervista.xsparter.www.updatelayout;

import android.view.View;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ScrollView;

public class InteractiveScrollView extends ScrollView {
OnBottomReachedListener mListener;
OnTopReachedListener mListener2;

public InteractiveScrollView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

public InteractiveScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public InteractiveScrollView(Context context) {
    super(context);
}

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    View view = (View) getChildAt(getChildCount()-1);
    int diff = (view.getBottom()-(getHeight()+getScrollY()));

    if (diff == 0 && mListener != null) {
        mListener.onBottomReached();
    }
    else if (getScrollY() == 0 && mListener2 != null) {
        mListener2.onTopReached();
    }

    super.onScrollChanged(l, t, oldl, oldt);
}


// Getters & Setters

public OnBottomReachedListener getOnBottomReachedListener() {
    return mListener;
}

public void setOnBottomReachedListener(
OnBottomReachedListener onBottomReachedListener) {
    mListener = onBottomReachedListener;
}
public OnTopReachedListener getOnTopReachedListener() {
    return mListener2;
}

public void setOnTopReachedListener(
OnTopReachedListener onTopReachedListener) {
    mListener2 = onTopReachedListener;
}


/**
* Event listener.
*/
public interface OnBottomReachedListener{
    public void onBottomReached();
}
public interface OnTopReachedListener{
    public void onTopReached();
}

}

@blackvvine

This comment has been minimized.

Show comment
Hide comment
@blackvvine

blackvvine Dec 31, 2015

Thank you all :-)

blackvvine commented Dec 31, 2015

Thank you all :-)

@VladPalamarchuk

This comment has been minimized.

Show comment
Hide comment
@VladPalamarchuk

VladPalamarchuk Feb 1, 2016

Thank you )) good job ))

VladPalamarchuk commented Feb 1, 2016

Thank you )) good job ))

@arjunbvb

This comment has been minimized.

Show comment
Hide comment
@arjunbvb

arjunbvb Aug 29, 2016

Thanks a lot man , Cheers !!

arjunbvb commented Aug 29, 2016

Thanks a lot man , Cheers !!

@nishsvn-dev

This comment has been minimized.

Show comment
Hide comment
@nishsvn-dev

nishsvn-dev Dec 20, 2016

First of all thank you very much for this.!
I ran into a problem with this. My case is, when reach the bottom, I'm enabling the save button. It works like a charm in the small screen. But when I'm using it the tablet where the screen is big enough not to scroll, the button is still disabled. I'd like to enable the button in this case, please suggest me what I can I do to get this working. Thank you again :)

nishsvn-dev commented Dec 20, 2016

First of all thank you very much for this.!
I ran into a problem with this. My case is, when reach the bottom, I'm enabling the save button. It works like a charm in the small screen. But when I'm using it the tablet where the screen is big enough not to scroll, the button is still disabled. I'd like to enable the button in this case, please suggest me what I can I do to get this working. Thank you again :)

@alcntml

This comment has been minimized.

Show comment
Hide comment
@alcntml

alcntml Feb 1, 2017

I have updated your code. It is support "OnScrolledDownListener" and "OnScrolledUpListener".

public class InteractiveScrollView extends ScrollView{

private OnBottomReachedListener onBottomReachedListener;
private OnTopReachedListener onTopReachedListener;
private OnScrolledDownListener onScrolledDownListener;
private OnScrolledUpListener onScrolledUpListener;

public InteractiveScrollView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init();
}

public InteractiveScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

public InteractiveScrollView(Context context) {
    super(context);
    init();
}

private void init() {
    setFadingEdgeLength(0);
    setVerticalFadingEdgeEnabled(false);
    setHorizontalFadingEdgeEnabled(false);
    setOverScrollMode(ScrollView.OVER_SCROLL_NEVER);
}

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    View view = (View) getChildAt(getChildCount()-1);
    int diff = (view.getBottom()-(getHeight()+getScrollY()));

    if (diff == 0 && onBottomReachedListener != null) {
        onBottomReachedListener.onBottomReached(t);
    } else if (getScrollY() <= 0 && onTopReachedListener != null) {
        onTopReachedListener.onTopReached(t);
    } else{
        if(t<oldt){
            if (onScrolledDownListener != null){
                onScrolledDownListener.onScrolledDown(t);
            }
        }else if (t>oldt){
            if (onScrolledUpListener != null){
                onScrolledUpListener.onScrolledUp(t);
            }
        }
    }

    super.onScrollChanged(l, t, oldl, oldt);
}

//Getters & Setters

public OnBottomReachedListener getOnBottomReachedListener() {
    return onBottomReachedListener;
}

public void setOnBottomReachedListener(OnBottomReachedListener onBottomReachedListener) {
    this.onBottomReachedListener = onBottomReachedListener;
}

public OnTopReachedListener getOnTopReachedListener() {
    return onTopReachedListener;
}

public void setOnTopReachedListener(OnTopReachedListener onTopReachedListener) {
    this.onTopReachedListener = onTopReachedListener;
}

public void setOnScrolledDownListener(OnScrolledDownListener onScrolledDownListener){
    this.onScrolledDownListener = onScrolledDownListener;
}

public OnScrolledDownListener getOnScrolledDownListener() {
    return onScrolledDownListener;
}

public void setOnScrolledUpListener(OnScrolledUpListener onScrolledUpListener){
    this.onScrolledUpListener = onScrolledUpListener;
}

public OnScrolledUpListener getOnScrolledUpListener() {
    return onScrolledUpListener;
}


/**
 * Event listener.
 */
public interface OnBottomReachedListener{
    public void onBottomReached(int scrollY);
}
public interface OnTopReachedListener{
    public void onTopReached(int scrollY);
}
public interface OnScrolledDownListener{
    public void onScrolledDown(int scrollY);
}
public interface OnScrolledUpListener{
    public void onScrolledUp(int scrollY);
}

}

alcntml commented Feb 1, 2017

I have updated your code. It is support "OnScrolledDownListener" and "OnScrolledUpListener".

public class InteractiveScrollView extends ScrollView{

private OnBottomReachedListener onBottomReachedListener;
private OnTopReachedListener onTopReachedListener;
private OnScrolledDownListener onScrolledDownListener;
private OnScrolledUpListener onScrolledUpListener;

public InteractiveScrollView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init();
}

public InteractiveScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

public InteractiveScrollView(Context context) {
    super(context);
    init();
}

private void init() {
    setFadingEdgeLength(0);
    setVerticalFadingEdgeEnabled(false);
    setHorizontalFadingEdgeEnabled(false);
    setOverScrollMode(ScrollView.OVER_SCROLL_NEVER);
}

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    View view = (View) getChildAt(getChildCount()-1);
    int diff = (view.getBottom()-(getHeight()+getScrollY()));

    if (diff == 0 && onBottomReachedListener != null) {
        onBottomReachedListener.onBottomReached(t);
    } else if (getScrollY() <= 0 && onTopReachedListener != null) {
        onTopReachedListener.onTopReached(t);
    } else{
        if(t<oldt){
            if (onScrolledDownListener != null){
                onScrolledDownListener.onScrolledDown(t);
            }
        }else if (t>oldt){
            if (onScrolledUpListener != null){
                onScrolledUpListener.onScrolledUp(t);
            }
        }
    }

    super.onScrollChanged(l, t, oldl, oldt);
}

//Getters & Setters

public OnBottomReachedListener getOnBottomReachedListener() {
    return onBottomReachedListener;
}

public void setOnBottomReachedListener(OnBottomReachedListener onBottomReachedListener) {
    this.onBottomReachedListener = onBottomReachedListener;
}

public OnTopReachedListener getOnTopReachedListener() {
    return onTopReachedListener;
}

public void setOnTopReachedListener(OnTopReachedListener onTopReachedListener) {
    this.onTopReachedListener = onTopReachedListener;
}

public void setOnScrolledDownListener(OnScrolledDownListener onScrolledDownListener){
    this.onScrolledDownListener = onScrolledDownListener;
}

public OnScrolledDownListener getOnScrolledDownListener() {
    return onScrolledDownListener;
}

public void setOnScrolledUpListener(OnScrolledUpListener onScrolledUpListener){
    this.onScrolledUpListener = onScrolledUpListener;
}

public OnScrolledUpListener getOnScrolledUpListener() {
    return onScrolledUpListener;
}


/**
 * Event listener.
 */
public interface OnBottomReachedListener{
    public void onBottomReached(int scrollY);
}
public interface OnTopReachedListener{
    public void onTopReached(int scrollY);
}
public interface OnScrolledDownListener{
    public void onScrolledDown(int scrollY);
}
public interface OnScrolledUpListener{
    public void onScrolledUp(int scrollY);
}

}

@remyat

This comment has been minimized.

Show comment
Hide comment
@remyat

remyat Feb 24, 2017

I encountered a problem. ie, Sometimes OnBottomReached is firing multiple times. Anyone has the same problem ???? Please help me to solve this

remyat commented Feb 24, 2017

I encountered a problem. ie, Sometimes OnBottomReached is firing multiple times. Anyone has the same problem ???? Please help me to solve this

@mendhak

This comment has been minimized.

Show comment
Hide comment
@mendhak

mendhak Apr 17, 2017

@alcntml thanks for your snippet. I've posted a modified version of yours here, IMO the scroll up and down were switched. Also made the 'bottom' detection a tad bit forgiving 😁

mendhak commented Apr 17, 2017

@alcntml thanks for your snippet. I've posted a modified version of yours here, IMO the scroll up and down were switched. Also made the 'bottom' detection a tad bit forgiving 😁

@sbouiachref

This comment has been minimized.

Show comment
Hide comment
@sbouiachref

sbouiachref May 30, 2017

I encountered a problem. ie, Sometimes OnBottomReached is firing multiple times. Anyone has the same problem ???? Please help me to solve this

sbouiachref commented May 30, 2017

I encountered a problem. ie, Sometimes OnBottomReached is firing multiple times. Anyone has the same problem ???? Please help me to solve this

@sbouiachref

This comment has been minimized.

Show comment
Hide comment
@sbouiachref

sbouiachref May 30, 2017

I encountered a problem. ie, Sometimes OnBottomReached is firing multiple times. Anyone has the same problem ???? Please help me to solve this

sbouiachref commented May 30, 2017

I encountered a problem. ie, Sometimes OnBottomReached is firing multiple times. Anyone has the same problem ???? Please help me to solve this

@shuresnepali

This comment has been minimized.

Show comment
Hide comment
@shuresnepali

shuresnepali Jul 24, 2017

How do i use it/call it from main Activity...help me i am having trouble ???

shuresnepali commented Jul 24, 2017

How do i use it/call it from main Activity...help me i am having trouble ???

@Croydon

This comment has been minimized.

Show comment
Hide comment
@Croydon

Croydon Nov 16, 2017

@marteinn Under which license do you publish this?

Croydon commented Nov 16, 2017

@marteinn Under which license do you publish this?

@marteinn

This comment has been minimized.

Show comment
Hide comment
@marteinn

marteinn Jan 28, 2018

@Croydon MIT. Will clarify in the description.

Owner

marteinn commented Jan 28, 2018

@Croydon MIT. Will clarify in the description.

@sevar83

This comment has been minimized.

Show comment
Hide comment
@sevar83

sevar83 Feb 17, 2018

One-liner in Kotlin:

fun ViewGroup.isScrolledToBottom() = getChildAt(childCount - 1).bottom - height - scrollY == 0

sevar83 commented Feb 17, 2018

One-liner in Kotlin:

fun ViewGroup.isScrolledToBottom() = getChildAt(childCount - 1).bottom - height - scrollY == 0
@wahyuade

This comment has been minimized.

Show comment
Hide comment
@wahyuade

wahyuade Mar 1, 2018

good, it's work

wahyuade commented Mar 1, 2018

good, it's work

@jemshit

This comment has been minimized.

Show comment
Hide comment
@jemshit

jemshit Apr 25, 2018

topReach is fired multiple times

jemshit commented Apr 25, 2018

topReach is fired multiple times

@klaszlo8207

This comment has been minimized.

Show comment
Hide comment
@klaszlo8207

klaszlo8207 Jul 30, 2018

fixed version

public class MyScrollView extends ScrollView {

OnBottomReachedListener mListener;

private boolean bottomreached = false;

public MyStickyScrollView(Context context, AttributeSet attrs,
                          int defStyle) {
    super(context, attrs, defStyle);
}

public MyStickyScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public MyStickyScrollView(Context context) {
    super(context);
}

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    View view = getChildAt(getChildCount()-1);
    int diff = (view.getBottom()-(getHeight()+getScrollY()));

    if (diff == 0 && mListener != null && !bottomreached) {
        mListener.onBottomReached();
        bottomreached = true;
    }
    else {
        bottomreached = false;
    }

    super.onScrollChanged(l, t, oldl, oldt);
}

// Getters & Setters
public OnBottomReachedListener getOnBottomReachedListener() {
    return mListener;
}

public void setOnBottomReachedListener(
        OnBottomReachedListener onBottomReachedListener) {
    mListener = onBottomReachedListener;
}


/**
 * Event listener.
 */
public interface OnBottomReachedListener{
    void onBottomReached();
}

}
only fires 1 time

klaszlo8207 commented Jul 30, 2018

fixed version

public class MyScrollView extends ScrollView {

OnBottomReachedListener mListener;

private boolean bottomreached = false;

public MyStickyScrollView(Context context, AttributeSet attrs,
                          int defStyle) {
    super(context, attrs, defStyle);
}

public MyStickyScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public MyStickyScrollView(Context context) {
    super(context);
}

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    View view = getChildAt(getChildCount()-1);
    int diff = (view.getBottom()-(getHeight()+getScrollY()));

    if (diff == 0 && mListener != null && !bottomreached) {
        mListener.onBottomReached();
        bottomreached = true;
    }
    else {
        bottomreached = false;
    }

    super.onScrollChanged(l, t, oldl, oldt);
}

// Getters & Setters
public OnBottomReachedListener getOnBottomReachedListener() {
    return mListener;
}

public void setOnBottomReachedListener(
        OnBottomReachedListener onBottomReachedListener) {
    mListener = onBottomReachedListener;
}


/**
 * Event listener.
 */
public interface OnBottomReachedListener{
    void onBottomReached();
}

}
only fires 1 time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment