Android之View(一)
掌握
- 什麼是View?
- View 座標的基本概念
- View的生命週期
- 如何自定義View
什麼是View?
android.app.View 就是手機的UI,View 負責繪製UI,處理事件(evnet),Android 利用 View 打造出所 Widgets,利用 Widget 可打造出互動式的使用者介面,每個View 負責一定區域的繪製。
一張圖理解常用控制元件層級關係
View 座標的基本概念
View的寬高是有top、left、right、bottom引數決定的 而X,Y和translationX,和translationY則負責View位置的改變。
從Android3.0開始,加入了translation的概念,即相對於父容器的偏移量以及X,Y座標的概念,X,Y代表左上頂點的橫縱座標。當View在發生平移時,getX,getY,setX,setY
get/setTranslationX/Y來獲得當前左上點的座標。
X=left+translationX Y同理。
注意:在View發生改變的過程中,top,left等值代表原始位置,是不會改變的。改變的只有X,Y,translationX/Y。
一張圖理解View的座標概念
View的生命週期
View 的幾個建構函式
public MyView(Context context)
java程式碼直接new一個Custom View例項的時候,會呼叫第一個建構函式public MyView(Context context, AttributeSet attrs)
在xml建立但是沒有指定style的時候被呼叫.多了一個AttributeSet型別的引數,自定義屬性,在通過佈局檔案xml建立一個view時,會把XML內的引數通過AttributeSet帶入到View內。public MyView(Context context, AttributeSet attrs, int defStyleAttr)
建構函式中第三個引數是預設的Style,這裡的預設的Style是指它在當前Application或Activity所用的Theme中的預設Style,且只有在明確呼叫的時候才會呼叫@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
該建構函式是在api21的時候才新增上的
View 的幾個重要方法
requestLayout
View重新呼叫一次layout過程invalidate
View重新呼叫一次draw過程forceLayout
標識View在下一次重繪,需要重新呼叫layout過程。postInvalidate
這個方法與invalidate方法的作用是一樣的,都是使View樹重繪,但兩者的使用條件不同,postInvalidate是在非UI執行緒中呼叫,invalidate則是在UI執行緒中呼叫。
自定義View
簡單理解View的繪製
這裡我們先簡單理解View 的繪製,後續文章我們會深入理解。
1.測量——onMeasure():決定View的大小
2.佈局——onLayout():決定View在ViewGroup中的位置
3.繪製——onDraw():如何繪製這個View。
自定義View的分類
- 繼承View
- 繼承ViewGroup
- 繼承系統控制元件(Button,LinearLayout…)
自定義View的過程
自定義 View 首先要實現一個繼承自 View 的類
新增類的構造方法,通常是三個構造方法,不過從 Android5.0 開始構造方法已經新增到 4 個了
override
父類的方法,如onDraw,(onMeasure)
等自定義屬性,需要在 values 下建立
attrs.xml
檔案,在其中定義屬性
通過context.obtainStyledAttributes將建構函式中的attrs進行解析出來,就可以拿到相對應的屬性.
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyView);
mColor = typedArray.getColor(R.styleable.MyView_myColor, 0XFF00FF00);
【注意】三個函式獲取尺寸的區別:
getDimension()
是基於當前DisplayMetrics進行轉換,獲取指定資源id對應的尺寸
getDimensionPixelSize()
與getDimension()
功能類似,不同的是將結果轉換為int,並且小數部分四捨五入
getDimensionPixelOffset()
與getDimension()
功能類似,不同的是將結果轉換為int,取整去除小數。舉個例子
列如getDimension()
返回結果是20.5f,那麼getDimensionPixelSize()
返回結果就是 21,getDimensionPixelOffset()
返回結果就是20。
開啟佈局檔案我們可以看到有很多的以xmlns開頭的欄位。其實這個就是XML name space 的縮寫。我們可以使用res-atuo
名稱空間,就不用在新增自定義View全類名。
xmlns:app="http://schemas.android.com/apk/res-auto"
/**
* Created by fuchenxuan on 16/6/4.
*/
public class MyView extends View {
private int mRadius=200;
private int mColor;
public MyView(Context context) {
this(context,null);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//read custom attrs
TypedArray t = context.obtainStyledAttributes(attrs,
R.styleable.rainbowbar, 0, 0);
mRadius = t.getDimensionPixelSize(R.styleable.coutom_radius, (int) hSpace);
t.getDimensionPixelOffset(R.styleable.coutom_at1, (int) vSpace);
mColor=t.getColor(R.styleable.color, barColor);
t.recycle(); // we should always recycle after used
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//set size
setMeasuredDimension(widthMode == MeasureSpec.AT_MOST ? (int) mRadius * 3 : widthSize, heightMode == MeasureSpec.AT_MOST ? (int) mRadius * 3 : heightSize);
}
//draw be invoke clire.
int index = 0;
@Override
protected void onDraw(Canvas canvas) {
//super.onDraw(canvas);
mPaint = new Paint();
mPaint.setColor(mColor);
mPaint.setAntiAlias(true);
canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
這裡是一個普通的自定義View,裡面畫了圓,根據不同的模式設定了父View的大小。
關於View重寫onMeasure()
時機:
如果用了wrap_content
。那麼在onMeasure()
中就要呼叫setMeasuredDimension()
,
來指定view的寬高。如果使用的是match_parent
或者一個具體的dp值。那麼直接使用super.onMeasure()
即可。
自定義ViewGroup
自定義ViewGroup的過程
- 自定義 ViewGroup 和自定義View 一樣,只是繼承自 ViewGroup 的類,和必須實現
onLayout()
函式
/**
* Created by fuchenxuan on 16-6-6.
*/
public class CostumViewGroup extends ViewGroup {
public CostumViewGroup(Context context) {
super(context);
}
public CostumViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
這裡是一個簡單的自定義ViewGroup,實現類似LinearLayout 橫向排放子View位置。這就是一個簡單的ViewGroup過程。
徹底理解MeasureSpec三種模式
View的大小不僅由自身所決定,同時也會受到父控制元件的影響,為了我們的控制元件能更好的適應各種情況,一般會自己進行測量。他們是由 mode+size兩部分組成的。widthMeasureSpec和heightMeasureSpec轉化成二進位制數字表示,他們都是30位的。前兩位代表mode(測量模 式),後面28位才是他們的實際數值(size);MeasureSpec.getMode()
獲取模式,MeasureSpec.getSize()
獲取尺寸
測量View大小使用的是onMeasure函式,所以我們需要了解三種測量模式:
EXACTLY
:一般是設定了明確的值(100dp)或者是MATCH_PARENT
AT_MOST
:表示子佈局限制在一個最大值內,一般為WARP_CONTENT
UNSPECIFIED
:表示子佈局想要多大就多大,很少使用
關於ViewGroup重寫onMeasure()
時機:
首先要先測量子View的寬高:
getChildAt(int index)
可以拿到index上的子view。
getChildCount()
得到子view的個數,再迴圈遍歷出子view。使用子view自身的測量方法
childView.measure(int wSpec, int hSpec);
或使用viewGroup的測量子view的方法:
measureChild(subView, int wSpec, int hSpec);
測量某一個子view,多寬,多高, 內部加上了viewGroup的padding值measureChildren(int wSpec, int hSpec);
測量所有子view 都是 多寬,多高, 內部呼叫了measureChild方法measureChildWithMargins(subView, intwSpec, int wUsed, int hSpec, int hUsed);
測量某一個子view,多寬,多高, 內部加上了viewGroup的padding值、margin值和傳入的寬高wUsed、hUsed
問題總結
getWidth()和getMeasuredWidth()的區別?
getMeasuredWidth():只要一執行完 setMeasuredDimension() 方法,就有值了,並且不再改變。
getWidth():必須執行完 onMeasure() 才有值,可能發生改變。
如果 onLayout 沒有對子 View 實際顯示的寬高進行修改,那麼 getWidth() 的值 == getMeasuredWidth() 的值。onLayout() 和Layout()的區別?
onLayout() ViewGroup中子View的佈局方法,layout()是子View佈局的方法View 裡面的 onSavedInstanceState和onRestoreInstanceState的作用?
View和Activity一樣的,每個View都有onSavedInstanceState和onRestoreInstanceState這兩個方法,可用於儲存和恢復view的狀態。
在本章節中我們知道什麼是View?,View 座標的基本概念,理解了View的生命週期,學習瞭如何自定義View?雖然全是理論知識總結,在後續我們會一起來自定義View的實戰學習。不管有沒有任何疑問,歡迎在下方留言吧。
更多Android 面試題總結,請點選下方圖片哦。