編輯:關於Android編程
android.app.View 就是手機的UI,View 負責繪制UI,處理事件(evnet),Android 利用 View 打造出所 Widgets,利用 Widget 可打造出互動式的使用者介面,每個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,不需要重寫所有這些方法。事實上,你可以只onDraw(android.graphics.Canvas)
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的時候才添加上的
requestLayout
View重新調用一次layout過程
invalidate
View重新調用一次draw過程
forceLayout
標識View在下一次重繪,需要重新調用layout過程。
postInvalidate
這個方法與invalidate方法的作用是一樣的,都是使View樹重繪,但兩者的使用條件不同,postInvalidate是在非UI線程中調用,invalidate則是在UI線程中調用。
這裡我們先簡單理解View 的繪制,後續文章我們會深入理解。
1.測量——onMeasure():決定View的大小
2.布局——onLayout():決定View在ViewGroup中的位置
3.繪制——onDraw():如何繪制這個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);
}
}
這裡是一個普通的自定義View,裡面畫了圓,根據不同的模式設置了父View的大小。
關於View重寫onMeasure()
時機:
如果用了wrap_content
。那麼在onMeasure()
中就要調用setMeasuredDimension()
,
來指定view的寬高。如果使用的是match_parent
或者一個具體的dp值。那麼直接使用super.onMeasure()
即可。
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());
}
}
}
}
這裡是一個簡單的自定義ViewGroup,實現類似LinearLayout 橫向排放子View位置。這就是一個簡單的ViewGroup過程。
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);
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的實戰學習。不管有沒有任何疑問,歡迎在下方留言吧。
本文描述init.rc腳本解析以及執行過程,讀完本章後,讀者應能 (1) 了解init.rc解析過程 (2) 定制init.rc ini
開源地址:https://github.com/SimonVT/android-menudrawer 簡介:menudrawer是跟sliderMenu差不多的一種框架,
Android 中自定義軟鍵盤 圖一為搜狗輸入法、圖二為自定義密碼鍵盤、圖三為自定義密碼鍵盤 java源文件 package
今天群裡邊有人問怎麼自定義Android holo主題下的Progressbar; 我想到之前做過自定義Progressbar,通過自己寫動畫和Style可以用任何圖片當