1. 程式人生 > >Android自定義控制元件熱身——View的座標位置和大小詳解

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檔案中指定檢視的大小,然後檢視本身會決定其最終的大小。