4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

View#getGlobalVisibleRect()は戻り値を確認しなさいという話

Last updated at Posted at 2020-05-18

一発ネタです

Viewの表示されている位置が欲しい場合、View#getGlobalVisibleRect(rect) を利用する場合があると思います。このrectが正しい値を示すのは、戻り値がtrueの時だけですよというお話。
表示範囲外にある場合は、戻り値がfalseになり、rectには計算途中の値が入った状態で戻るため、この値を元に何かを判断してはいけません。画面外にあるのに画面内にあるような値が入ってくることがあります。
逆に言えば、画面内に表示されているか否かは戻り値を見るだけで判定できます。

言いたいことは以上なのですが、以下解説

実験

以下のようなレイアウトを作ります。何でもいいですが、Viewが画面に表示されない位置に移動できるようにしておきます。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scroll_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >

        <View
            android:id="@+id/view"
            android:layout_width="200dp"
            android:layout_height="50dp"
            android:layout_gravity="center_horizontal|bottom"
            android:layout_marginTop="1000dp"
            android:background="#ff0000"
            />
    </FrameLayout>
</ScrollView>

では、このid/viewの位置を取ってみましょう。
以下のように画面サイズの高さと併せて見てみます。

scroll_view.setOnScrollChangeListener { _, _, _, _, _ ->
    val size = Point()
    windowManager.defaultDisplay.getSize(size)
    val rect = Rect()
    view.getGlobalVisibleRect(rect)
    Log.e("XXXX", "getGlobalVisibleRect: ${rect.top} ${size.y}")
}

これをゆっくりスクロールさせると

E/XXXX: getGlobalVisibleRect: 1588 1794
E/XXXX: getGlobalVisibleRect: 1587 1794
E/XXXX: getGlobalVisibleRect: 1584 1794
E/XXXX: getGlobalVisibleRect: 1791 1794 ←
E/XXXX: getGlobalVisibleRect: 1788 1794
E/XXXX: getGlobalVisibleRect: 1787 1794

このように、画面の高さよりもrect.topの値が小さくなってもViewは見えません、そして見えるようになると急に値が変わって、画面高さよりも少し小さく、頭が見えた位置らしい値になっていそうです。

getGlobalVisibleRectの戻り値も併せて表示するとこうなります。

E/XXXX: getGlobalVisibleRect: false 1590 1794
E/XXXX: getGlobalVisibleRect: false 1587 1794
E/XXXX: getGlobalVisibleRect: false 1585 1794
E/XXXX: getGlobalVisibleRect: true 1793 1794 ←
E/XXXX: getGlobalVisibleRect: true 1791 1794
E/XXXX: getGlobalVisibleRect: true 1788 1794

画面上に表示されていない状態ではfalse、表示されるようになるとtrueになってrectの値をも確からしい値になります。

なぜか

コードを追ってみます。まずはゼロ座標に左上がある前提でrectに格納し、親のgetChildVisibleRectをコールしています。

View.java
public final boolean getGlobalVisibleRect(Rect r) {
    return getGlobalVisibleRect(r, null);
}

public boolean getGlobalVisibleRect(Rect r, Point globalOffset) {
    int width = mRight - mLeft;
    int height = mBottom - mTop;
    if (width > 0 && height > 0) {
        r.set(0, 0, width, height);
        if (globalOffset != null) {
            globalOffset.set(-mScrollX, -mScrollY);
        }
        return mParent == null || mParent.getChildVisibleRect(this, r, globalOffset);
    }
    return false;
}

ではgetChildVisibleRectは何をやっているかというと

ViewGroup.java
public boolean getChildVisibleRect(
        View child, Rect r, android.graphics.Point offset, boolean forceParentCheck) {
    final RectF rect = mAttachInfo != null ? mAttachInfo.mTmpTransformRect : new RectF();
    rect.set(r);

// 略

if ((forceParentCheck || rectIsVisible) && mParent != null) {
        if (mParent instanceof ViewGroup) {
            rectIsVisible = ((ViewGroup) mParent)
                    .getChildVisibleRect(this, r, offset, forceParentCheck);
        } else {
            rectIsVisible = mParent.getChildVisibleRect(this, r, offset);
        }
    }
    return rectIsVisible;
}

長いので省略しますが、要するに子Viewの相対的な位置から自分のViewの上のオフセットなどを計算し、「見えている場合」さらに親のgetChildVisibleRectをコールしています。最終的にRootのViewまで到達するとViewの全体から見た位置が分かると言うことですね。

逆に、どこかの親Viewからはみ出ていたりする場合、つまり「見えていない」と分かった段階で、親へ親への伝達が止まります。そのためオフセット計算が途中の信頼できない値が入っている状態になります。

Returns
boolean true if r is non-empty (i.e. part of the view is visible at the root level.

ドキュメントはちゃんと読みなさいということでした。

4
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?