網(wǎng)上有很多關(guān)于關(guān)于自定義view 的文章,這里我還是自己動手謝了一下自定義view,來熟悉自定義view 的主要步驟??傆?jì)如下幾點(diǎn)
- 聲明自定義的屬性。
- 獲取自定義view 的屬性value。
- onMesure 獲取自定義view 的大小
- onLayout 確定自定義view 的位置
- onDraw 使用Canvas 畫出自定義view
我們通過自定義一個CustomTextview 來了解一下相關(guān)的過程。
代碼參見 * https://github.com/lijing01/AndroidDemo
聲明自定義View 的屬性
在values 下面創(chuàng)建一個attrs 屬性文件。
<code><declare-styleable name="CustomTextView">
<attr name="ttext" format="string"></attr>
<attr name="ttextColor" format="color"></attr>
<attr name="ttextBgColor" format="color"></attr>
<attr name="ttextSize" format="dimension|integer"></attr>
</declare-styleable>
</code>
上面一共定義了四個屬性,text, textColor,textBgColor,textSize 。format 指的是 attr 的類型,具體可以參考一下這篇文章Android中attr自定義屬性詳解 。
獲取自定義屬性的 value
<code>
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTextView, defStyleAttr, 0);
for (int i = 0; i < a.getIndexCount(); i++) {
int arrt = a.getIndex(i);
switch (arrt) {
case R.styleable.CustomTextView_ttext:
text = a.getString(arrt);
break;
case R.styleable.CustomTextView_ttextColor:
textColor = a.getColor(arrt, Color.BLACK);
break;
case R.styleable.CustomTextView_ttextBgColor:
textBgColor = a.getColor(arrt, Color.BLUE);
break;
case R.styleable.CustomTextView_ttextSize:
textSize = a.getDimensionPixelSize(arrt,(int)
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
16,getResources().getDisplayMetrics()));
break;
}
}
if(textSize == 0){
textSize=(int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics());
}
a.recycle();
mPaint= newPaint();
mPaint.setTextSize(textSize);
mPaint.setColor(textColor);
mRect = new Rect();
mPaint.getTextBounds(text, 0, text.length(), mRect);
</code>
如上我們獲取到了自定義的屬性值。當(dāng)textSize 沒有賦值的時候我們會給textSize 賦默認(rèn)值,以免在真實(shí)繪制的過程中size 為 0。 獲取完,自定義屬性 調(diào)用 a.recycle(); 防止內(nèi)存溢出。
繪制過程
<code>
@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);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
mPaint.setTextSize(textSize);
mPaint.getTextBounds(text, 0, text.length(), mRect);
float textWidth = mRect.width();
int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
width = desired;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
mPaint.setTextSize(textSize);
mPaint.getTextBounds(text, 0, text.length(), mRect);
float textHeight = mRect.height();
int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
height = desired;
}
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(textBgColor);
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
mPaint.setColor(textColor);
canvas.drawText(text, (getWidth()- mRect.width()) / 2, (getHeight() +
mRect.height()) / 2, mPaint);
}</code>
剛開始的時候遇到一個坑,沒有重現(xiàn) onmesure 方法,帶來的后果是 layout_width, layout_height, 無論是設(shè)置成 match_parent, 還是 warp_content, 的結(jié)果都是match_parent。 重寫之后一切正常。效果如下圖。

OnLayout
因?yàn)槲覀儸F(xiàn)在討論的是View,沒有子View需要排列,所以這一步其實(shí)我們不需要做額外的工作。插一句,對ViewGroup類,onLayout方法中,我們需要將所有子View的大小寬高設(shè)置好,這個我們下一篇會詳細(xì)說。
View中還有三個比較重要的方法
- requestLayout
View重新調(diào)用一次layout過程。 - invalidate
View重新調(diào)用一次draw過程 - forceLayout
標(biāo)識View在下一次重繪,需要重新調(diào)用layout過程。
自定義view 相關(guān)文章
android 自定義ViewGroup