自定义View的步骤:
- 自定义VIew的属性
- 在VIew的构造方法中获得我们的属性
- 重写OnMeasure方法
- 重写OnDraw方法
效果如下:
自定义属性
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>
构造我们的自定义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:
//转换sp为dp
mTextSize = a.getDimensionPixelSize(attr, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
16,getResources().getDisplayMetrics()));
break;
}
}
/*
* 在TypedArray后调用recycle主要是为了缓存。当recycle被调用后,这就说明这个对象从现在可以被重用了。TypedArray 内部持有部分数组,它们缓存在Resources类中的静态字段中,这样就不用每次使用前都需要分配内存。
* */
a.recycle();
mPaint = new Paint();
rect = new Rect();
mTextBound = new Rect();
mPaint.setTextSize(mTextSize);
//获得TextView的宽度和高度
//计算文字所在矩形,可以得到宽高
mPaint.getTextBounds(mTitle, 0, mTitle.length(), mTextBound);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/*
* 顾名思义,通过measureSpec这个参数,获取size ,两个都是int类型,怎么通过一个int类型的数获取另一个int类型的数。
* 我们在学习java的时候知道,一个int类型是32位,任何int类型的数都是有32位,比如一个int类型的数值3,它也是占有32位,只是高30位全部为0。
* google 也是利用这一点,让这个int类型的measureSpec数存了两个信息,一个就是size,保存在int类型的低30位,另一个就是mode,保存在int类型的高2位。
* 前面我们看到了有几个成员变量,UNSPECIFIED,EXACTLY,AT_MOST
者就是mode的三种选择,目前也只有这三种选择,所以只需要2位就能实现。
* */
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
/*
* 这也好理解,获取模式,但这些模式有啥用处呢?
* 1)、EXACTLY 模式: 准确的、精确的;这种模式,是最容易理解和处理的,可以理解为大小固定,比如在定义layout_width的时候,定义为固定大小 10dp,20dp,或者match_parent(此时父控件是固定的)这时候,获取出来的mode就是EXACTLY
* 2)、AT_MOST 模式: 最大的;这种模式稍微难处理些,不过也好理解,就是View的大小最大不能超过父控件,超过了,取父控件的大小,没有,则取自身大小,这种情况一般都是在layout_width设为warp_content时。
* 3)、UNSPECIFIED 模式:不指定大小,这种情况,我们几乎用不上,它是什么意思呢,就是View的大小想要多大,就给多大,不受父View的限制,几个例子就好理解了,ScrollView控件就是。
* */
////重点来了,判断模式,这个模式哪里来的呢,就是在编写xml的时候,设置的layout_width
//如果是精确的,好说,是多少,就给多少;
if (specMode == MeasureSpec.EXACTLY){
mWidth = specSize;
} else {
//由图片决定的宽
int desireByImg = getPaddingLeft() + getPaddingRight() + mImage.getWidth();
//由字体决定的宽
int desireByTitle = getPaddingLeft() + getPaddingRight() + mTextBound.width();
//如果是AT_MOST,不能超过父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);
//设置边框
mPaint.setStrokeWidth(4);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.CYAN);
//画布
canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaint);
//获取坐标
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);
// 根据长度截取出剪裁后的文字
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 {
//计算居中的矩形范围
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"
/>
在导入自定义View的时候遇到一个坑,因为如果我设置<attr name="titleTextColor" format="reference"/> 的时候,会报
Error:(133) Attribute "titleTextColor" has already been defined
错,这时由于系统中已经有这个属性,我们尽量要避免与之重名。