Android View各种尺寸位置相关的方法探究
本来想做一个View间的碰撞检测之类的。
动手做了才发现不是想象的那么简单。
首先,写好了碰撞检测的工具类如下:
package com.mengdd.utils; import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; import android.view.View; public class Collision2DUtils { public static boolean isPointInRect(int x, int y, Rect rect) { boolean isIn = false; isIn = rect.contains(x, y); return isIn; } public static boolean isPointInRectF(float x, float y, RectF rectF) { boolean isIn = false; isIn = rectF.contains(x, y); return isIn; } public static boolean isPointInView(float x, float y, View view) { if (null == view) { throw new IllegalArgumentException("view is null!"); } boolean isIn = false; int top = view.getTop(); int left = view.getLeft(); int right = view.getRight(); int bottom = view.getBottom(); int width = view.getWidth(); int height = view.getHeight(); if (x >= left && x <= right && y >= top && y <= bottom) { isIn = true; } Log.i("View", ", x: " + x + ", left: " + left + ", right: " + right + ", y: " + y + ", top: " + top + ", bottom: " + bottom + ", " + width + ", height: " + height + ", result: " + isIn); return isIn; } }
三个方法,分别用于判断点是否在一个矩形中(整形,浮点型),还有判断一个点是否在一个View显示的范围中。
然后测试了一下前两个方法,因为矩形对象都是自己直接用数字构建的,所以没有问题。
测试判断点是否在View中的方法
思路是:在布局中写两个TextView,在Activity中重写onTouchEvent方法,判断点击的点是否在View中。
@Override public boolean onTouchEvent(MotionEvent event) { if (MotionEvent.ACTION_DOWN == event.getAction()) { float x = event.getX(); float y = event.getY(); detectCollision(x, y); } return super.onTouchEvent(event); }
其中判断的方法:
结果显示在另一个TextView里
private void detectCollision(float x, float y) { boolean result1 = Collision2DUtils.isPointInView(x, y, block1); boolean result2 = Collision2DUtils.isPointInView(x, y, block2); mResult1.setText("result1: " + result1 + ", result2: " + result2); }
一试就发现问题了:判断结果并不对。
判断错误的原因:
onTouchEvent()回调中获取的坐标值,是点击位置,是在绝对坐标中的。
碰撞检测方法中View的getTop()、getBottom()、getLeft()、getRight()获取的都是当前View相对于它的父类容器的顶部、底部、左边和右边的位置。
因为一个是绝对位置一个是相对位置,所以无法比较。
当然也有可以比较的情况,就是这个View的父类充满了整个屏幕,然后窗口的设置是无标题并且全屏的。
在onCreate()方法中添加如下语句设置无标题全屏:
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
那如何获取View的绝对位置呢?
逐一看了看View中与位置相关的几个get方法:
getGlobalVisibleRect(Rect r, Point globalOffset)
getLocationInWindow(int[] location)
getLocationOnScreen(int[] location)
完整Demo如下:
布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".CollisionDetectionActivity" > <TextView android:id="@+id/touch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" /> <TextView android:id="@+id/result1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="#FFBBBBFF" android:textSize="20sp" /> <TextView android:id="@+id/block1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/touch" android:background="#FFFFBBFF" android:padding="10dp" android:text="Touch Block1" android:textSize="20sp" /> <TextView android:id="@+id/block2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/block1" android:background="#FFBBFFBB" android:padding="10dp" android:text="Touch Block2" android:textSize="20sp" /> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@id/result1" android:layout_below="@id/block2" android:id="@+id/myScrollView"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <TextView android:id="@+id/info1" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10dp" android:textSize="20sp" /> <TextView android:id="@+id/info2" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10dp" android:textSize="20sp" /> </LinearLayout> </ScrollView> </RelativeLayout>
Activity:
package com.example.hellocollisiondetection; import com.mengdd.utils.Collision2DUtils; import android.os.Bundle; import android.app.Activity; import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; import android.view.Menu; import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.ScrollView; import android.widget.TextView; public class CollisionDetectionActivity extends Activity { private TextView mTouchTextView = null; private TextView mResult1 = null; private View block1 = null; private View block2 = null; private TextView mInfo1 = null; private TextView mInfo2 = null; private ScrollView mScrollView = null; @Override protected void onCreate(Bundle savedInstanceState) { // requestWindowFeature(Window.FEATURE_NO_TITLE); // getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); super.onCreate(savedInstanceState); setContentView(R.layout.activity_collision_detection); mTouchTextView = (TextView) findViewById(R.id.touch); mResult1 = (TextView) findViewById(R.id.result1); block1 = findViewById(R.id.block1); block2 = findViewById(R.id.block2); mInfo1 = (TextView) findViewById(R.id.info1); mInfo2 = (TextView) findViewById(R.id.info2); mScrollView = (ScrollView) findViewById(R.id.myScrollView); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.collision_detection, menu); return true; } @Override public boolean onTouchEvent(MotionEvent event) { if (MotionEvent.ACTION_DOWN == event.getAction()) { float x = event.getX(); float y = event.getY(); mTouchTextView.setText("x: " + x + ", y: " + y); Log.i("Touch", "x: " + x + ", y: " + y); detectCollision(x, y); getInfos(); } return super.onTouchEvent(event); } private void detectCollision(float x, float y) { // Rect rect1 = new Rect(0, 0, 200, 200); // boolean result1 = Collision2DUtils.isPointInRect((int)x, (int)y, // rect1); // // RectF rect2 = new RectF(0,200,600,1000); // boolean result2 = Collision2DUtils.isPointInRectF(x, y, rect2); boolean result1 = Collision2DUtils.isPointInView(x, y, block1); boolean result2 = Collision2DUtils.isPointInView(x, y, block2); Log.i("Touch", "result1: " + result1 + ", result2: " + result2); mResult1.setText("result1: " + result1 + ", result2: " + result2); } private void getInfos() { mInfo1.setText(getViewInfo(block1)); mInfo2.setText(getViewInfo(block2)); } private String getViewInfo(View view) { StringBuffer stringBuffer = new StringBuffer(); int top = view.getTop(); int left = view.getLeft(); int right = view.getRight(); int bottom = view.getBottom(); int width = view.getWidth(); int height = view.getHeight(); stringBuffer.append("Info relative to Parent: " + "left: " + left + ", right: " + right + ", top: " + top + ", bottom: " + bottom + ", " + width + ", height: " + height); // API Level 11 stringBuffer.append(" view.getTranslationX(): " + view.getTranslationX()); stringBuffer.append(" view.getTranslationY(): " + view.getTranslationY()); Rect rect = new Rect(); view.getLocalVisibleRect(rect); stringBuffer.append(" getLocalVisibleRect(): "); stringBuffer.append(" " + rect.toString()); Rect globalRect = new Rect(); view.getGlobalVisibleRect(globalRect); stringBuffer.append(" getGlobalVisibleRect(): "); stringBuffer.append(" " + globalRect.toString()); int[] locationInWindow = new int[2]; view.getLocationInWindow(locationInWindow); stringBuffer.append(" getLocationInWindow(): "); stringBuffer.append(" " + locationInWindow[0] + ", " + locationInWindow[1]); int[] locationOnScreen = new int[2]; view.getLocationOnScreen(locationOnScreen); stringBuffer.append(" getLocationOnScreen(): "); stringBuffer.append(" " + locationOnScreen[0] + ", " + locationOnScreen[1]); return stringBuffer.toString(); } }
将两个有背景色的TextView的信息都获取并显示出来,注意此时是有标题栏的非全屏情况:
运行截图如下:
TextView1的信息:(在截图上加了红色的圈)
TextView2的信息:
说明:
getGlobalVisibleRect(Rect r)是可以得到绝对坐标的。
getLocationInWindow(int[] location)
getLocationOnScreen(int[] location)
这两个方法可以返回View左上角的绝对坐标。目前还不出有什么差别。
查看源码发现:
public void getLocationOnScreen(int[] location) { getLocationInWindow(location); final AttachInfo info = mAttachInfo; if (info != null) { location[0] += info.mWindowLeft; location[1] += info.mWindowTop; } }
所以屏幕位置是在窗口位置上加了一个偏置。