Android:如何检查ScrollView中的View是否可见?

我有一个ScrollView ,它包含一系列Views 。 我想能够确定一个视图当前是否可见(如果它的任何部分当前由ScrollView显示)。 我期望下面的代码来做到这一点,令人惊讶的是它不:

 Rect bounds = new Rect(); view.getDrawingRect(bounds); Rect scrollBounds = new Rect(scroll.getScrollX(), scroll.getScrollY(), scroll.getScrollX() + scroll.getWidth(), scroll.getScrollY() + scroll.getHeight()); if(Rect.intersects(scrollBounds, bounds)) { //is visible } 

在您正在testing的视图上使用View#getHitRect而不是View#getDrawingRect 。 您可以在ScrollView上使用View#getDrawingRect而不是显式计算。

来自View#getDrawingRect代码View#getDrawingRect

  public void getDrawingRect(Rect outRect) { outRect.left = mScrollX; outRect.top = mScrollY; outRect.right = mScrollX + (mRight - mLeft); outRect.bottom = mScrollY + (mBottom - mTop); } 

来自View#getHitRect代码:

 public void getHitRect(Rect outRect) { outRect.set(mLeft, mTop, mRight, mBottom); } 

这工作:

 Rect scrollBounds = new Rect(); scrollView.getHitRect(scrollBounds); if (imageView.getLocalVisibleRect(scrollBounds)) { // Any portion of the imageView, even a single pixel, is within the visible window } else { // NONE of the imageView is within the visible window } 

如果你想检测到视图是完全可见的:

 private boolean isViewVisible(View view) { Rect scrollBounds = new Rect(); mScrollView.getDrawingRect(scrollBounds); float top = view.getY(); float bottom = top + view.getHeight(); if (scrollBounds.top < top && scrollBounds.bottom > bottom) { return true; } else { return false; } } 

要使用getLocalVisibleRect扩展Bill Mote的答案,您可能需要检查视图是否仅部分可见:

 Rect scrollBounds = new Rect(); scrollView.getHitRect(scrollBounds); if (!imageView.getLocalVisibleRect(scrollBounds) || scrollBounds.height() < imageView.getHeight()) { // imageView is not within or only partially within the visible window } else { // imageView is completely visible } 

我今天面对同样的问题。 当谷歌和阅读Android的参考,我发现这个职位和我最终使用的方法,而不是;

 public final boolean getLocalVisibleRect (Rect r) 

他们不只是提供Rect,而且布尔值指示View是否可见。 在消极的一面这种方法是无证:(

 public static int getVisiblePercent(View v) { if (v.isShown()) { Rect r = new Rect(); v.getGlobalVisibleRect(r); double sVisible = r.width() * r.height(); double sTotal = v.getWidth() * v.getHeight(); return (int) (100 * sVisible / sTotal); } else { return -1; } } 

我的解决scheme是使用NestedScrollView滚动元素:

  final Rect scrollBounds = new Rect(); scroller.getHitRect(scrollBounds); scroller.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() { @Override public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { if (myBtn1 != null) { if (myBtn1.getLocalVisibleRect(scrollBounds)) { if (!myBtn1.getLocalVisibleRect(scrollBounds) || scrollBounds.height() < myBtn1.getHeight()) { Log.i(TAG, "BTN APPEAR PARCIALY"); } else { Log.i(TAG, "BTN APPEAR FULLY!!!"); } } else { Log.i(TAG, "No"); } } } }); } 

我想要检测您的View是否完全visible ,请尝试以下方法:

 private boolean isViewVisible(View view) { Rect scrollBounds = new Rect(); mScrollView.getDrawingRect(scrollBounds); float top = view.getY(); float bottom = top + view.getHeight(); if (scrollBounds.top < top && scrollBounds.bottom > bottom) { return true; //View is visible. } else { return false; //View is NOT visible. } } 

严格来说,您可以通过以下方式获得视图的可见性:

 if (myView.getVisibility() == View.VISIBLE) { //VISIBLE } else { //INVISIBLE } 

View中可见常量的值是:

可见这个视图是可见的。 使用setVisibility(int)和android:visibility。

INVISIBLE这个视图是隐形的,但它仍然占用空间来布局。 使用setVisibility(int)和android:visibility。

GONE这个视图是不可见的,它不占用任何空间用于布局。 使用setVisibility(int)和android:visibility。

您可以使用FocusAwareScrollView在视图变为可见时通知它:

 FocusAwareScrollView focusAwareScrollView = (FocusAwareScrollView) findViewById(R.id.focusAwareScrollView); if (focusAwareScrollView != null) { ArrayList<View> viewList = new ArrayList<>(); viewList.add(yourView1); viewList.add(yourView2); focusAwareScrollView.registerViewSeenCallBack(viewList, new FocusAwareScrollView.OnViewSeenListener() { @Override public void onViewSeen(View v, int percentageScrolled) { if (v == yourView1) { // user have seen view1 } else if (v == yourView2) { // user have seen view2 } } }); } 

这是class级:

 import android.content.Context; import android.graphics.Rect; import android.support.v4.widget.NestedScrollView; import android.util.AttributeSet; import android.view.View; import java.util.ArrayList; import java.util.List; public class FocusAwareScrollView extends NestedScrollView { private List<OnScrollViewListener> onScrollViewListeners = new ArrayList<>(); public FocusAwareScrollView(Context context) { super(context); } public FocusAwareScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public FocusAwareScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public interface OnScrollViewListener { void onScrollChanged(FocusAwareScrollView v, int l, int t, int oldl, int oldt); } public interface OnViewSeenListener { void onViewSeen(View v, int percentageScrolled); } public void addOnScrollListener(OnScrollViewListener l) { onScrollViewListeners.add(l); } public void removeOnScrollListener(OnScrollViewListener l) { onScrollViewListeners.remove(l); } protected void onScrollChanged(int l, int t, int oldl, int oldt) { for (int i = onScrollViewListeners.size() - 1; i >= 0; i--) { onScrollViewListeners.get(i).onScrollChanged(this, l, t, oldl, oldt); } super.onScrollChanged(l, t, oldl, oldt); } @Override public void requestChildFocus(View child, View focused) { super.requestChildFocus(child, focused); } private boolean handleViewSeenEvent(View view, int scrollBoundsBottom, int scrollYOffset, float minSeenPercentage, OnViewSeenListener onViewSeenListener) { int loc[] = new int[2]; view.getLocationOnScreen(loc); int viewBottomPos = loc[1] - scrollYOffset + (int) (minSeenPercentage / 100 * view.getMeasuredHeight()); if (viewBottomPos <= scrollBoundsBottom) { int scrollViewHeight = this.getChildAt(0).getHeight(); int viewPosition = this.getScrollY() + view.getScrollY() + view.getHeight(); int percentageSeen = (int) ((double) viewPosition / scrollViewHeight * 100); onViewSeenListener.onViewSeen(view, percentageSeen); return true; } return false; } public void registerViewSeenCallBack(final ArrayList<View> views, final OnViewSeenListener onViewSeenListener) { final boolean[] viewSeen = new boolean[views.size()]; FocusAwareScrollView.this.postDelayed(new Runnable() { @Override public void run() { final Rect scrollBounds = new Rect(); FocusAwareScrollView.this.getHitRect(scrollBounds); final int loc[] = new int[2]; FocusAwareScrollView.this.getLocationOnScreen(loc); FocusAwareScrollView.this.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() { boolean allViewsSeen = true; @Override public void onScrollChange(NestedScrollView v, int x, int y, int oldx, int oldy) { for (int index = 0; index < views.size(); index++) { //Change this to adjust criteria float viewSeenPercent = 1; if (!viewSeen[index]) viewSeen[index] = handleViewSeenEvent(views.get(index), scrollBounds.bottom, loc[1], viewSeenPercent, onViewSeenListener); if (!viewSeen[index]) allViewsSeen = false; } //Remove this if you want continuous callbacks if (allViewsSeen) FocusAwareScrollView.this.setOnScrollChangeListener((NestedScrollView.OnScrollChangeListener) null); } }); } }, 500); } } 

我知道它很晚。 但我有一个很好的解决scheme。 以下是用于在滚动视图中获取视图可见性百分比的代码片段。

首先在滚动视图上设置触摸监听器来获取滚动停止的callback。

 @Override public boolean onTouch(View v, MotionEvent event) { switch ( event.getAction( ) ) { case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: new Handler().postDelayed(new Runnable() { @Override public void run() { if(mScrollView == null){ mScrollView = (ScrollView) findViewById(R.id.mScrollView); } int childCount = scrollViewRootChild.getChildCount(); //Scroll view location on screen int[] scrollViewLocation = {0,0}; mScrollView.getLocationOnScreen(scrollViewLocation); //Scroll view height int scrollViewHeight = mScrollView.getHeight(); for (int i = 0; i < childCount; i++){ View child = scrollViewRootChild.getChildAt(i); if(child != null && child.getVisibility() == View.VISIBLE){ int[] viewLocation = new int[2]; child.getLocationOnScreen(viewLocation); int viewHeight = child.getHeight(); getViewVisibilityOnScrollStopped(scrollViewLocation, scrollViewHeight, viewLocation, viewHeight, (String) child.getTag(), (childCount - (i+1))); } } } }, 150); break; } return false; } 

在上面的代码片段中,我们获取了滚动视图触摸事件的callback函数,并在滚动停止的callback之后发布150毫秒(不是必需的)之后的可运行。 在那个runnable中,我们将获得滚动视图在屏幕上的位置并滚动视图高度。 然后获取滚动视图的直接子视图组实例并获取子计数。 在我的情况下,滚动视图的直接子对象是名为scrollViewRootChild的 LinearLayout。 然后迭代scrollViewRootChild的所有子视图。 在上面的代码片段中,您可以看到我在屏幕上的一个名为viewLocation的整数数组中获取子级的位置,在variables名称viewHeight中获取视图的高度。 然后我调用了一个私有方法getViewVisibilityOnScrollStopped 。 您可以通过阅读文档来了解该方法的内部工作。

 /** * getViewVisibilityOnScrollStopped * @param scrollViewLocation location of scroll view on screen * @param scrollViewHeight height of scroll view * @param viewLocation location of view on screen, you can use the method of view claas's getLocationOnScreen method. * @param viewHeight height of view * @param tag tag on view * @param childPending number of views pending for iteration. */ void getViewVisibilityOnScrollStopped(int[] scrollViewLocation, int scrollViewHeight, int[] viewLocation, int viewHeight, String tag, int childPending) { float visiblePercent = 0f; int viewBottom = viewHeight + viewLocation[1]; //Get the bottom of view. if(viewLocation[1] >= scrollViewLocation[1]) { //if view's top is inside the scroll view. visiblePercent = 100; int scrollBottom = scrollViewHeight + scrollViewLocation[1]; //Get the bottom of scroll view if (viewBottom >= scrollBottom) { //If view's bottom is outside from scroll view int visiblePart = scrollBottom - viewLocation[1]; //Find the visible part of view by subtracting view's top from scrollview's bottom visiblePercent = (float) visiblePart / viewHeight * 100; } }else{ //if view's top is outside the scroll view. if(viewBottom > scrollViewLocation[1]){ //if view's bottom is outside the scroll view int visiblePart = viewBottom - scrollViewLocation[1]; //Find the visible part of view by subtracting scroll view's top from view's bottom visiblePercent = (float) visiblePart / viewHeight * 100; } } if(visiblePercent > 0f){ visibleWidgets.add(tag); //List of visible view. } if(childPending == 0){ //Do after iterating all children. } } 

如果你觉得这个代码有任何改进,请贡献一下。

使用@Qberticus答案是重要的,但很好顺便说一句,我编写了一堆代码来检查是否每当一个滚动视图被称为和滚动触发@Qberticus答案,你可以做任何你想要的,在我的情况下,我有一个包含video的社交networking,所以当在屏幕上绘制视图时,我会播放像Facebook和Instagram这样的video。 代码如下:

 mainscrollview.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() { @Override public void onScrollChanged() { //mainscrollview is my scrollview that have inside it a linearlayout containing many child views. Rect bounds = new Rect(); for(int xx=1;xx<=postslayoutindex;xx++) { //postslayoutindex is the index of how many posts are read. //postslayoutchild is the main layout for the posts. if(postslayoutchild[xx]!=null){ postslayoutchild[xx].getHitRect(bounds); Rect scrollBounds = new Rect(); mainscrollview.getDrawingRect(scrollBounds); if(Rect.intersects(scrollBounds, bounds)) { vidPreview[xx].startPlaywithoutstoppping(); //I made my own custom video player using textureview and initialized it globally in the class as an array so I can access it from anywhere. } else { } } } } });