2012年10月23日火曜日

Android View の位置を取得する

View クラスのメソッドで位置やサイズを取得するメソッドがいくつかあるので紹介します。

getLocationInWindow(int[] location)

ウィンドウ上でのこの View の位置を計算します。引数は長さが2以上の int 配列で、Index 0 に x 座標、Index 1 に y 座標の値が入ります。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/view/View.java#12047
12047 public void getLocationInWindow(int[] location) { 12048 if (location == null || location.length < 2) { 12049 throw new IllegalArgumentException("location must be an array of " 12050 + "two integers"); 12051 } 12052 12053 location[0] = mLeft; 12054 location[1] = mTop; 12055 if (mTransformationInfo != null) { 12056 location[0] += (int) (mTransformationInfo.mTranslationX + 0.5f); 12057 location[1] += (int) (mTransformationInfo.mTranslationY + 0.5f); 12058 } 12059 12060 ViewParent viewParent = mParent; 12061 while (viewParent instanceof View) { 12062 final View view = (View)viewParent; 12063 location[0] += view.mLeft - view.mScrollX; 12064 location[1] += view.mTop - view.mScrollY; 12065 if (view.mTransformationInfo != null) { 12066 location[0] += (int) (view.mTransformationInfo.mTranslationX + 0.5f); 12067 location[1] += (int) (view.mTransformationInfo.mTranslationY + 0.5f); 12068 } 12069 viewParent = view.mParent; 12070 } 12071 12072 if (viewParent instanceof ViewRootImpl) { 12073 // *cough* 12074 final ViewRootImpl vr = (ViewRootImpl)viewParent; 12075 location[1] -= vr.mCurScrollY; 12076 } 12077 } mTransformationInfo は変形(Rotate, Scale, Translation など)の情報を保持するためのフィールドです。translationX や translationY がセットされていればその分もカウントするということです。

例えば、次のような画面のボタンに対して呼び出すと



final int[] anchorPos = new int[2]; v.getLocationOnScreen(anchorPos); Log.d(TAG, "position : " + v.getLeft() + ", " + v.getTop()); Log.d(TAG, "window location : " + anchorPos[0] + ", " + anchorPos[1] ); position : 0, 0
window location : 0, 146

となりますが、translationX をセットすると

v.setTranslationX(100); final int[] anchorPos = new int[2]; v.getLocationOnScreen(anchorPos); Log.d(TAG, "position : " + v.getLeft() + ", " + v.getTop()); Log.d(TAG, "window location : " + anchorPos[0] + ", " + anchorPos[1] ); position : 0, 0
window location : 100, 146

となります。translationX は getLeft() には影響しません。

mParent をたどって、View の親 ViewGroup の左上の位置とスクロール位置と Translation を考慮するので、例えば次のように ScrollView の中にいれたとすると



最初の状態では
window location : 0, 746

ですが、スクロールするとその分だけ y の位置が変わります。
window location : 0, 488






getLocationOnScreen(int[] location)

スクリーン上でのこの View の位置を計算します。引数は長さが2以上の int 配列で、Index 0 に x 座標、Index 1 に y 座標の値が入ります。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/view/View.java#12030
12030 public void getLocationOnScreen(int[] location) { 12031 getLocationInWindow(location); 12032 12033 final AttachInfo info = mAttachInfo; 12034 if (info != null) { 12035 location[0] += info.mWindowLeft; 12036 location[1] += info.mWindowTop; 12037 } 12038 } mAttachInfo が null なら getLoactionOnScreen() の戻り値は getLocationInWindow() と同じになるということです。
getLocationInWindow() と getLocationOnScreen() が異なる例として、ダイアログがあります。
例えばこの Activity のテーマを Theme.Holo.Light.Dialog にしてみます。



そうすると、次のように値が異なります。

window location : 16, 148
screen location : 56, 635

getLocationInWindow() はダイアログからの相対位置、getLocationOnScreen() は画面上の位置になります。
もちろん Dialog クラスを使ったダイアログ上の View でも同じです。




getWindowVisibleDisplayFrame(Rect outRect)

このメソッドは上の2つとはちょっと違って、このビューが配置されているウィンドウのサイズを取得します。
実質的にはこのコンテンツ(View)が配置できかつユーザーから見える領域になります。
引数で渡した Rect の top, bottom, left, right に値が格納されます。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/view/View.java#5825
5825 public void getWindowVisibleDisplayFrame(Rect outRect) { 5826 if (mAttachInfo != null) { 5827 try { 5828 mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect); 5829 } catch (RemoteException e) { 5830 return; 5831 } 5832 // XXX This is really broken, and probably all needs to be done 5833 // in the window manager, and we need to know more about whether 5834 // we want the area behind or in front of the IME. 5835 final Rect insets = mAttachInfo.mVisibleInsets; 5836 outRect.left += insets.left; 5837 outRect.top += insets.top; 5838 outRect.right -= insets.right; 5839 outRect.bottom -= insets.bottom; 5840 return; 5841 } 5842 Display d = WindowManagerImpl.getDefault().getDefaultDisplay(); 5843 d.getRectSize(outRect); 5844 } 例えば、次のような画面のボタンに対して呼び出すと



dispalyFrame : 0, 50, 720, 1184
(left, top, right, bottom)

となります。ステータスバーやナビゲーションバーは入らない(View を配置できない)ということです。

一方、次のように Toast にセットしたビューに対して取得すると

dispalyFrame : 0, 0, 720, 1184
(left, top, right, bottom)

となり、ステータスバーの領域も含まれます。
これは Toast を表示する Window のレイヤーが Activity の setContentView() で表示される View とは異なるからです。

Toast toast = Toast.makeText(MainActivity.this, "Hello World", Toast.LENGTH_SHORT); toast.setGravity(Gravity.TOP, 0, 0); toast.show(); toast.getView().getWindowVisibleDisplayFrame(displayFrame);


また、IME 部分は含まれません。そのため、IME を表示した状態と表示していない状態では値がかわります。

表示していない状態
dispalyFrame : 0, 50, 720, 1184
(left, top, right, bottom)



表示している状態
dispalyFrame : 0, 50, 720, 604
(left, top, right, bottom)



ちなみに DisplayMetrics の値は
widthPixels : 720
heightPixels : 1184
でステータスバーは入りますが、ナビゲーションバーは入りません(ナビゲーションバーの高さは 48dip * 2 = 96px)。


getGlobalVisibleRect(Rect r)

この View の可視領域を global (root) の座標で返します。 getLocationInWindow() と同じように親の ViewGroup の位置、スクロール、Translation が考慮されます。


getDrawingRect(Rect outRect)

この View の可視描画領域を返します。 親の ViewGroup の位置、スクロール、Translation は考慮されません。




0 件のコメント:

コメントを投稿