简体   繁体   English

"检测 ScrollView 的结束"

[英]Detect end of ScrollView

I have an app, that has an Activity that uses a ScrollView<\/code> .我有一个应用程序,它有一个使用ScrollView<\/code>的 Activity 。 I need to detect when user gets to the bottom of the ScrollView<\/code> .我需要检测用户何时到达ScrollView<\/code>的底部。 I did some googleing and I found this page<\/a> where is explained.我做了一些谷歌搜索,发现这个页面<\/a>有解释。 But, in the example, that guys extends ScrollView<\/code> .但是,在示例中,这些人扩展了ScrollView<\/code> 。 As I said, I need to extend Activity.正如我所说,我需要扩展 Activity。

So, I said "ok, let's try to make a custom class extending ScrollView<\/code> , override the onScrollChanged()<\/code> method, detect the end of the scroll, and act accordingly".所以,我说“好的,让我们尝试创建一个扩展ScrollView<\/code>的自定义类,重写onScrollChanged()<\/code>方法,检测滚动的结束,并采取相应的行动”。

I did, but in this line:我做到了,但在这一行:

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

Did it!做到了!

Aside of the fix Alexandre kindly provide me, I had to create an Interface:除了 Alexandre 为我提供的修复程序之外,我还必须创建一个接口:

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

Then, i had to override the OnScrollChanged method from ScrollView in my ScrollViewExt:然后,我必须在我的 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);
        }
    }
}

Now, as Alexandre said, put the package name in the XML tag (my fault), make my Activity class implement the interface created before, and then, put it all together:现在,正如Alexandre所说,把包名放在XML标签中(我的错),让我的Activity类实现之前创建的接口,然后,把它们放在一起:

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

And in the method OnScrollChanged, from the interface...在方法 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
    }
}

And it worked!它奏效了!

Thank you very much for your help, Alexandre!非常感谢您的帮助,亚历山大!

I found a simple way to detect this :我找到了一个简单的方法来检测这个:

   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
                }
            }
        });
  • Doesn't need to custom ScrollView.不需要自定义 ScrollView。
  • Scrollview can host only one direct child, so scrollView.getChildAt(0) is okay. Scrollview 只能承载一个直接子级,因此scrollView.getChildAt(0)没问题。
  • This solution is right even the height of direct child of scroll view is match_parent or wrap_content .即使滚动视图的直接子项的高度是match_parentwrap_content ,此解决方案也是正确的。

All of these answers are so complicated, but there is a simple built-in method that accomplishes this: canScrollVertically(int)所有这些答案都非常复杂,但有一个简单的内置方法可以实现这一点: canScrollVertically(int)

For example:例如:

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

This also works with RecyclerView, ListView, and actually any other view since the method is implemented on View .这也适用于 RecyclerView、ListView 和实际上任何其他视图,因为该方法是在View上实现的。

If you have a horizontal ScrollView, the same can be achieved with canScrollHorizontally(int)如果你有一个水平 ScrollView,同样可以用canScrollHorizontally(int)来实现

Fustigador answer was great, but I found some device (Like Samsung Galaxy Note V) cannot reach 0, have 2 point left, after the calculation. Fustigador 的回答很棒,但我发现有些设备(如三星 Galaxy Note V)无法达到 0,计算后还剩 2 点。 I suggest to add a little buffer like below:我建议添加一个小缓冲区,如下所示:

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


                    }
                }
            });

EDIT编辑

With the content of your XML, I can see that you use a ScrollView.通过 XML 的内容,我可以看到您使用了 ScrollView。 If you want to use your custom view, you must write com.your.packagename.ScrollViewExt and you will be able to use it in your code.如果你想使用你的自定义视图,你必须写 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>

EDIT END编辑结束

Could you post the xml content ?你能发布xml内容吗?

I think that you could simply add a scroll listener and check if the last item showed is the lastest one from the listview like :我认为您可以简单地添加一个滚动侦听器并检查显示的最后一项是否是列表视图中的最新一项,例如:

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

You can make use of the Support Library's NestedScrollView and it's NestedScrollView.OnScrollChangeListener interface.您可以使用支持库的NestedScrollViewNestedScrollView.OnScrollChangeListener接口。

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

Alternatively if your app is targeting API 23 or above, you can make use of the following method on the ScrollView :或者,如果您的应用面向 API 23 或更高版本,您可以在ScrollView上使用以下方法:

View.setOnScrollChangeListener(OnScrollChangeListener listener) 

Then follow the example that @Fustigador described in his answer.然后按照@Fustigador 在他的回答中描述的示例进行操作。 Note however that as @Will described, you should consider adding a small buffer in case the user or system isn't able to reach the complete bottom of the list for any reason.但是请注意,正如@Will 所述,您应该考虑添加一个小缓冲区,以防用户或系统因任何原因无法到达列表的完整底部。

Also worth noting is that the scroll change listener will sometimes be called with negative values or values greater than the view height.另外值得注意的是,滚动更改侦听器有时会使用负值或大于视图高度的值调用。 Presumably these values represent the 'momentum' of the scroll action.大概这些值代表滚动动作的“动量”。 However unless handled appropriately (floor / abs) they can cause problems detecting the scroll direction when the view is scrolled to the top or bottom of the range.但是,除非处理得当(地板/绝对),否则当视图滚动到范围的顶部或底部时,它们可能会导致检测滚动方向的问题。

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

We should always add scrollView.getPaddingBottom() to match full scrollview height because some time scroll view has padding in xml file so that case its not going to work.我们应该总是添加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
                }
            }
        }
    });

I went through the solutions on the internet.我浏览了互联网上的解决方案。 Mostly solutions didn't work in the project I'm working on.大多数解决方案在我正在从事的项目中不起作用。 Following solutions work fine for me.以下解决方案对我来说很好。

Using onScrollChangeListener (works on API 23):使用 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
                }
            }
        });
    }

using scrollChangeListener on TreeObserver在 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
            }
        }
    });

Hope this solution helps :)希望这个解决方案有帮助:)

To determine if you are at the end of your custom ScrollView you could also use a member variable and store the last y-position.要确定您是否在自定义ScrollView的末尾,您还可以使用成员变量并存储最后的 y 位置。 Then you can compare the last y-position with the current scroll position.然后您可以将最后一个 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;
}

Most of answers works beside a fact, that when u scroll to the bottom, listener is triggered several times , which in my case is undesirable .大多数答案都适用于一个事实,即当您滚动到底部时,会多次触发侦听器,这在我的情况下是不可取的 To avoid this behavior I've added flag scrollPositionChanged that checks if scroll position even changed before calling method once again.为了避免这种行为,我添加了标志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;
    }
}

Then just set listener然后只需设置侦听器

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

You may do the same thing if u do in like如果你喜欢,你可以做同样的事情

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

but you have to provide flag from class your adding this listener.但是您必须从添加此侦听器的类中提供标志。

I wanted to show/hide a FAB with an offset before the very bottom of the scrollview.我想在滚动视图的最底部之前显示/隐藏带有偏移量的 FAB。 This is the solution I came up with (Kotlin):这是我想出的解决方案(Kotlin):

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

The answer is very simple:答案很简单:

Just add a OnScrollChangedListener<\/strong> to your scrollView (RecyclerView or NestedScrollBar, etc) and implement the onScrollChanged<\/strong> method.只需将OnScrollChangedListener<\/strong>添加到您的滚动视图(RecyclerView 或 NestedScrollBar 等)并实现onScrollChanged<\/strong>方法。 For instance, if you use a NestedScrollView:例如,如果您使用 NestedScrollView:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM