如何最好地检查视图是否可见于窗口?

25

如何检查视图是否在窗口中可见?

我有一个CustomView,它是我的SDK的一部分,任何人都可以将CustomView添加到他们的布局中。我的CustomView会在定期内适时地对用户进行某些操作。因此,如果视图对用户不可见,则需要停止计时器,并在再次可见时重新开始其过程。

但是,不幸的是,没有确切的方法来检查我的CustomView对用户是否可见。有一些我可以检查和监听的事情:

onVisibilityChange //it is for view's visibility change, and is introduced in new API 8 version so has backward compatibility issue
onWindowVisibilityChange //but my CustomView can be part of a ViewFlipper's Views so it can pose issues
onDetachedFromWindows //this not as useful
onWindowFocusChanged //Again my CustomView can be part of ViewFlipper's views.
所以,如果有人遇到这种问题,请给予一些指引。
9个回答

11
在我的情况下,以下代码可以最好地监听视图是否可见:

在我的情况下,以下代码最适合监听视图是否可见:

@Override
protected void onWindowVisibilityChanged(int visibility) {
    super.onWindowVisibilityChanged(visibility);
    Log.e(TAG, "is view visible?: " + (visibility == View.VISIBLE));
}

非常适用于自定义视图。 - Khalid Lakhani

8

onDraw()会在每次视图需要被绘制时调用。当视图不在屏幕上时,onDraw()不会被调用。当视图的一小部分对用户可见时,则会调用onDraw()。虽然这不是理想的,但我无法找到其他可用于我想要做的事情的调用。记得调用super.onDraw,否则视图将无法绘制。在onDraw中更改任何导致视图无效的内容时要小心,因为这将导致另一个onDraw的调用。

如果您正在使用ListView,则可以在ListView变为对用户可见时使用getView

显然,当您的所有视图都被覆盖并且对用户不可见时,活动的onPause()会被调用。也许在父视图上调用invalidate(),如果onDraw()没有被调用,则该视图不可见。


2
我还必须执行这个操作,以便使其正常工作。 - John Leehey
不准确。onDraw() 可能会在视图不可见时被调用。例如,BottomSheet 中的 RecyclerView 将呈现其不可见的视图。 - l33t

4

这是我在我的应用程序中经常使用的一种方法,对我来说效果非常好:

static private int screenW = 0, screenH = 0;

@SuppressWarnings("deprecation") static public boolean onScreen(View view) {
    int coordinates[] = { -1, -1 };
    view.getLocationOnScreen(coordinates);

    // Check if view is outside left or top
    if (coordinates[0] + view.getWidth() < 0) return false;
    if (coordinates[1] + view.getHeight() < 0) return false;

    // Lazy get screen size. Only the first time.
    if (screenW == 0 || screenH == 0) {
        if (MyApplication.getSharedContext() == null) return false;
        Display display = ((WindowManager)MyApplication.getSharedContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
        try {
            Point screenSize = new Point();
            display.getSize(screenSize); // Only available on API 13+
            screenW = screenSize.x;
            screenH = screenSize.y;
        } catch (NoSuchMethodError e) { // The backup methods will only be used if the device is running pre-13, so it's fine that they were deprecated in API 13, thus the suppress warnings annotation at the start of the method.
            screenW = display.getWidth();
            screenH = display.getHeight();
        }
    }

    // Check if view is outside right and bottom
    if (coordinates[0] > screenW) return false;
    if (coordinates[1] > screenH) return false;

    // Else, view is (at least partially) in the screen bounds
    return true;
}

要使用它,只需传入任何视图或视图子类(即在Android上绘制的几乎任何内容)。如果它在屏幕上,它将返回true,否则返回false...我认为这相当直观。
如果您没有将上述方法用作静态方法,则可能可以通过其他方式获取上下文,但是要从静态方法获取应用程序上下文,需要执行以下两个步骤:
1- 在清单文件中的application标记中添加以下属性:
android:name="com.package.MyApplication"

2 - 添加一个继承Application类的类,如下所示:

public class MyApplication extends Application {
    // MyApplication exists solely to provide a context accessible from static methods.
    private static Context context;

    @Override public void onCreate() {
        super.onCreate();
        MyApplication.context = getApplicationContext();
    }

    public static Context getSharedContext() {
        return MyApplication.context;
    }
}

1
一个视图应该有一个相关的上下文(通过 getContext()),所以你不能使用Application / shared context,而应该直接使用它。这样做会省去很多代码。 - greg7gkb
自2012年以来,我就没有碰过Android了,但我记得我需要这段代码能够在启动过程的非常早期运行,此时并未分配所有上下文。我不记得为什么我需要那样做了。 - ArtOfWarfare
我认为这个方法只检查视图是否在屏幕范围内。如果要检测视图是否实际显示,您需要将其与isShown()结合使用。 - Binoy Babu

3
除了 view.getVisibility() 外,还有 view.isShown()。 isShown 检查视图树以确定所有祖先视图是否也可见。
尽管如此,这并不能处理被遮挡的视图,只能处理隐藏或消失在自身或其父元素中的视图。

这真的帮了我很多(view.isShown())。谢谢 +1。 - New Guy

2

在处理类似问题时,我需要知道视图是否有其他窗口位于其上方,因此我在自定义视图中使用了以下代码:

@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
   super.onWindowFocusChanged(hasWindowFocus);
   if (!hasWindowFocus) {

   } else {

   }
}

2

您可以使用getGlobalVisibleRect方法进行检查。如果此方法返回的矩形与View的大小完全相同,则当前View完全可见于屏幕上。

/**
 * Returns whether this View is completely visible on the screen
 *
 * @param view view to check
 * @return True if this view is completely visible on the screen, or false otherwise.
 */
public static boolean onScreen(@NonNull View view) {
    Rect visibleRect = new Rect();
    view.getGlobalVisibleRect(visibleRect);
    return visibleRect.height() == view.getHeight() && visibleRect.width() == view.getWidth();
}

如果您需要计算可见度百分比,可以使用平方计算方法:

float visiblePercentage = (visibleRect.height() * visibleRect.width()) / (float)(view.getHeight() * view.getWidth()) 

1
多种用例的完美解决方案。可以获得%的可见度,并用于任何用例。 - Shubham Pandey

1
这个解决方案考虑了受状态栏和工具栏遮挡的视图,也包括窗口外的视图(例如滚动到屏幕之外)
/**
 * Test, if given {@code view} is FULLY visible in window. Takes into accout window decorations
 * (statusbar and toolbar)
 *
 * @param view
 * @return true, only if the WHOLE view is visible in window
 */
public static boolean isViewFullyVisible(View view) {
    if (view == null || !view.isShown())
        return false;

    //windowRect - will hold available area where content remain visible to users
    //Takes into account screen decorations (e.g. statusbar)
    Rect windowRect = new Rect();
    view.getWindowVisibleDisplayFrame(windowRect);

    //if there is toolBar, get his height
    int actionBarHeight = 0;
    Context context = view.getContext();
    if (context instanceof AppCompatActivity && ((AppCompatActivity) context).getSupportActionBar() != null)
        actionBarHeight = ((AppCompatActivity) context).getSupportActionBar().getHeight();
    else if (context instanceof Activity && ((Activity) context).getActionBar() != null)
        actionBarHeight = ((Activity) context).getActionBar().getHeight();

    //windowAvailableRect - takes into account toolbar height and statusbar height
    Rect windowAvailableRect = new Rect(windowRect.left, windowRect.top + actionBarHeight, windowRect.right, windowRect.bottom);

    //viewRect - holds position of the view in window
    //(methods as getGlobalVisibleRect, getHitRect, getDrawingRect can return different result,
    // when partialy visible)
    Rect viewRect;
    final int[] viewsLocationInWindow = new int[2];
    view.getLocationInWindow(viewsLocationInWindow);
    int viewLeft = viewsLocationInWindow[0];
    int viewTop = viewsLocationInWindow[1];
    int viewRight = viewLeft + view.getWidth();
    int viewBottom = viewTop + view.getHeight();
    viewRect = new Rect(viewLeft, viewTop, viewRight, viewBottom);

    //return true, only if the WHOLE view is visible in window
    return windowAvailableRect.contains(viewRect);
}

0
在您的自定义视图中,设置监听器:
 getViewTreeObserver().addOnScrollChangedListener(this);
 getViewTreeObserver().addOnGlobalLayoutListener(this);

我正在使用这段代码来在视图对用户可见时执行动画。
需要考虑两种情况。
  1. 你的视图不在屏幕上。但是如果用户滚动它,它将可见。

    public void onScrollChanged() {
        final int i[] = new int[2];
        this.getLocationOnScreen(i);
        if (i[1] <= mScreenHeight - 50) {
            this.post(new Runnable() {
                @Override
                public void run() {
                    Log.d("ITEM", "animate");
                    //animate once
                    showValues();
                }
            });
            getViewTreeObserver().removeOnScrollChangedListener(this);
            getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
    }
    
  2. 你的视图最初在屏幕上。(不是在滚动视图中用户看不到的其他地方,而是最初在屏幕上并对用户可见)

    public void onGlobalLayout() {
     final int i[] = new int[2];
     this.getLocationOnScreen(i);
     if (i[1] <= mScreenHeight) {
        this.post(new Runnable() {
            @Override
            public void run() {
                Log.d("ITEM", "animate");
                //animate once
                showValues();
            }
        });
        getViewTreeObserver().removeOnGlobalLayoutListener(this);
        getViewTreeObserver().removeOnScrollChangedListener(this);
     }
    }
    

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接