編輯:關於Android編程
對於很多Android入門程序猿來說自定義View,都是比較恐懼的,但是這又是高手進階的必經之路。先總結下自定義View的步驟:
1、自定義View的屬性
2、在View的構造方法中獲得我們自定義的屬性
[ 3、重寫onMesure ]
4、重寫onDraw
我把3用[]標出了,所以說3不一定是必須的,當然了大部分情況下還是需要重寫的。
其中第1,第2點在前面的文章已經有詳細的介紹Android自定義屬性,不了解的童鞋可以去看看參考下,本文著重介紹第3和第4點。
onMeasure()
onMeasure()方法顧名思義就是用於測量視圖的大小的。它接收兩個參數,widthMeasureSpec和heightMeasureSpec,這兩個值分別用於確定視圖的寬度和高度的規格和大小。
MeasureSpec的值由specSize和specMode共同組成的,其中specSize記錄的是大小,specMode記錄的是規格。specMode一共有三種類型,如下所示:
表示父視圖希望子視圖的大小應該是由specSize的值來決定的,系統默認會按照這個規則來設置子視圖的大小,開發人員當然也可以按照自己的意願設置成任意的大小。(一般是在布局中設置了明確的值或者是MATCH_PARENT)
AT_MOST表示子視圖最多只能是specSize中指定的大小,開發人員應該盡可能小得去設置這個視圖,並且保證不會超過specSize。系統默認會按照這個規則來設置子視圖的大小,開發人員當然也可以按照自己的意願設置成任意的大小。(一般為WARP_CONTENT)
UNSPECIFIED表示開發人員可以將視圖按照自己的意願設置成任意的大小,沒有任何限制。這種情況比較少見,不太會用到。
那麼你可能會有疑問了,widthMeasureSpec和heightMeasureSpec這兩個值又是從哪裡得到的呢?通常情況下,這兩個值都是由父視圖經過計算後傳遞給子視圖的,說明父視圖會在一定程度上決定子視圖的大小。
當然,onMeasure()方法是可以重寫的,也就是說,如果你不想使用系統默認的測量方式,可以按照自己的意願進行定制,比如:
public class MyView extends View {
......
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(200, 200);
}
}
這樣的話就把View默認的測量流程覆蓋掉了,不管在布局文件中定義MyView這個視圖的大小是多少,最終在界面上顯示的大小都將會是200*200。
需要注意的是,在setMeasuredDimension()方法調用之後,我們才能使用getMeasuredWidth()和getMeasuredHeight()來獲取視圖測量出的寬高,以此之前調用這兩個方法得到的值都會是0(注意區別於而getWidth()和getHeight(),這兩個方法要在ViewGroup的onLayout()過程結束後才能獲取到)。
由此可見,視圖大小的控制是由父視圖、布局文件、以及視圖本身共同完成的,父視圖會提供給子視圖參考的大小,而開發人員可以在XML文件中指定視圖的大小,然後視圖本身會對最終的大小進行拍板。
onDraw()
measure和layout的過程都結束後,接下來就進入到draw的過程了。同樣,根據名字你就能夠判斷出,在這裡才真正地開始對視圖進行繪制。
好了,原理性東西我們講過了,下面通過幾個簡單的例子來驗證一下,同樣按上面四步寫法:
1自定義View的屬性,首先在res/values/ 下建立一個attrs.xml , 在裡面定義我們的屬性和聲明我們的整個樣式。
然後在布局中聲明我們的自定義View,一定要引入我們的命名空間xmlns:custom=”http://schemas.android.com/apk/res/com.hx.test”
2
在View的構造方法中,獲得我們的自定義的屬性,我們重寫了3個構造方法,默認的布局文件調用的是兩個參數的構造方法,所以記得讓所有的構造調用我們的三個參數的構造,我們在三個參數的構造中獲得自定義屬性。
public CustomTextView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public CustomTextView(Context context)
{
this(context, null);
}
/**
* 獲得我自定義的樣式屬性
*
* @param context
* @param attrs
* @param defStyle
*/
public CustomTextView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
/**
* 獲得我們所定義的自定義樣式屬性
*/
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTextView, defStyle, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++)
{
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.CustomTextView_titleText:
mTitleText = a.getString(attr);
break;
case R.styleable.CustomTextView_titleTextColor:
mTitleTextColor = a.getColor(attr, Color.BLACK);
break;
case R.styleable.CustomTextView_titleTextSize:
// 默認設置為16sp,TypeValue也可以把sp轉化為px
mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
break;
}
}
a.recycle();
/**
* 獲得繪制文本的寬和高
*/
mPaint = new Paint();
mPaint.setTextSize(mTitleTextSize);
mPaint.setColor(mTitleTextColor);
mBound = new Rect();
mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
}
3
我們重寫onDraw,onMesure調用系統提供的:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.GREEN);
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
mPaint.setColor(mTitleTextColor);
canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2,
getHeight() / 2 + mBound.height() / 2, mPaint);
}
運行效果圖:

