88

我有一个应用程序,它有一个使用ScrollView. 我需要检测用户何时到达ScrollView. 我做了一些谷歌搜索,发现这个页面有解释。但是,在示例中,那些家伙扩展了ScrollView. 正如我所说,我需要扩展 Activity。

所以,我说“好吧,让我们尝试做一个自定义类扩展ScrollView,重写onScrollChanged()方法,检测滚动的结束,并采取相应的行动”。

我做到了,但在这一行:

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

它抛出一个 java.lang.ClassCastException. 我更改了<ScrollView>XML 中的标签,但显然它不起作用。我的问题是:如果ScrollViewExtextends 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。我不认为它有解决方案...

感谢您的回答!

4

13 回答 13

114

做到了!

除了 Alexandre 为我提供的修复之外,我还必须创建一个接口:

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
    }
}

它奏效了!

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

于 2012-04-26T13:23:06.200 回答
96

我找到了一种简单的检测方法:

   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 ,此解决方案也是正确的。
于 2016-12-14T10:00:50.550 回答
79

所有这些答案都非常复杂,但是有一个简单的内置方法可以做到这一点: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)

于 2018-02-08T19:58:30.090 回答
20

Fustigador 的回答很好,但我发现某些设备(如三星 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
    }
}
于 2015-09-17T04:08:20.140 回答
14
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


                    }
                }
            });
于 2018-10-12T08:25:35.073 回答
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内容吗?

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

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
        }
    }
});
于 2012-04-25T13:41:03.010 回答
5

您可以使用支持库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)

于 2016-10-07T02:35:05.003 回答
4

我们应该始终添加scrollView.getPaddingBottom()以匹配完整的滚动视图高度,因为有时滚动视图在 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
                }
            }
        }
    });
于 2016-12-20T17:33:30.733 回答
4

我浏览了互联网上的解决方案。大多数解决方案在我正在从事的项目中不起作用。以下解决方案对我来说很好。

使用 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
            }
        }
    });

希望这个解决方案有帮助:)

于 2020-07-07T04:53:33.920 回答
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;
}
于 2014-07-03T13:58:55.240 回答
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(...)

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

于 2017-09-22T12:24:57.683 回答
0

我想在滚动视图的最底部显示/隐藏带有偏移量的 FAB。这是我提出的解决方案(Kotlin):

scrollview.viewTreeObserver.addOnScrollChangedListener {
    if (scrollview.scrollY < scrollview.getChildAt(0).bottom - scrollview.height - offset) {
        // fab.hide()
    } else {
        // fab.show()
    }
}
于 2019-07-03T13:50:52.403 回答
0

答案很简单:

只需将OnScrollChangedListener添加到您的滚动视图(RecyclerView 或 NestedScrollBar 等)并实现onScrollChanged方法。例如,如果您使用 NestedScrollView:

val nestedScrollView = view.findViewById<NestedScrollView>(R.id.nested_scroll_view_id)
nestedScrollView.viewTreeObserver?.addOnScrollChangedListener {
    if (!nestedScrollView.canScrollVertically(1)) {
        button.isEnabled = true
    }
}

如果您对此有任何问题,请立即告诉我。

于 2021-09-08T12:07:46.057 回答