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
  1. 12047     public void getLocationInWindow(int[] location) {  
  2. 12048         if (location == null || location.length < 2) {  
  3. 12049             throw new IllegalArgumentException("location must be an array of "  
  4. 12050                     + "two integers");  
  5. 12051         }  
  6. 12052   
  7. 12053         location[0] = mLeft;  
  8. 12054         location[1] = mTop;  
  9. 12055         if (mTransformationInfo != null) {  
  10. 12056             location[0] += (int) (mTransformationInfo.mTranslationX + 0.5f);  
  11. 12057             location[1] += (int) (mTransformationInfo.mTranslationY + 0.5f);  
  12. 12058         }  
  13. 12059   
  14. 12060         ViewParent viewParent = mParent;  
  15. 12061         while (viewParent instanceof View) {  
  16. 12062             final View view = (View)viewParent;  
  17. 12063             location[0] += view.mLeft - view.mScrollX;  
  18. 12064             location[1] += view.mTop - view.mScrollY;  
  19. 12065             if (view.mTransformationInfo != null) {  
  20. 12066                 location[0] += (int) (view.mTransformationInfo.mTranslationX + 0.5f);  
  21. 12067                 location[1] += (int) (view.mTransformationInfo.mTranslationY + 0.5f);  
  22. 12068             }  
  23. 12069             viewParent = view.mParent;  
  24. 12070         }  
  25. 12071   
  26. 12072         if (viewParent instanceof ViewRootImpl) {  
  27. 12073             // *cough*  
  28. 12074             final ViewRootImpl vr = (ViewRootImpl)viewParent;  
  29. 12075             location[1] -= vr.mCurScrollY;  
  30. 12076         }  
  31. 12077     }  
mTransformationInfo は変形(Rotate, Scale, Translation など)の情報を保持するためのフィールドです。translationX や translationY がセットされていればその分もカウントするということです。

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



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

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

  1. v.setTranslationX(100);  
  2. final int[] anchorPos = new int[2];  
  3. v.getLocationOnScreen(anchorPos);  
  4.   
  5. Log.d(TAG, "position : " + v.getLeft() + ", " + v.getTop());  
  6. 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
  1. 12030     public void getLocationOnScreen(int[] location) {  
  2. 12031         getLocationInWindow(location);  
  3. 12032   
  4. 12033         final AttachInfo info = mAttachInfo;  
  5. 12034         if (info != null) {  
  6. 12035             location[0] += info.mWindowLeft;  
  7. 12036             location[1] += info.mWindowTop;  
  8. 12037         }  
  9. 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
  1. 5825     public void getWindowVisibleDisplayFrame(Rect outRect) {  
  2. 5826         if (mAttachInfo != null) {  
  3. 5827             try {  
  4. 5828                 mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect);  
  5. 5829             } catch (RemoteException e) {  
  6. 5830                 return;  
  7. 5831             }  
  8. 5832             // XXX This is really broken, and probably all needs to be done  
  9. 5833             // in the window manager, and we need to know more about whether  
  10. 5834             // we want the area behind or in front of the IME.  
  11. 5835             final Rect insets = mAttachInfo.mVisibleInsets;  
  12. 5836             outRect.left += insets.left;  
  13. 5837             outRect.top += insets.top;  
  14. 5838             outRect.right -= insets.right;  
  15. 5839             outRect.bottom -= insets.bottom;  
  16. 5840             return;  
  17. 5841         }  
  18. 5842         Display d = WindowManagerImpl.getDefault().getDefaultDisplay();  
  19. 5843         d.getRectSize(outRect);  
  20. 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 とは異なるからです。

  1. Toast toast = Toast.makeText(MainActivity.this"Hello World", Toast.LENGTH_SHORT);  
  2. toast.setGravity(Gravity.TOP, 00);  
  3. toast.show();  
  4. 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 件のコメント:

コメントを投稿