這已經基本已經實現了自定義View。但是此時如果我們把布局文件的寬和高寫成wrap_content,會發現效果並不是我們的預期:

這是因為MATCH_PARENT 時系統返回的specMode的值為AT_MOST,而specSize為全屏,子視圖默認占據了specSize中指定大小的范圍。這時需要我們重寫onDraw來實現自己的邏輯。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height ;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
mPaint.setTextSize(mTitleTextSize);
mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
float textWidth = mBound.width();
int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
width = desired;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
mPaint.setTextSize(mTitleTextSize);
mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
float textHeight = mBound.height();
int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
height = desired;
}
setMeasuredDimension(width, height);
}
我們再來修改下布局文件
效果如下:

完全復合我們的預期,現在我們可以對高度、寬度進行隨便的設置了,基本可以滿足我們的需求。我們還可以在構造函數中對它的點擊事件做一些處理:
this.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mTitleText = randomText();
postInvalidate();
//invalidate();
}
});
private String randomText() {
Random random = new Random();
int randomInt = random.nextInt(5);
return Str[randomInt];
}
其中
private String[] Str = {"Text0", "Text1", "Text2", "Text3", "Text4"};

可以看到點擊事件已經生效了,每次當我們點擊自定義View時顯示的文字內容都發生變化。調用postInvalidate()就是讓View重新執行onDraw,其實這裡也可以使用invalidate(),都能生效。他們區別在於:
invalidate()得在UI線程中被調動,在工作者線程中可以通過Handler來通知UI線程進行界面更新。而postInvalidate()可以在工作者線程中被調用。
但是還有一個問題,我們看到點擊後自定義View的文字變短了,但自定義View的寬卻沒有相應變窄,這不符合我們寫的WRAP_CONTENT的思想。其實這是由於postInvalidate()只是強制View重新走onDraw,而View的寬高是在onMesure中定義的,所以文字改變後需要強制自定義View也要走onMesure,重新測量,繼續修改代碼:
this.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v){
mTitleText = randomText();
requestLayout();
}
});

完美實現我們的構想。總結一下RequestLayout用法:
RequestLayout:
當view確定自身已經不再適合現有的區域時,該view本身調用這個方法要求parent view重新調用他的onMeasure onLayout來對重新設置自己位置。
特別的當view的layoutparameter發生改變,並且它的值還沒能應用到view上,這時候適合調用這個方法。也就是當通過getLayoutParrms().width = XXX的時候,我們需要重新調用RequestLayout。
invalidate:
View類調用迫使view重畫(onDraw)。
Android自定義View編寫隨機驗證碼
很多的Android入門程序猿來說對於Android自定義View,可能都是比較恐懼的,但是這又是高手進階的必經之路,所有准備在自定義View上面花一些功夫,多寫一些文章
自繪制Android界面核心類圖
自繪制Android界面核心類圖。入門Android時,會看到過一張系統架構圖,從那張圖可以知道Android系統自上到下被劃分了幾個層次(具體每個層次的職責定義不再概述
Android實現粒子雨效果
本文實例介紹了Android實現粒子雨效果的實現過程,分享給大家供大家參考,具體內容如下先看看效果圖:具體實現方法:1.baseview主要是設定雨滴要實現的動作,只是先
Android Audio Effect 機制初探
前言支持多媒體是Android設備的一個重要功能,可以想象一台不支持多媒體的設備是何等枯燥。通常意義上的多媒體(Multimedia)通常是指圖片、視頻、音頻、文本等等。