检测ScrollView的结尾


76

我有一个应用程序,该应用程序的活动使用ScrollView。我需要检测用户何时到达底部ScrollView。我做了一些谷歌搜索,我找到了解释的页面。但是,在这个例子中,这些家伙扩展了ScrollView。如我所说,我需要扩展Activity。

因此,我说“好吧,让我们尝试使自定义类扩展ScrollView,覆盖onScrollChanged()方法,检测滚动的结尾并采取相应的措施”。

我做了,但是在这一行:

scroll = (ScrollViewExt) findViewById(R.id.scrollView1);

它抛出一个 java.lang.ClassCastException。我更改了<ScrollView>XML中的标签,但是显然不起作用。我的问题是:为什么,如果ScrollViewExt延伸ScrollView,会丢给我一个ClassCastException?有什么方法可以检测滚动结束而不会造成太多混乱?

谢谢大家

编辑:如所承诺的,这是我重要的XML片段:

<ScrollView
        android:id="@+id/scrollView1"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >


        <WebView
            android:id="@+id/textterms"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_horizontal"
            android:textColor="@android:color/black" />

    </ScrollView>

我将其从更改TextViewWebView,以便能够证明其中的文本。我要实现的是“接受按钮直到合同的条款全部阅读后才激活”的东西。我的延伸班级叫ScrollViewExt。如果我更改标签ScrollViewScrollViewExt它会抛出一个

android.view.InflateException: Binary XML file line #44: Error inflating class ScrollViewExt

因为它不了解标签ScrollViewEx。我认为这没有解决方案...

感谢您的回答!


无需自定义类或复杂的扩展。只需使用canScrollVertically(int)
yuval,

Answers:


108

做到了!

除了Alexandre乐意提供的修复程序之外,我还必须创建一个Interface:

public interface ScrollViewListener {
    void onScrollChanged(ScrollViewExt scrollView, 
                         int x, int y, int oldx, int oldy);
}

然后,我必须在ScrollViewExt中从ScrollView重写OnScrollChanged方法:

public class ScrollViewExt extends ScrollView {
    private ScrollViewListener scrollViewListener = null;
    public ScrollViewExt(Context context) {
        super(context);
    }

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

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

    public void setScrollViewListener(ScrollViewListener scrollViewListener) {
        this.scrollViewListener = scrollViewListener;
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (scrollViewListener != null) {
            scrollViewListener.onScrollChanged(this, l, t, oldl, oldt);
        }
    }
}

现在,正如Alexandre所说,将包名称放入XML标记中(这是我的错),让我的Activity类实现之前创建的接口,然后将它们全部组合在一起:

scroll = (ScrollViewExt) findViewById(R.id.scrollView1);
scroll.setScrollViewListener(this);

然后在方法OnScrollChanged中,从界面...

@Override
public void onScrollChanged(ScrollViewExt scrollView, int x, int y, int oldx, int oldy) {
    // We take the last son in the scrollview
    View view = (View) scrollView.getChildAt(scrollView.getChildCount() - 1);
    int diff = (view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY()));

    // if diff is zero, then the bottom has been reached
    if (diff == 0) {
        // do stuff
    }
}

而且有效!

非常感谢您的帮助,亚历山大!


我试图在我的应用程序中使用此答案,问题是-scrollView.getHeight()始终等于view.getBottom(),而scrollView.getScrollY()始终为0。您知道为什么会这样吗?
Marko Cakic 2012年

谢谢,这很出色,因为它允许在ScrollView的顶部和底部创建箭头,并根据滚动视图的位置使它们显示或消失,以使用户意识到还有更多项目可以查看...
Lumis

@MarkoCakic查看此答案以获取滚动视图内容的高度。stackoverflow.com/questions/3609297/...
Uselessinfo

由于scrollView只有一个孩子view = scrollView.getChildAt(0),并scrollView.getScrollY()==y 在你的功能,可以在功能上面用这个,而不是访问冗余数据进行修改。
laaptu

我有一个问题,我正在动态更新我的滚动视图,所以我不希望在scrollChanged上进行此操作,我想检测用户是否向下滚动到底部(打开自动滚动)是否向上滚动(关闭自动滚动)。请帮忙。
AL̲̳I

81

