本文中用到的例子是來自於http://blog.csdn.net/lmj623565791/article/details/24300125,只是爲了方便更多的人瞭解自定義view的過程對其中的代碼進行詳細的解釋,如果給原作者帶來的不便還請諒解,如果本文中,有什麼說的不對的地方,還請指正,謝謝。
對於自定義view來說我們通常走的步驟大概分爲:
1、自定義View的屬性
2、在View的構造方法中獲得我們自定義的屬性
[ 3、重寫onMesure ]
4、重寫onDraw
我們以上面提到的例子源碼進行詳細的分析:
一、代碼解析
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 自定義屬性 -->
<attr name="titleText" format="string" />
<!-- 前面是自定義屬性的名稱,後面是可使用的類型,注意大小寫 -->
<attr name="titleSize" format="dimension" />
<!-- 這個表示只要是顏色都可以 -->
<attr name="titleColor" format="color" />
<!-- 這個表示只要是資源文件即可 -->
<attr name="image" format="reference" />
<!-- 這種類似於填寫相對位置信息時的選項下面的枚舉類型就是可選項 -->
<attr name="imageScaleType">
<enum name="fillXY" value="0" />
<enum name="center" value="1" />
</attr>
<!-- 此處用於說明CustomImageView 有哪些屬性 -->
<declare-styleable name="customImageView">
<attr name="titleText" />
<attr name="titleSize" />
<attr name="titleColor" />
<attr name="image" />
<attr name="imageScaleType" />
</declare-styleable>
</resources>
這個是屬性文件的內容,在 res/valuse/attr.xml 設置此屬性,代碼中已經做出了詳細的解釋,在此先不贅訴。
下面的是整個view的編寫代碼,也有了詳細的註釋,稍後對其中的代碼片段進行解釋。
public class customImageView extends View {
private Bitmap mImage;
private int mImageScale;
private String mTitle;
private int mTextColor;
private int mTextSize;
private Rect rect;
private Paint mPaint;
private Rect mTextBound;
private int mWidth;
private int mHeight;
private static int IMAGE_SCALE_FITXY = 0;
public customImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);// 此處一定要寫成this
// ,因爲你除非設置樣式否則都會執行第一或者第二個構造函數,不會經過第三個,所以第一第二個要調用第三個
// TODO Auto-generated constructor stub
}
public customImageView(Context context) {
this(context, null);
// TODO Auto-generated constructor stub
}
public customImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 獲取我們自定義的屬性
// 得到屬性數組 //attrs 屬性集合(哪一個對應那些值) 屬性集(前面的那個的哪一個) 默認樣式
TypedArray array = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.customImageView, defStyleAttr, 0);// 這個地方傳入的R.styleable.customImageView
// 就是需要篩選的集合
int n = array.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = array.getIndex(i); // 獲取其中的一個屬性
switch (attr) {
case R.styleable.customImageView_image:
// array 通過get 方法,通過傳入的標識(attr),獲取對應的屬性的內容,此處的就是圖片資源
mImage = BitmapFactory.decodeResource(getResources(),
array.getResourceId(attr, 0));
// bitmap工廠類生成bitmap
break;
case R.styleable.customImageView_imageScaleType:
mImageScale = array.getInt(attr, 0);// 此處是整數 //放大倍數
break;
case R.styleable.customImageView_titleText:
mTitle = array.getString(attr);
break;
case R.styleable.customImageView_titleColor:
mTextColor = array.getColor(attr, 0);
break;
case R.styleable.customImageView_titleSize:
mTextSize = array.getDimensionPixelSize(attr, (int) TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16,
getResources().getDisplayMetrics()));
break;
default:
break;
}
}
array.recycle(); // 釋放內存資源
rect = new Rect(); // 矩形畫布
mPaint = new Paint();// 新建畫筆
mTextBound = new Rect();// 文字的矩形畫布
// 畫筆根據文字內容測算文字矩形畫布的寬高並賦值給mTextBound(此時是包裹文字的最小矩形)
mPaint.getTextBounds(mTitle, 0, mTitle.length(), mTextBound);
}
// 測量函數
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 此處傳入的值由父容器決定
// ---設置控件寬度---
int specMode = MeasureSpec.getMode(widthMeasureSpec); // 獲取寬度的設置類型
int specSize = MeasureSpec.getSize(widthMeasureSpec);// 獲取寬度大小
if (specMode == MeasureSpec.EXACTLY) {// fill_parent,或者具體值 --type : 精確模式
mWidth = specSize;
} else {//非精確模式
// 由圖片決定的寬
int desireByImg = getPaddingLeft() + getPaddingRight()
+ mImage.getWidth();
// 由文字決定的寬
int desireByTitle = getPaddingLeft() + getPaddingRight()
+ mTextBound.width();
if (specMode == MeasureSpec.AT_MOST) {// wrap_content
int desire = Math.max(desireByImg, desireByTitle); // 獲取文字和圖片之間的最大值,保證能正常顯示
mWidth = Math.min(specSize, desire); // 此處的specSize 是父佈局剩餘的大小
// 選擇能正常顯示和剩餘大小的最小值//也就是說如果剩餘值大於最小值此時圖片會顯示不完整,下面的代碼會詳細標明
}
}
// ---設置高度---
specMode = MeasureSpec.getMode(heightMeasureSpec);
specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {
mHeight = specSize;
} else {
int desire = getPaddingTop() + getPaddingBottom()
+ mImage.getHeight() + mTextBound.height();
if (specMode == MeasureSpec.AT_MOST) { // 同上理解
mHeight = Math.min(desire, specSize);
}
}
setMeasuredDimension(mWidth, mHeight);// 設置當前控件的寬高
}
@Override
protected void onDraw(Canvas canvas) {
// ---------繪製一個寬度爲4的矩形邊框----------------
mPaint.setStrokeWidth(4);// 設置畫筆寬度
mPaint.setStyle(Paint.Style.STROKE); // 設置畫筆填充樣式
mPaint.setColor(Color.CYAN);// 設置畫筆顏色
// 繪製一個矩形,左上座標爲0,0 右下爲getMeasuredWidth(), getMeasuredHeight()
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
// --------設置這個控件的4個位置信息--------------------------------------------
rect.left = getPaddingLeft(); // 左上的x 座標
rect.right = mWidth - getPaddingRight();// 右下的x 座標 ,看mWidth 的組成
// 減去這個getPaddingRight等同於getPaddingLeft()+控件自身所用到的寬度
rect.top = getPaddingTop();// 左上的y
rect.bottom = mHeight - getPaddingBottom(); // 右下的y ,解釋同上
// ---------開始繪製文字----------------------
mPaint.setColor(mTextColor);
mPaint.setStyle(Style.FILL);
// 當設置的寬度小於字體需要的寬度,將內容更改爲xxx... 後面爲省略號的形式
if (mTextBound.width() > mWidth) {
TextPaint paint = new TextPaint(mPaint);
String msg = TextUtils.ellipsize(mTitle,
paint, // 參數含義 原始內容,畫筆,真實寬度,出現“...”的方式(中間或者結尾)
(float) mWidth - getPaddingLeft() - getPaddingRight(),
TextUtils.TruncateAt.END).toString();
canvas.drawText(msg, getPaddingLeft(),
mHeight - getPaddingBottom(), mPaint); // 特別指出
} else {
// 正常情況,將字體居中
canvas.drawText(mTitle, mWidth / 2 - mTextBound.width() * 1.0f / 2,
mHeight - getPaddingBottom(), mPaint);
}
// 去掉使用掉的高度
rect.bottom -= mTextBound.height();
if (mImageScale == IMAGE_SCALE_FITXY) {
canvas.drawBitmap(mImage, null, rect, mPaint);
} else {
rect.left = mWidth / 2 - mImage.getWidth() / 2; // 圖片大於控件寬度的時候會是負值,圖片會被裁剪、、也就是說會有一部分顯示不出來
rect.right = mWidth / 2 + mImage.getWidth() / 2;
rect.top = (mHeight - mTextBound.height()) / 2 - mImage.getHeight()
/ 2;
rect.bottom = (mHeight - mTextBound.height()) / 2
+ mImage.getHeight() / 2;
canvas.drawBitmap(mImage, null, rect, mPaint);
}
}
}
二、疑問解答
下面對其中出現的不太容易理解的地方進行分析:
1. 下面這行代碼的含義是什麼呢?
mTextSize = array.getDimensionPixelSize(attr, (int) TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16,
getResources().getDisplayMetrics()));
getDimensionPixelSize 這個方法的功能是獲取像素值,TypedValue .applyDimension(TypedValue.COMPLEX_UNIT_SP, 16,
getResources().getDisplayMetrics()) 這個代碼的含義是將16轉化爲sp 格式時int 的值。也就是16sp 的int 值。
2. 這個將文字居中怎麼理解?
// 正常情況,將字體居中
canvas.drawText(mTitle, mWidth / 2 - mTextBound.width() * 1.0f / 2,
mHeight - getPaddingBottom(), mPaint);
首先我們要理解一下canvas.drawText(text, x, y, paint) 這個函數的含義:首先第一個和第四個參數的含義很容易理解,第一個是需要繪製的文本,最後一個是所使用的畫筆,x,y 的話按照一般的規律來說應該是文本的位置信息,但是他的x,y 並不是一般的理解思路, 首先y 的確定上跟baseline 有關,不清楚的可以百度一下,這個就不解釋了,x 和設置的文字的居中方式有關,如果設置了paint.setTextAlign(Paint.Align.CENTER),則x 的起點默認是字符的中心位置,默認是最左側。
所以上面的y 的計算其實是計算baseline ,x的位置參考下圖: