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

背景&問題

在Android開發(fā)中,我們常常會對View的可視性visiblity進行操作或者檢查。如網(wǎng)絡請求數(shù)據(jù)某宪,根據(jù)返回的數(shù)據(jù)結果控制相應View可見或不可見,或者判斷某個View是否在屏幕中可見锐朴,不可見時給予用戶相應提示信息等兴喂。在ListView、RecyclerView焚志、ScrollView里我們可能會比較經(jīng)常做這些事衣迷。比如在下面的ScrollView中:



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

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

為什么有這樣的結果呢?四種方法的具體的區(qū)別是什么酱酬?getGlobalVisibleRect和getLocalVisibleRect具體怎么用呢壶谒?先說下幾種方法的具體區(qū)別。

基本方法

1.View.getVisibility()

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

2.View.isShown()

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

  • getVisibility()返回的是int值痛阻,isShown()返回的是boolean值
  • View.isShown()會對View的所有父類調(diào)用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過(比如創(chuàng)建界面UI時动猬,可能會先new一個View,然后根據(jù)條件動態(tài)地把它add帶一個ViewGroup中)表箭,那肯定是不可能對用戶可見的赁咙。

3.View.getGlobalVisibleRect()

顧名思義,這個方法會返回一個View是否可見的boolean值免钻,同時還會將該View的可見區(qū)域left彼水,top,right极舔,bottom值保存在一個rect對象中凤覆,具體使用方法如下:

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

getGlobalVisibleRect(Rect r)最后調(diào)用的是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)慈俯,那么將把沒有被遮擋的那部分區(qū)域保存在rect對象中返回,且返回visibility為true拥峦。此時的rect是以手機屏幕作為坐標系(即global coordinates)贴膘,也就是原點是屏幕左上角;如果它全部被父View遮擋住了或者本身就是不可見的略号,返回的visibility就為false刑峡,rect中的值為0。

4.View.getLocalVisibleRect()

這個方法和getGlobalVisibleRect有些類似玄柠,也可以拿到這個View在屏幕的可見區(qū)域的坐標突梦,唯一的區(qū)別getLocalVisibleRect(rect)獲得的rect坐標系的原點是View自己的左上角,而不是屏幕左上角羽利。其也會調(diào)用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的偏移坐標),然后再去調(diào)用getGlobalVisibleRect(Rect r, Point globalOffset)方法來獲取可見區(qū)域这弧,最后再把得到的GlobalVisibleRect和Offset坐標做一個加減法撮奏,轉(zhuǎn)換坐標系原點。使用方法如下:

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

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



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



由以上結果可以看出getGlobalVisibleRect()和getLocalVisibleRect()對View的可見性visibility判斷結果相同挂洛,只是獲取出的rect值有所區(qū)別:
  • 當View在屏幕中全部可見時(圖中view3),根據(jù)上面的介紹知眠砾,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()最終調(diào)用的是getGlobalVisibleRect()方法扮休,并會減去View自身的便偏移坐標offset point,但只有當View可見時才會減去這個偏移坐標贱案,要是不可見就直接返回了肛炮,所以此時兩者獲取出的rect值是相同的。
6.注意&tips

(1)使用getGlobalVisibleRect() getLocalVisibleRect()判斷View的可見性時宝踪,一定要等View繪制完成后侨糟,再去調(diào)用這兩個方法,否則無法得到對的結果瘩燥,返回值的rect值都是0秕重,visibility為false。這和獲取View的寬高原理是一樣的厉膀,如果View沒有被繪制完成溶耘,那么View.getWidth和View.getHeight一定是等于0的。例如服鹅,測試時發(fā)現(xiàn)凳兵,僅僅在代碼中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旨椒,得到的可見矩形區(qū)域rect也是沒有任何變化的胜嗓。也就是說View1.getGlobalVisibleRect(rect)得到的結果與View2沒有任何關系。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末钩乍,一起剝皮案震驚了整個濱河市辞州,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寥粹,老刑警劉巖变过,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件埃元,死亡現(xiàn)場離奇詭異,居然都是意外死亡媚狰,警方通過查閱死者的電腦和手機岛杀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來崭孤,“玉大人类嗤,你說我怎么就攤上這事”娉瑁” “怎么了遗锣?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嗤形。 經(jīng)常有香客問我精偿,道長,這世上最難降的妖魔是什么赋兵? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任笔咽,我火速辦了婚禮,結果婚禮上霹期,老公的妹妹穿的比我還像新娘叶组。我一直安慰自己,他們只是感情好历造,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布扶叉。 她就那樣靜靜地躺著,像睡著了一般帕膜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上溢十,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天垮刹,我揣著相機與錄音,去河邊找鬼张弛。 笑死荒典,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的吞鸭。 我是一名探鬼主播寺董,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼刻剥!你這毒婦竟也來了遮咖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤造虏,失蹤者是張志新(化名)和其女友劉穎御吞,沒想到半個月后麦箍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡陶珠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年挟裂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片揍诽。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡诀蓉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出暑脆,到底是詐尸還是另有隱情渠啤,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布饵筑,位于F島的核電站埃篓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏根资。R本人自食惡果不足惜架专,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望玄帕。 院中可真熱鬧部脚,春花似錦、人聲如沸裤纹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鹰椒。三九已至锡移,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間漆际,已是汗流浹背淆珊。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奸汇,地道東北人施符。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像擂找,于是被迫代替她去往敵國和親戳吝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容