我发现了一种简单的检测方法:

   scrollView.getViewTreeObserver()
       .addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
            @Override
            public void onScrollChanged() {
                if (scrollView.getChildAt(0).getBottom()
                     <= (scrollView.getHeight() + scrollView.getScrollY())) {
                    //scroll view is at bottom
                } else {
                    //scroll view is not at bottom
                }
            }
        });
  • 不需要自定义ScrollView。
  • Scrollview只能托管一个直接子代,因此scrollView.getChildAt(0)可以。
  • 即使滚动视图的直接子级的高度为match_parentwrap_content,此解决方案也是正确的。

非常感谢你!作品!
DmitryKanunnikoff

不错的解决方案!谢谢。
Lee Hounshell

8
为避免多次调用底部,将“ <=”替换为“ ==“
Harshil Pansare

进行一些小的调整即可完美。:)
John T

1
谢谢,我们不必扩展ScrollView即可完成此常规操作。
安德鲁·科斯特

67

所有这些答案都非常复杂,但是有一个简单的内置方法可以完成此任务: canScrollVertically(int)

例如:

@Override
public void onScrollChanged() {
    if (!scrollView.canScrollVertically(1)) {
        // bottom of scroll view
    }
    if (!scrollView.canScrollVertically(-1)) {
        // top of scroll view
    }
}

这也适用于RecyclerView,ListView和其他任何视图,因为该方法是在 View

如果您有水平的ScrollView,则可以使用 canScrollHorizontally(int)


8
完美的答案!经过测试!
LionisIAm

4
真的有帮助。谢谢!
尼克'18

4
它必须是一个可接受的答案,它很简单并且可以完美地工作

3
为了完整起见,这是!scrollView.canScrollHorizontally(1)检查它是否已经在右端,!scrollView.canScrollHorizontally(-1)是否在左边。
keithmaxx

1
完美的解决方案!
Dimitri Schultheis,

17

Fustigador的回答很好,但是在计算之后,我发现某些设备(如Samsung Galaxy Note V)无法达到0,还剩2点。我建议添加一些如下所示的缓冲区:

@Override
public void onScrollChanged(ScrollViewExt scrollView, int x, int y, int oldx, int oldy) {
    // We take the last son in the scrollview
    View view = (View) scrollView.getChildAt(scrollView.getChildCount() - 1);
    int diff = (view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY()));

    // if diff is zero, then the bottom has been reached
    if (diff <= 10) {
        // do stuff
    }
}

11
scrollView = (ScrollView) findViewById(R.id.scrollView);

    scrollView.getViewTreeObserver()
            .addOnScrollChangedListener(new 
            ViewTreeObserver.OnScrollChangedListener() {
                @Override
                public void onScrollChanged() {

                    if (!scrollView.canScrollVertically(1)) {
                        // bottom of scroll view
                    }
                    if (!scrollView.canScrollVertically(-1)) {
                        // top of scroll view


                    }
                }
            });

10

编辑

通过XML的内容,我可以看到您使用了ScrollView。如果要使用自定义视图,则必须编写com.your.packagename.ScrollViewExt,然后才能在代码中使用它。

<com.your.packagename.ScrollViewExt
    android:id="@+id/scrollView1"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <WebView
        android:id="@+id/textterms"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:textColor="@android:color/black" />

</com.your.packagename.ScrollViewExt>

编辑结束

您可以发布xml内容吗?

我认为您可以简单地添加一个滚动侦听器,并检查显示的最后一项是否是listview中最新的一项:

mListView.setOnScrollListener(new OnScrollListener() {      
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        // TODO Auto-generated method stub      
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
        // TODO Auto-generated method stub
        if(view.getLastVisiblePosition()==(totalItemCount-1)){
            //dosomething
        }
    }
});

我明天会做,我现在不在工作。但我看到您建议使用ListView。我没有使用ListView,只是在ScrollView中使用了TextView。
Fustigador

哦,很抱歉,我想到了ListView。您可以在自定义视图中发布由setContentViewl调用的xm吗?
Alexandre B.

4

您可以使用支持库NestedScrollView及其NestedScrollView.OnScrollChangeListener接口。

https://developer.android.com/reference/android/support/v4/widget/NestedScrollView.html

另外,如果您的应用定位到API 23或更高版本,则可以在上使用以下方法ScrollView

View.setOnScrollChangeListener(OnScrollChangeListener listener) 

