自定義View的步驟:
- 自定義VIew的屬性
- 在VIew的構(gòu)造方法中獲得我們的屬性
- 重寫(xiě)OnMeasure方法
- 重寫(xiě)OnDraw方法
效果如下:
device-2016-04-26-124048.png
自定義屬性
res/attr.xml
<resources>
<attr name="titleText" format="string"/>
<attr name="titleTextSizes" format="dimension"/>
<attr name="titleTextColors" format="reference"/>
<attr name="image" format="reference"/>
<attr name="imageScaleType" format="reference">
<enum name="fillXY" value="0"/>
<enum name="center" value="1"/>
</attr>
<declare-styleable name="CustomImageView">
<attr name="titleText"/>
<attr name="titleTextSizes"/>
<attr name="titleTextColors"/>
<attr name="image"/>
<attr name="imageScaleType"/>
</declare-styleable>
</resources>
構(gòu)造我們的自定義View
public class CustomImageView extends View{
private static int IMAGE_SCALE_FITXY = 0;
private Bitmap mImage;
private int mImageScale;
private String mTitle;
private int mTextColor;
private int mTextSize;
private Rect rect;
private Rect mTextBound;
private Paint mPaint;
private int mWidth;
private int mHeight;
public CustomImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs,defStyle);
//獲取自定義的屬性
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomImageView,defStyle,0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++){
int attr = a.getIndex(i);
switch (attr){
case R.styleable.CustomImageView_image:
mImage = BitmapFactory.decodeResource(getResources(),a.getResourceId(attr,0));
break;
case R.styleable.CustomImageView_imageScaleType:
mImageScale = a.getInt(attr,0);
break;
case R.styleable.CustomImageView_titleText:
mTitle = a.getString(attr);
break;
case R.styleable.CustomImageView_titleTextColors:
mTextColor = a.getColor(attr, Color.BLACK);
break;
case R.styleable.CustomImageView_titleTextSizes:
//轉(zhuǎn)換sp為dp
mTextSize = a.getDimensionPixelSize(attr, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
16,getResources().getDisplayMetrics()));
break;
}
}
/*
* 在TypedArray后調(diào)用recycle主要是為了緩存剔应。當(dāng)recycle被調(diào)用后要出,這就說(shuō)明這個(gè)對(duì)象從現(xiàn)在可以被重用了允蜈。TypedArray 內(nèi)部持有部分?jǐn)?shù)組,它們緩存在Resources類中的靜態(tài)字段中,這樣就不用每次使用前都需要分配內(nèi)存供屉。
* */
a.recycle();
mPaint = new Paint();
rect = new Rect();
mTextBound = new Rect();
mPaint.setTextSize(mTextSize);
//獲得TextView的寬度和高度
//計(jì)算文字所在矩形往产,可以得到寬高
mPaint.getTextBounds(mTitle, 0, mTitle.length(), mTextBound);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/*
* 顧名思義,通過(guò)measureSpec這個(gè)參數(shù)撒犀,獲取size 福压,兩個(gè)都是int類型,怎么通過(guò)一個(gè)int類型的數(shù)獲取另一個(gè)int類型的數(shù)或舞。
* 我們?cè)趯W(xué)習(xí)java的時(shí)候知道荆姆,一個(gè)int類型是32位,任何int類型的數(shù)都是有32位映凳,比如一個(gè)int類型的數(shù)值3胆筒,它也是占有32位,只是高30位全部為0诈豌。
* google 也是利用這一點(diǎn)仆救,讓這個(gè)int類型的measureSpec數(shù)存了兩個(gè)信息,一個(gè)就是size矫渔,保存在int類型的低30位彤蔽,另一個(gè)就是mode,保存在int類型的高2位庙洼。
* 前面我們看到了有幾個(gè)成員變量顿痪,UNSPECIFIED,EXACTLY油够,AT_MOST
者就是mode的三種選擇蚁袭,目前也只有這三種選擇,所以只需要2位就能實(shí)現(xiàn)叠聋。
* */
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
/*
* 這也好理解撕阎,獲取模式,但這些模式有啥用處呢碌补?
* 1)虏束、EXACTLY 模式: 準(zhǔn)確的、精確的厦章;這種模式镇匀,是最容易理解和處理的,可以理解為大小固定袜啃,比如在定義layout_width的時(shí)候汗侵,定義為固定大小 10dp,20dp,或者match_parent(此時(shí)父控件是固定的)這時(shí)候晰韵,獲取出來(lái)的mode就是EXACTLY
* 2)发乔、AT_MOST 模式: 最大的;這種模式稍微難處理些雪猪,不過(guò)也好理解栏尚,就是View的大小最大不能超過(guò)父控件,超過(guò)了只恨,取父控件的大小译仗,沒(méi)有,則取自身大小官觅,這種情況一般都是在layout_width設(shè)為warp_content時(shí)纵菌。
* 3)、UNSPECIFIED 模式:不指定大小休涤,這種情況咱圆,我們幾乎用不上,它是什么意思呢滑绒,就是View的大小想要多大闷堡,就給多大,不受父View的限制疑故,幾個(gè)例子就好理解了杠览,ScrollView控件就是。
* */
////重點(diǎn)來(lái)了纵势,判斷模式踱阿,這個(gè)模式哪里來(lái)的呢,就是在編寫(xiě)xml的時(shí)候钦铁,設(shè)置的layout_width
//如果是精確的软舌,好說(shuō),是多少牛曹,就給多少佛点;
if (specMode == MeasureSpec.EXACTLY){
mWidth = specSize;
} else {
//由圖片決定的寬
int desireByImg = getPaddingLeft() + getPaddingRight() + mImage.getWidth();
//由字體決定的寬
int desireByTitle = getPaddingLeft() + getPaddingRight() + mTextBound.width();
//如果是AT_MOST,不能超過(guò)父View的寬度
if (specMode == MeasureSpec.AT_MOST){ //WRAP_CONTENT
int desire = Math.max(desireByImg,desireByTitle);
mWidth = Math.min(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) {
//super.onDraw(canvas);
//設(shè)置邊框
mPaint.setStrokeWidth(4);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.CYAN);
//畫(huà)布
canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaint);
//獲取坐標(biāo)
rect.left = getPaddingLeft();
rect.right = mWidth - getPaddingRight();
rect.top = getPaddingTop();
rect.bottom = mHeight - getPaddingBottom();
mPaint.setColor(mTextColor);
mPaint.setStyle(Paint.Style.FILL);
if (mTextBound.width() > mWidth){
TextPaint paint = new TextPaint(mPaint);
// 根據(jù)長(zhǎng)度截取出剪裁后的文字
String msg = TextUtils.ellipsize(mTitle, paint, (float)mWidth - getPaddingLeft()
,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 {
//計(jì)算居中的矩形范圍
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);
}
}
}
在布局中引用
<com.riane.customimageview.CustomImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="10dp"
custom:image = "@mipmap/ic_launcher"
custom:imageScaleType="center"
custom:titleText="hello andorid ! "
custom:titleTextColors="@color/colorPrimary"
custom:titleTextSizes="30sp"
/>
<com.riane.customimageview.CustomImageView
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="10dp"
custom:image="@mipmap/ic_launcher"
custom:imageScaleType="center"
custom:titleText="helloworldwelcome"
custom:titleTextColors="@color/colorPrimary"
custom:titleTextSizes="20sp"
/>
<com.riane.customimageview.CustomImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="10dp"
custom:image="@mipmap/meizi"
custom:imageScaleType="center"
custom:titleText="妹子~"
custom:titleTextColors="@color/colorPrimary"
custom:titleTextSizes="12sp"
/>
在導(dǎo)入自定義View的時(shí)候遇到一個(gè)坑超营,因?yàn)槿绻以O(shè)置<attr name="titleTextColor" format="reference"/> 的時(shí)候,會(huì)報(bào)
Error:(133) Attribute "titleTextColor" has already been defined
錯(cuò)阅虫,這時(shí)由于系統(tǒng)中已經(jīng)有這個(gè)屬性演闭,我們盡量要避免與之重名。