View 的可見性檢查還可以這樣~

背景&問題

在Android開發中,我們常常會對View的可視性visiblity進行操作或者檢查。如網絡請求數據,根據返回的數據結果控制相應View可見或不可見,或者判斷某個View是否在屏幕中可見,不可見時給予用戶相應提示信息等。在ListView、RecyclerView、ScrollView里我們可能會比較經常做這些事。比如在下面的ScrollView中:



四種方法獲取的結果如下:

View5.getVisibility() = View.VISIBLE;
View5.isShown() = true; 
View5.getGlobalVisibleRect() = false;
View5.getLocalVisibleRect() =  false;

為什么有這樣的結果呢?四種方法的具體的區別是什么?getGlobalVisibleRect和getLocalVisibleRect具體怎么用呢?先說下幾種方法的具體區別。

基本方法

1.View.getVisibility()

這是常用的也是最基本的檢查View可見性的方法,這個方法的返回值有View.VISIBLE(可見)、View.INVISIBLE(不可見但占著原來的空間)和View.GONE( 不可見且不占原來的空間)。如果這個方法返回的是View.INVISIBLE或者View.GONE,那么這個View肯定是對用戶不可見的。

2.View.isShown()

這個方法和View.getVisibility()作用類似,重要的區別就是:

  • getVisibility()返回的是int值,isShown()返回的是boolean值
  • View.isShown()會對View的所有父類調用getVisibility方法
/**
 * Returns the visibility of this view and all of its ancestors
 *
 * @return True if this view and all of its ancestors are {@link #VISIBLE}
 */
public boolean isShown() {
    View current = this;
    //noinspection ConstantConditions
    do {
        if ((current.mViewFlags & VISIBILITY_MASK) != VISIBLE) {
            return false;
        }
        ViewParent parent = current.mParent;
        if (parent == null) {
            return false; // We are not attached to the view root
        }
        if (!(parent instanceof View)) {
            return true;
        }
        current = (View) parent;
    } while (current != null);

    return false;
}

由源碼中注釋可以知道,這個方法遞歸地去檢查這個View以及它的parentView的Visibility屬性是不是等于View.VISIBLE,這樣就對這個View的所有parentView做了一個檢查。另外這個方法還在遞歸的檢查過程中,檢查了parentView == null,也就是說所有的parentView都不能為null。否則就說明這個View根本沒有被addView過(比如創建界面UI時,可能會先new一個View,然后根據條件動態地把它add帶一個ViewGroup中),那肯定是不可能對用戶可見的。

3.View.getGlobalVisibleRect()

顧名思義,這個方法會返回一個View是否可見的boolean值,同時還會將該View的可見區域left,top,right,bottom值保存在一個rect對象中,具體使用方法如下:

Rect globalRect = new Rect();
boolean visibile = view5.getGlobalVisibleRect(globalRect);

getGlobalVisibleRect(Rect r)最后調用的是getGlobalVisibleRect(Rect r, Point globalOffset)方法,看下該方法的注釋:

/**
 * If some part of this view is not clipped by any of its parents, then
 * return that area in r in global (root) coordinates. To convert r to local
 * coordinates (without taking possible View rotations into account), offset
 * it by -globalOffset (e.g. r.offset(-globalOffset.x, -globalOffset.y)).
 * If the view is completely clipped or translated out, return false.
 *
 * @param r If true is returned, r holds the global coordinates of the
 *        visible portion of this view.
 * @param globalOffset If true is returned, globalOffset holds the dx,dy
 *        between this view and its root. globalOffet may be null.
 * @return true if r is non-empty (i.e. part of the view is visible at the
 *         root level.
 */

由以上注釋可以知道,當這個View只要有一部分仍然在屏幕中(沒有被父View遮擋,即not clipped by any of its parents),那么將把沒有被遮擋的那部分區域保存在rect對象中返回,且返回visibility為true。此時的rect是以手機屏幕作為坐標系(即global coordinates),也就是原點是屏幕左上角;如果它全部被父View遮擋住了或者本身就是不可見的,返回的visibility就為false,rect中的值為0。

4.View.getLocalVisibleRect()

