Android自定義控制元件熱身——View的座標位置和大小詳解
在自定義控制元件中我們經常會用到View位置的騰挪移動,今天就來和大家一塊揭開View座標位置的神祕面紗。
android中View的座標系統 :螢幕的左上角View繪製區是座標系統原點(0,0),原點向右延伸是X軸正方向,原點向下延伸是Y軸正方向。
螢幕高度=狀態列高度+應用區高度=狀態列高度+(標題欄高度+View繪製區域高度)
一、View的座標位置
1、getTop(),getBottom(),getLeft()和getRight()
需要注意view的座標位置是相對父容器(緊包括著View的父容器不是最外層的父容器)而言的。
以getTop為例,函式原始碼為:
/** * Top position of this view relative to its parent. * 相對應父控制元件的top位置,單位為畫素,即頭部到父控制元件的距離 * @return The top of this view, in pixels. */ @ViewDebug.CapturedViewProperty public final int getTop() { return mTop; }
①為View.getLeft() ;
②為View.getTop();
③為ViewGroup.getRight();
④為ViewGroup.getBottom()
2、getX和getY
如果對View進行了移動如:
View.setTranslationX(200); 則View.getLeft()的值依然為①,View.getLeft() ≠ ① + 200;
View.getLeft()是控制元件原始位置距離父View左邊的距離,那麼移動後的距離如何獲得呢?其實可以通過View.getX獲得控制元件移動後的座標View.getX
= ① + 200即, View.getX()= View.getLeft() + View.getTranslationX()
getTop()、getBottom()、getLeft()和getRight()是控制元件初始位置距離父View容器上、下、左、右邊的距離;
View.getX、View.getY是控制元件最後視覺位置(如果有移動則是移動過後的位置)距離父View父容器左邊、上邊的距離。
也可獲取控制元件距離螢幕的距離
final int[] location = new int[2]; View.getLocationOnScreen(location); int x = location[0]; int y = location[1]; // 控制元件最終視覺位置(如果有移動則是移動過後的位置)距離手機螢幕螢幕左邊、上邊的距離 Log.i(TAG, "OnScreenX=" + x); Log.i(TAG, "OnScreenY=" + y);
在Activity的onCreat方法中獲取不到控制元件的位置和大小,必修要控制元件完成繪製才可以獲取到其位置和大小,可以用以下兩種方式來實現:
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//獲取控制元件的位置和大小
}
View.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
// 在控制元件完成繪製後呼叫
@Override
public void onGlobalLayout() {
//後去控制元件的位置和大小
.
.
.
// 測量成功後移除監聽器,只調用一次
View.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
3、MotionEvent類中 getRawX()和 getRawY()
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
float rawX = event.getRawX();
float x = event.getX();
float rawY = event.getRawY();
float y = event.getY();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return super.onTouchEvent(event);
}
event.getRowX():觸控點相對於手機螢幕原點的x座標,不管App是否有狀態列、全屏等。
event.getX(): 觸控點相對於自身元件原點的x座標 ,觸控點到觸控點所在控制元件左上角的X軸距離
4、通過對View多種方式的移動來研究View的getLeft和getX的不同
activity_main.xml
<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"
tools:context="com.havorld.viewxy.MainActivity" >
<LinearLayout
android:id="@+id/ll"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:background="@android:color/holo_green_light"
android:text="我是TextView" />
</LinearLayout>
</RelativeLayout>
MainActivity.java
public class MainActivity extends Activity implements OnClickListener {
protected static final String TAG = "Havorld";
private TextView textView;
private LinearLayout linearLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.tv);
linearLayout = (LinearLayout) findViewById(R.id.ll);
textView.setOnClickListener(this);
linearLayout.setOnClickListener(this);
}
private void move6() {
textView.layout(textView.getLeft() + 20, textView.getTop(),
textView.getRight(), textView.getBottom());
}
private void move5() {
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) textView
.getLayoutParams();
lp.leftMargin += 100;
textView.setLayoutParams(lp);
}
private void move4() {
linearLayout.scrollBy(-30, -30);
}
private void move3() {
textView.setTranslationY(300);
}
private void move2() {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,
"translationY", 0, 50, 100, 150, 200, 250, 300);
objectAnimator.setDuration(2000).start();
}
private void move1() {
TranslateAnimation translateAnimation = new TranslateAnimation(20, 20,
0, 300);
translateAnimation.setDuration(2000);
translateAnimation.setFillAfter(true);// 保持移動後的狀態
textView.startAnimation(translateAnimation);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv:// 點選檢視位置
Log.e(TAG, "getLeft:" + textView.getLeft());
Log.e(TAG, "getTop:" + textView.getTop());
Log.e(TAG, "getX:" + textView.getX());
Log.e(TAG, "getY:" + textView.getY());
break;
case R.id.ll: // 點選對textView進行移動
// 方式一:通過補間動畫移動
// move1();
// 方式二:通過屬性動畫移動
// move2();
// 方式三:通過setTranslationX或setTranslationY等移動
// move3();
// 方式四:通過scrollBy或ScrollTo移動
move4();
// 方式五:通過改變佈局引數
// move5();
// 方式六:通過改變佈局引數
move6();
break;
default:
break;
}
}
}
先點選外層佈局linearLayout分別通過四種方式對textView進行移動,然後再分別點選textView獲取其移動後的位置。
方式一:通過補間動畫移動
注意:補間動畫移動後點擊TextView獲取其位置時要點選其原始位置而不是移動後的位置
移動後位置的點選事件不可執行
方式二:通過屬性動畫移動
移動後位置的點選事件可執行
方式三:通過setTranslationX或setTranslationY等移動
移動後位置的點選事件可執行
方式四:通過ScrollTo和scrollBy移動
移動後位置的點選事件可執行
方式五:通過改變佈局引數
移動後位置的點選事件可執行
方式六:通過改變佈局引數
移動後位置的點選事件可執行
二、View控制元件的大小
1、getWidth()和getHeight()
getWidth()和getHeight()是獲取控制元件的寬高。得到的是view在父容器中佈局好後的寬高值,如果沒有父佈局,那麼預設的父佈局是整個螢幕。2、getMeasuredWidth()和getMeasuredHeight()
getMeasuredWidth()和getMeasuredHeight()是獲取控制元件內容的寬高。
對控制元件上的內容進行測量後得到的view裡面的內容佔據的寬度和高度,前提是你必須在父佈局的onLayout()方法或者此View的onDraw()方法裡呼叫measure(int widthMeasureSpec, int heightMeasureSpec),獲取之前必須呼叫否則將與getWidth()和getHeight()得到的結果相同。
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyLayout myLayout = new MyLayout(this);
MyTextView textView = new MyTextView(this);
LayoutParams layoutParams = new LayoutParams(200, 400);
textView.setLayoutParams(layoutParams);
myLayout.addView(textView);
setContentView(myLayout);
}
public class MyLayout extends LinearLayout {
public MyLayout(Context context) {
super(context);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
// View childView=getChildAt(0);
// childView.measure(0, 0);
// 如果新增測量的話:列印日誌:MyLayout---MeasuredWidth= 200,MeasuredHeight= 400
// measure(0, 0);
// 列印日誌: MyLayout---width= 1080,height= 1716
Log.i("TAG", "MyLayout---width= " + getWidth() + ",height= "
+ getHeight());
// 列印日誌:MyLayout---MeasuredWidth= 1080,MeasuredHeight= 1716
Log.i("TAG", "MyLayout---MeasuredWidth= " + getMeasuredWidth()
+ ",MeasuredHeight= " + getMeasuredHeight());
}
}
public class MyTextView extends TextView {
public MyTextView(Context context) {
super(context);
setText("test test ");
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
measure(0, 0); // 新增測量方法
// 列印日誌:TextView---width: 200,height: 400
Log.i("TAG", "TextView---width: " + getWidth() + ",height: "
+ getHeight());
// 列印日誌:TextViewTest---MeasuredWidth: 164,MeasuredHeight: 57
Log.i("TAG", "TextView---MeasuredWidth: " + getMeasuredWidth()
+ ",MeasuredHeight: " + getMeasuredHeight());
}
}
}
注:
getMeasuredWidth()和getMeasuredHeight 的值是在 onMeausre 方法結束後獲取到的;
getWidth() 和 getHeight() 的值是在 onLayout 方法結束後可以獲取到的。
另外getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進行設定的,
而getWidth()方法中的值則是通過檢視右邊的座標減去左邊的座標計算出來的。
檢視大小的控制是由父檢視、佈局檔案、以及檢視本身共同完成的,父檢視會提供給子檢視參考的大小,而開發人員可以在XML檔案中指定檢視的大小,然後檢視本身會決定其最終的大小。