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

ab1*_*b11 160 android scrollview visible

我有一个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
}
Run Code Online (Sandbox Code Playgroud)

Bil*_*ote 184

这有效:

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
}
Run Code Online (Sandbox Code Playgroud)

  • 这不包括重叠,如果子视图与另一个子元素重叠,它仍将返回true (3认同)

Ric*_*ler 65

使用View#getHitRect而不是View#getDrawingRect在您正在测试的视图上.您可以使用View#getDrawingRectScrollView,而不是明确地计算.

代码来自View#getDrawingRect:

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

代码来自View#getHitRect:

public void getHitRect(Rect outRect) {
        outRect.set(mLeft, mTop, mRight, mBottom);
}
Run Code Online (Sandbox Code Playgroud)

  • 我应该在哪里称这种方法? (30认同)
  • @Qberticus如何调用方法?我正在使用它,它总是返回false.请告诉我 (3认同)
  • 究竟在哪里调用这些方法? (2认同)

Den*_*nko 50

如果要检测视图是否完全可见:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这是正确的答案=)在我的情况下,我改变了if如下:scrollBounds.top <= top && scrollBounds.bottom => bottom (5认同)
  • 仅当视图直接添加到 ScrollView 容器的根目录时,此代码才有效。如果您想在子视图等中处理子视图,请检查 Phan Van Linh 的答案。 (3认同)
  • +1 Helton如果将视图按滚动视图的顶部或底部,则分别需要&lt;=或&gt; = (2认同)

Pha*_*inh 20

此扩展有助于检测完全可见的视图。
如果您View是 ... of 的孩子的孩子,它也可以工作ScrollView(例如:ScrollView-> LinearLayout-> ContraintLayout-> ... -> YourView)。

fun ScrollView.isViewVisible(view: View): Boolean {
    val scrollBounds = Rect()
    this.getDrawingRect(scrollBounds)
    var top = 0f
    var temp = view
    while (temp !is ScrollView){
        top += (temp).y
        temp = temp.parent as View
    }
    val bottom = top + view.height
    return scrollBounds.top < top && scrollBounds.bottom > bottom
}
Run Code Online (Sandbox Code Playgroud)

笔记

1)view.getY()并将view.getX()x,y 值返回给FIRST PARENT

2)这是关于如何getDrawingRect返回的 示例在此处输入图片说明 关联


Dan*_*alf 11

为了扩大使用getLocalVisibleRect比尔·莫特的回答一点,你可能要检查如果视图是仅部分可见:

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
}
Run Code Online (Sandbox Code Playgroud)

  • 这不起作用..甚至部分可见的视图被归类为完全可见 (4认同)

yan*_*nko 8

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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • @Romain Guy当视图完全滚动离开屏幕时,代码不会覆盖.它应该是`public static int getVisiblePercent(View v){if(v.isShown()){Rect r = new Rect(); boolean isVisible = v.getGlobalVisibleRect(r); if(isVisible){double sVisible = r.width()*r.height(); double sTotal = v.getWidth()*v.getHeight(); return(int)(100*sVisible/sTotal); } else {return -1; }} else {return -1; } (3认同)
  • 这与ab11要求的不同.isShown()仅检查可见性标志,而不检查视图是否在屏幕的可见区域. (2认同)

Web*_*eis 7

我的解决方案是使用NestedScrollViewScroll元素:

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

        }
    });
}
Run Code Online (Sandbox Code Playgroud)


har*_*ism 6

我今天遇到了同样的问题.谷歌搜索和阅读Android参考,我发现这篇文章和我最终使用的方法;

public final boolean getLocalVisibleRect (Rect r)
Run Code Online (Sandbox Code Playgroud)

很高兴他们不仅提供Rect而且还有boolean指示View是否可见.在消极方面,这种方法没有记录:(


Jor*_*sys 6

我想检测你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.
    }
}
Run Code Online (Sandbox Code Playgroud)

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

if (myView.getVisibility() == View.VISIBLE) {
    //VISIBLE
} else {
    //INVISIBLE
}
Run Code Online (Sandbox Code Playgroud)

视图中可见性的可能常量值为:

可见 这种看法是可见的.与setVisibility(int)和android:visibility一起使用.

INVISIBLE 这个视图是不可见的,但它仍然占用了布局空间.与setVisibility(int)和android:visibility一起使用.

GONE 此视图不可见,并且不需要任何空间用于布局.与setVisibility(int)和android:visibility一起使用.

  • *慢速拍手.*OP想要知道的是,假设视图的可见性是View#VISIBLE,如何知道视图本身是否在滚动视图中可见. (3认同)

Caf*_*han 6

科特林方式;

用于列出滚动视图的滚动并在子视图在屏幕上可见时获取操作的扩展。

@SuppressLint("ClickableViewAccessibility")
fun View.setChildViewOnScreenListener(view: View, action: () -> Unit) {
    val visibleScreen = Rect()

    this.setOnTouchListener { _, motionEvent ->
        if (motionEvent.action == MotionEvent.ACTION_MOVE) {
            this.getDrawingRect(visibleScreen)

            if (view.getLocalVisibleRect(visibleScreen)) {
                action()
            }
        }

        false
    }
}
Run Code Online (Sandbox Code Playgroud)

将此扩展功能用于任何可滚动视图

nestedScrollView.setChildViewOnScreenListener(childView) {
               action()
            }
Run Code Online (Sandbox Code Playgroud)


Vai*_*ani 5

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

    }
Run Code Online (Sandbox Code Playgroud)

这是课程:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)