检测ScrollView的结束

时间:2012-04-25 13:30:05

标签: android android-scrollview

我有一个应用,其活动使用ScrollView。我需要检测用户何时到达ScrollView的底部。我做了一些googleing,我发现this page在哪里解释。但是,在这个例子中,那些人扩展了ScrollView。正如我所说,我需要扩展活动。

所以,我说“好吧,我们试着让自定义类扩展ScrollView,覆盖onScrollChanged()方法,检测滚动结束,然后采取相应行动”。

我做了,但在这一行:

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

它会抛出java.lang.ClassCastException。我更改了XML中的<ScrollView>标签,但显然它不起作用。我的问题是:如果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>

我将其从TextView更改为WebView,以便能够证明其中的文字。我想要实现的是“在完全阅读合同条款之前,接受按钮不会激活”的事情。我的扩展类名为ScrollViewEx t。如果我为ScrollView更改了标记ScrollViewExt,则会抛出

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

因为它无法理解标记ScrollViewEx。我不认为它有解决方案......

感谢您的回答!

12 个答案:

答案 0 :(得分:104)

做到了!

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

它有效!

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

答案 1 :(得分:50)

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

   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_parent wrap_content ,此解决方案也是正确的。

答案 2 :(得分:25)

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

实现相同的目标

答案 3 :(得分:15)

福斯蒂加多的答案很棒,但是我发现有些设备(比如三星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
    }
}

答案 4 :(得分: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
        }
    }
});

答案 5 :(得分:4)

您可以使用支持资源库的NestedScrollView及其NestedScrollView.OnScrollChangeListener界面。

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

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

View.setOnScrollChangeListener(OnScrollChangeListener listener) 

然后按照@Fustigador在他的回答中描述的例子。但请注意,正如@Will所描述的那样,您应该考虑添加一个小缓冲区,以防用户或系统因任何原因无法到达列表的完整底部。

另外值得注意的是,滚动更改侦听器有时会被调用负值或大于视图高度的值。据推测,这些价值观代表着“动力”和“动力”。滚动动作。但是,除非适当处理(floor / abs),否则当视图滚动到范围的顶部或底部时,它们可能会导致检测滚动方向的问题。

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

答案 6 :(得分:3)

我们应该总是添加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
                }
            }
        }
    });

答案 7 :(得分:3)

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


                    }
                }
            });

答案 8 :(得分: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(...)

但你必须在课程中提供你添加这个监听器的标志。

答案 9 :(得分:1)

要确定您是否在自定义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;
}

答案 10 :(得分:1)

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

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

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

答案 11 :(得分:0)

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

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