检测 ScrollView 的结束

Posted

技术标签:

【中文标题】检测 ScrollView 的结束【英文标题】:Detect end of ScrollView 【发布时间】:2012-05-06 04:49:57 【问题描述】:

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

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

我做了,但在这一行:

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

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

谢谢大家。

编辑: 正如所承诺的,这是我的 XML 中重要的部分:

<ScrollView
        android:id="@+id/scrollView1"
        android:layout_
        android:layout_ >


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

    </ScrollView>

我将它从 TextView 更改为 WebView 以便能够证明里面的文本。我想要实现的是“在完全阅读合同条款之前,接受按钮不会激活”的事情。我的扩展类称为ScrollViewExt。如果我将标签 ScrollView 更改为 ScrollViewExt 它会抛出一个

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

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

感谢您的回答!

【问题讨论】:

不需要自定义类或复杂的扩展。只需使用canScrollVertically(int) 【参考方案1】:

做到了!

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

它成功了!

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

【讨论】:

我正在尝试在我的应用程序中使用这个答案,问题是 -scrollView.getHeight() 始终等于 view.getBottom(),而 scrollView.getScrollY() 始终为 0。你知道为什么会这样吗? 谢谢,这太棒了,因为它允许在 ScrollView 的顶部和底部创建箭头,并根据滚动视图的位置使它们出现或消失,以使用户意识到还有更多的项目需要见... @MarkoCakic 查看此答案以获取滚动视图内容的高度。 ***.com/questions/3609297/… 由于scrollView在你的函数中只有一个孩子view = scrollView.getChildAt(0)scrollView.getScrollY()==y ,可以使用上面的方法修改函数,而不是访问冗余数据。 我有一个问题,我正在动态更新我的滚动视图,所以我不想在 scrollChanged 上出现这个,我想检测用户是否向上滚动(关闭自动滚动)如果用户向下滚动到底部(打开自动滚动)..请任何人帮忙。【参考方案2】:

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

   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) 没问题。 即使滚动视图的直接子级高度为 ma​​tch_parentwrap_content,此解决方案也是正确的。

【讨论】:

为了避免多次调用底部,将“ 谢谢,我们不应该扩展 ScrollView 来做这种常见的事情。 当孩子使用边距时,这不起作用。解决方法是scrollview(framelayout(childWithMargin))) 绝对应该是公认的答案【参考方案3】:

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

【讨论】:

为了完整起见,!scrollView.canScrollHorizontally(1) 检查它是否已经在右端,!scrollView.canScrollHorizontally(-1) 如果它在左端。【参考方案4】:

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
    

【讨论】:

【参考方案5】:
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


                    
                
            );

【讨论】:

【参考方案6】:

编辑

通过您的 XML 内容,我可以看到您使用的是 ScrollView。如果您想使用自定义视图,您必须编写 com.your.packagename.ScrollViewExt 并且您将能够在您的代码中使用它。

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

    <WebView
        android:id="@+id/textterms"
        android:layout_
        android:layout_
        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
        
    
);

【讨论】:

我明天会做,我现在不在工作。但我看到你建议使用 ListView。我没有使用 ListView,只是 ScrollView 中的 TextView。 抱歉,我想到了 ListView。您能否将 setContentViewl 调用的 xm 与您的自定义视图一起发布?【参考方案7】:

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

【讨论】:

【参考方案8】:

我们应该始终添加 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
                
            
        
    );

【讨论】:

【参考方案9】:

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

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

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

【讨论】:

【参考方案10】:

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

【讨论】:

【参考方案11】:

大多数答案都有效除了一个事实,当你滚动到底部时,监听器会被触发多次,在我的情况下这是不受欢迎的。为了避免这种行为,我添加了标志 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 之前的版本!【参考方案12】:

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

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

【讨论】:

【参考方案13】:

答案很简单:

只需将 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
    

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

【讨论】:

以上是关于检测 ScrollView 的结束的主要内容,如果未能解决你的问题,请参考以下文章

如何检测scrollView的滚动方向?

Android:ScrollView 与 NestedScrollView

渐进式滚动子scrollView

如何在 ScrollView 中居中 UIImageView(缩放)

是否可以使用 ScrollView 和 Horizo​​ntalScrollView 实现对角滚动?

除非我添加另一个 UIView,否则 ScrollView 中的 TableView 为空?