這個方法和getGlobalVisibleRect有些類似,也可以拿到這個View在屏幕的可見區域的坐標,唯一的區別getLocalVisibleRect(rect)獲得的rect坐標系的原點是View自己的左上角,而不是屏幕左上角。其也會調用getGlobalVisibleRect()方法:

public final boolean getLocalVisibleRect(Rect r) {
    final Point offset = mAttachInfo != null ? mAttachInfo.mPoint : new Point();
    if (getGlobalVisibleRect(r, offset)) {
        r.offset(-offset.x, -offset.y); // make r local
        return true;
    }
    return false;
}

由以上源碼可以看到,getLocalVisibleRect()會先獲取View的offset point(相對屏幕或者ParentView的偏移坐標),然后再去調用getGlobalVisibleRect(Rect r, Point globalOffset)方法來獲取可見區域,最后再把得到的GlobalVisibleRect和Offset坐標做一個加減法,轉換坐標系原點。使用方法如下:

Rect localRect = new Rect();
boolean visibile = view5.getLocalVisibleRect(localRect);
* 5.getGlobalVisibleRect() VS getLocalVisibleRect()*

回到最開始的問題,四種方法獲取view5的visibility結果應該很好理解了,那getGlobalVisibleRect()和getLocalVisibleRect()中獲取出的rect值具體區別在哪兒?如下圖,假設屏幕大小為1080x1920,以ScrollView為Parent View,在ScrollView的onScrollChanged()中對view1,view3和view5的可見性進行判斷:



代碼比較簡單,直接就看debug結果吧,如下:



由以上結果可以看出getGlobalVisibleRect()和getLocalVisibleRect()對View的可見性visibility判斷結果相同,只是獲取出的rect值有所區別:
  • 當View在屏幕中全部可見時(圖中view3),根據上面的介紹知,getLocalVisibleRect()的原點是自己的左上角,所以當View的左上角在屏幕中時,獲取的rect左上角坐標一定為(0,0),右下角為(View.getWidth, View.getHeight),而getGlobalVisibleRect()的原點是屏幕左上角,獲取出的rect值是與getLocalVisibleRect()左上角不為(0,0);
  • 當View在屏幕中部分可見時(圖中view1),getLocalVisibleRect()獲取的rect值左上角不為(0,0),但此時也與getGlobalVisibleRect()獲取值不同;
  • View在屏幕中全部不可見時(圖中view5),兩者的visibility都為false,且兩者獲取的rect值相同。這是為什么呢?由源碼可以知道,getLocalVisibleRect()最終調用的是getGlobalVisibleRect()方法,并會減去View自身的便偏移坐標offset point,但只有當View可見時才會減去這個偏移坐標,要是不可見就直接返回了,所以此時兩者獲取出的rect值是相同的。
6.注意&tips

(1)使用getGlobalVisibleRect() getLocalVisibleRect()判斷View的可見性時,一定要等View繪制完成后,再去調用這兩個方法,否則無法得到對的結果,返回值的rect值都是0,visibility為false。這和獲取View的寬高原理是一樣的,如果View沒有被繪制完成,那么View.getWidth和View.getHeight一定是等于0的。例如,測試時發現,僅僅在代碼中findViewById()把View初始化出來,而對View沒有其他操作,并不能保證View繪制完成,就像以下代碼:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    visibleButton = (Button) findViewById(R.id.visible_test);
    boolean localVisibility = visibleButton.getLocalVisibleRect(rectLocal);    //localVisibility始終為false,rectLocal值為0
    boolean globalVisibility = visibleButton.getGlobalVisibleRect(rectGlobal);  //globalVisibility始終為false,rectGlobal值為0          
}

(2)關于getGlobalVisibleRect()方法的特別說明,這個方法只能檢查出這個View在手機屏幕(或者說是相對它的父View)的位置,而不能檢查出與其他兄弟View的相對位置:
比如有一個ViewGroup,下面有View1、View2這兩個子View,View1和View2是平級關系。此時如果View2蓋住了View1,那么用getGlobalVisibleRect方法檢查View1的可見性,得到的返回值依然是true,得到的可見矩形區域rect也是沒有任何變化的。也就是說View1.getGlobalVisibleRect(rect)得到的結果與View2沒有任何關系。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容