Android自己定義組件系列【1】——自己定義View及ViewGroup
View類是ViewGroup的父類,ViewGroup具有View的全部特性。ViewGroup主要用來充當View的容器。將當中的View作為自己孩子,並對其進行管理。當然孩子也能夠是ViewGroup類型。
View類一般用於畫圖操作,重寫它的onDraw方法,但它不能夠包括其它組件,沒有addView(View view)方法。
ViewGroup是一個組件容器,它能夠包括不論什麽組件,但必須重寫onLayout(boolean changed,int l,int t,int r,int b)和onMesure(int widthMesureSpec,int heightMesureSpec)方法. 否則
package com.example.testrefreshview; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new MyViewGroup(this)); } public class MyViewGroup extends ViewGroup { public MyViewGroup(Context context) { super(context); Button button1 = new Button(context); button1.setText("button1"); Button button2 = new Button(context); button2.setText("button2"); TextView textView = new TextView(context); textView.setText("textView"); addView(button1); addView(button2); addView(textView); } @Override protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) { } } }
View的layout(int left,int top,int right,int bottom)方法負責把該view放在參數指定位置,所以如果我們在自己定義的ViewGroup::onLayout中遍歷每個子view並用view.layout()指定其位置。每個子View又會調用onLayout,這就構成了一個遞歸調用的過程
如果在ViewGroup中重寫onDraw方法。須要在構造方法中調用this.setWillNoDraw(flase); 此時,系統才會調用重寫過的onDraw(Canvas cancas)方法。否則系統不會調用onDraw(Canvas canvas)方法.
將上面代碼改動一下,就能夠顯示出來。
package com.example.testrefreshview;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyViewGroup(this));
}
public class MyViewGroup extends ViewGroup {
public MyViewGroup(Context context) {
super(context);
Button button1 = new Button(context);
button1.setText("button1");
Button button2 = new Button(context);
button2.setText("button2");
TextView textView = new TextView(context);
textView.setText("textView");
addView(button1);
addView(button2);
addView(textView);
}
@Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3,
int arg4) {
int childCount = getChildCount();
int left = 0;
int top = 10;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
child.layout(left, top, left + 60, top + 60);
top += 70;
}
}
}
}
再看一段代碼:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int childCount = getChildCount();
//設置該ViewGroup的大小
int specSize_width = MeasureSpec.getSize(widthMeasureSpec);
int specSize_height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(specSize_width, specSize_height);
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
childView.measure(80, 80);
}
}
通過重寫onMeasure方法不但能夠為ViewGroup指定大小,還能夠通過遍歷為每個子View指定大小。在自己定義ViewGroup中加入上面代碼為ViewGroup中的每個子View分配了顯示的寬高。 以下我們讓子View動起來吧。加入代碼例如以下:
public boolean onTouchEvent(MotionEvent ev) {
final float y = ev.getY();
switch(ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastMotionY = y;
break;
case MotionEvent.ACTION_MOVE:
int detaY = (int)(mLastMotionY - y);
mLastMotionY = y;
scrollBy(0, detaY);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
在上面用到了一個scrollBy方法,打開官方API能夠看到View類有例如以下兩個方法:
這兩個函數貌似都是移動視圖的。那麽它們有什麽差別呢?帶著這個疑問我們向下看
首先 ,我們必須明確在Android View視圖是沒有邊界的,Canvas是沒有邊界的,僅僅只是我們通過繪制特定的View時對 Canvas對象進行了一定的操作。比如 : translate(平移)、clipRect(剪切)等,以便達到我們的對該Canvas對象繪制的要求 。我們能夠將這樣的無邊界的視圖稱為“視圖坐標”-----它不受物理屏幕限制。通常我們所理解的一個Layout布局文件僅僅是該視圖的顯示區域,超過了這個顯示區域將不能顯示到父視圖的區域中 ,相應的,我們能夠將這樣的有邊界的視圖稱為“布局坐標”------ 父視圖給子視圖分配的布局(layout)大小。並且, 一個視圖的在屏幕的起始坐標位於視圖坐標起始處。例如以下圖所看到的。
由於布局坐標僅僅能顯示特定的一塊內容。所以我們僅僅有移動布局坐標的坐標原點就能夠將視圖坐標的不論什麽位置顯示出來。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#888888">
<TextView
android:id="@+id/txt"
android:layout_width="300dip"
android:layout_height="120dip"
android:background="#cccccc"
android:text="textview" />
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="button" />
</LinearLayout>
當我點擊按鈕觸發事件後例如以下:
上面代碼中觸發點擊事件後,運行了textView.scrollTo(-200, -100);scrollTo中的兩個參數的含義不是坐標位置。而是相對於視圖坐標位置的偏移量,如果我們要移動到(200,100)的位置則偏移量為(0,0)-(200,100) = (-200。 -100)。
如果我們將上面代碼換成scrollBy則會發現點擊多次按鈕後textview就會移出可見界面,這是由於scrollBy是相對我們當前坐標進行偏移。
以下我們來看看源碼中對這兩個方法是怎樣實現的:
/** * Set the scrolled position of your view. This will cause a call to * [email protected] #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the x position to scroll to * @param y the y position to scroll to */ public void scrollTo(int x, int y) { if (mScrollX != x || mScrollY != y) { int oldX = mScrollX; int oldY = mScrollY; mScrollX = x; mScrollY = y; invalidateParentCaches(); onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (!awakenScrollBars()) { invalidate(true); } } }能夠看到 mScrollX = x; mScrollY = y;
/** * Move the scrolled position of your view. This will cause a call to * [email protected] #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the amount of pixels to scroll by horizontally * @param y the amount of pixels to scroll by vertically */ public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }能夠看到 mScrollX + x, mScrollY + y;
mScrollX和mScrollY是相對於視圖坐標的當前偏移量。
Android自己定義組件系列【1】——自己定義View及ViewGroup