然后按照@Fustigador在其答案中描述的示例进行操作。但是请注意,正如@Will所述,您应该考虑添加一个小的缓冲区,以防用户或系统由于某种原因而无法到达列表的底部。

还值得注意的是,有时会使用负值或大于视图高度的值来调用滚动更改侦听器。大概这些值代表滚动动作的“动量”。但是,除非适当处理(地板/绝对),否则当视图滚动到范围的顶部或底部时,它们可能会导致无法检测滚动方向。

https://developer.android.com/reference/android/view/View.html#setOnScrollChangeListener(android.view.View.OnScrollChangeListener)


4

我们应该始终添加scrollView.getPaddingBottom()以匹配完整的scrollview高度,因为有些时候滚动视图在xml文件中有填充,因此这种情况将无法正常工作。

scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            if (scrollView != null) {
               View view = scrollView.getChildAt(scrollView.getChildCount()-1);
               int diff = (view.getBottom()+scrollView.getPaddingBottom()-(scrollView.getHeight()+scrollView.getScrollY()));

          // if diff is zero, then the bottom has been reached
               if (diff == 0) {
               // do stuff
                }
            }
        }
    });

2

要确定您是否处于自定义结尾,您ScrollView还可以使用成员变量并存储最后的y位置。然后,您可以将最后一个y位置与当前滚动位置进行比较。

private int scrollViewPos;

...

@Override
public void onScrollChanged(ScrollViewExt scrollView, int x, int y, int oldx, int oldy) {

   //reached end of scrollview
   if (y > 0 && scrollViewPos == y){
      //do something
   }

   scrollViewPos = y;
}

2

大多数答案都基于以下事实:当您滚动到底部时,监听器会被触发多次,在我看来,这是不希望的。为了避免这种行为,我添加了标志scrollPositionChanged ,它在再次调用方法之前检查滚动位置是否发生了变化。

public class EndDetectingScrollView extends ScrollView {
    private boolean scrollPositionChanged = true;

    private ScrollEndingListener scrollEndingListener;

    public interface ScrollEndingListener {
        void onScrolledToEnd();
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);

        View view = this.getChildAt(this.getChildCount() - 1);
        int diff = (view.getBottom() - (this.getHeight() + this.getScrollY()));
        if (diff <= 0) {
            if (scrollPositionChanged) {
                scrollPositionChanged = false;
                if (scrollEndingListener != null) {
                    scrollEndingListener.onScrolledToEnd();
                }
            }
        } else {
            scrollPositionChanged = true;
        }
    }

    public void setScrollEndingListener(ScrollEndingListener scrollEndingListener) {
        this.scrollEndingListener = scrollEndingListener;
    }
}

然后只需设置监听器

scrollView.setScrollEndingListener(new EndDetectingScrollView.ScrollEndingListener() {
    @Override
    public void onScrolledToEnd() {
        //do your stuff here    
    }
});

如果你喜欢,你可能会做同样的事情

scrollView.getViewTreeObserver().addOnScrollChangedListener(...)

但是您必须从类中提供添加此侦听器的标志。


真好!适用于API之前的23级!
Abhiroop Nandi Ray

0

我想在滚动视图的最底部之前显示/隐藏具有偏移量的FAB。这是我想出的解决方案(科特琳):

scrollview.viewTreeObserver.addOnScrollChangedListener {
    if (scrollview.scrollY < scrollview.getChildAt(0).bottom - scrollview.height - offset) {
        // fab.hide()
    } else {
        // fab.show()
    }
}

0

我浏览了互联网上的解决方案。通常,解决方案在我正在进行的项目中不起作用。以下解决方案对我来说很好。

使用onScrollChangeListener(适用于API 23):

   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
            @Override
            public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {

                int bottom =   (scrollView.getChildAt(scrollView.getChildCount() - 1)).getHeight()-scrollView.getHeight()-scrollY;

                if(scrollY==0){
                    //top detected
                }
                if(bottom==0){
                    //bottom detected
                }
            }
        });
    }

在TreeObserver上使用scrollChangeListener

 scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            int bottom =   (scrollView.getChildAt(scrollView.getChildCount() - 1)).getHeight()-scrollView.getHeight()-scrollView.getScrollY();

            if(scrollView.getScrollY()==0){
                //top detected
            }
            if(bottom==0) {
                //bottom detected
            }
        }
    });

希望此解决方案有帮助:)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.