背景&問題
在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沒有任何關系。