在公司很少用系统的控件,基本上都是自定义的控件,这些控件一般封装了一些特殊的功能,或者设计成特殊的样式,
所以自定网上找资料研究了一下,在原有的基础上加了些注释(向洪洋致敬):
第一步:自定义属性(自定义控件通常比较复杂,所以有时需要用户声明特殊的值,所以要自定义属性)
第二步:在构造方法中获取自己定义的属性(前提是你自己必须在布局中声明,否则将得不到值)
第三步:重写onMeasure()方法(根据系统已经分配的布局高宽,做自定义调整)
第四步:重写onDraw()方法 (canvas.draw**()方法,画出自定义的样式)
一,自定义属性:res/values/目录下新建attrs.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="titleText" format="string" />
<attr name="titleTextSize" format="dimension" />
<attr name="titleTextColor" format="color" />
<attr name="image" format="reference" />
<attr name="imageScaleType">
<enum name="fillXY" value="0" />
<enum name="center" value="1" />
</attr>
<declare-styleable name="CustomImageView">
<attr name="titleText" />
<attr name="titleTextSize" />
<attr name="titleTextColor" />
<attr name="image" />
<attr name="imageScaleType" />
</declare-styleable>
</resources>
<attr name="titleText" format="string" /> 是单个属性的声明,(单个声明的属性可以被“属性包“引用如上)
<declare-styleable name="CustomImageView"> 是自定义CustomImageView的所有属性,想当一个类(一个包),包含的都为其成员属性(是基本单位)
(包里面的属性包不能被其他属性包利用)。
上面的属性声明还可以如下:
<declare-styleable name="CustomImageView">
<pre name="code" class="html"> <attr name="titleText" format="string" />
<attr name="titleTextSize" format="dimension" />
<attr name="titleTextColor" format="color" />
<attr name="image" format="reference" />
<attr name="imageScaleType">
<enum name="fillXY" value="0" />
<enum name="center" value="1" />
</attr>
</declare-styleable>
二,从构造方法中获取自定义的属性的值,刚刚也说过它的前提是在布局中声明:
注意这里必须有代码:xmlns:zhy="http://schemas.android.com/apk/res/com.zhy.customview02"
xmlns:tools="http://schemas.android.com/tools" 我试过不加也没事
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:zhy="http://schemas.android.com/apk/res/com.zhy.customview02"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.zhy.customview02.view.CustomImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="10dp"
zhy:image="@drawable/ic_launcher"
zhy:imageScaleType="center"
zhy:titleText="hello andorid ! "
zhy:titleTextColor="#ff0000"
zhy:titleTextSize="30sp"
/>
<com.zhy.customview02.view.CustomImageView
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="10dp"
zhy:image="@drawable/ic_launcher"
zhy:imageScaleType="center"
zhy:titleText="helloworldwelcome"
zhy:titleTextColor="#00ff00"
zhy:titleTextSize="20sp" />
<com.zhy.customview02.view.CustomImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="10dp"
zhy:image="@drawable/xiaoxiong"
zhy:imageScaleType="center"
zhy:titleText="史上最萌"
zhy:titleTextColor="#ff0000"
zhy:titleTextSize="12sp" />
</LinearLayout>
接下来就是在构造方法中获取,自定义的属性:
public class CustomImageView extends View
{
/**
* 控件的宽
*/
private int mWidth;
/**
* 控件的高
*/
private int mHeight;
/**
* 控件中的图片
*/
private Bitmap mImage;
/**
* 图片的缩放模式
*/
private int mImageScale;
private static final int IMAGE_SCALE_FITXY = 0;//fitxy
private static final int IMAGE_SCALE_CENTER = 1;//center
/**
* 图片的介绍
*/
private String mTitle;
/**
* 字体的颜色
*/
private int mTextColor;
/**
* 字体的大小
*/
private int mTextSize;
//画笔对象
private Paint mPaint;
/**
* 对文本的约束
*/
private Rect mTextBound;
/**
* 控制整体布局
*/
private Rect rect;
public CustomImageView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public CustomImageView(Context context)
{
this(context, null);
}
/**
* 初始化所特有自定义类型
*
* @param context
* @param attrs
* @param defStyle
*/
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_titleTextColor:
mTextColor = a.getColor(attr, Color.BLACK);
break;
case R.styleable.CustomImageView_titleTextSize:
mTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
16, getResources().getDisplayMetrics()));
break;
}
}
Log.i("zhy", "mImage:"+mImage+";mImageScale:"+mImageScale+";mTextColor:"+mTextColor+";mTextSize:"+mTextSize+";mTitle:"+mTitle);
//使其可以被重复调用
a.recycle();
rect = new Rect();
mPaint = new Paint();
mTextBound = new Rect();
mPaint.setTextSize(mTextSize);
// 计算了描绘字体需要的范围
mPaint.getTextBounds(mTitle, 0, mTitle.length(), mTextBound);
}
三是重写onMeasure()方法:说到重写onMeasure方法,就必须讲一下MesureSpec.getMode(int spec)方法的返回值:
1, MeasureSpec.EXACTLY:
当我们将控件的layout_width或layout_height指定为具体数值时如andorid:layout_width="50dip",
或者为FILL_PARENT(Match_parent)时,都是控件大小已经确定的情况,
int size=MeasureSpec.getSize(int spec); 得到的都是精确尺寸值。
2, MeasureSpec.AT_MOST是最大尺寸,
当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,
此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,
int size=MeasureSpec.getSize(int spec);给出了父控件允许的最大尺寸。
3, MeasureSpec.UNSPECIFIED是未指定尺寸,这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式。*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 设置宽度
*/
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate
{
Log.e("xxx", "EXACTLY");
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(desire, specSize);
Log.e("xxx", "AT_MOST");
}
}
/***
* 设置高度
*/
specMode = MeasureSpec.getMode(heightMeasureSpec);
specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate
{
mHeight = specSize;
} else
{
int desire = getPaddingTop() + getPaddingBottom() + mImage.getHeight() + mTextBound.height();
if (specMode == MeasureSpec.AT_MOST)// wrap_content
{
mHeight = Math.min(desire, specSize);
}
}
setMeasuredDimension(mWidth, mHeight);
}
四重写onDraw方法:(这是重中之重):
@Override
protected void onDraw(Canvas 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(mColor);
mPaint.setStyle(Style.FILL);
if(mBound.width()>mWidth){
TextPaint tvPaint=new TextPaint(mPaint);
String msg=TextUtils.ellipsize(mText,
tvPaint,
(float)mWidth-getPaddingLeft()-getPaddingRight(),
TextUtils.TruncateAt.END).toString();
canvas.drawText(msg, getPaddingLeft(), mHeight-getPaddingBottom(), mPaint);
}else{
//正常情况下自定居中
canvas.drawText(mText, mWidth/2-mBound.width()*1.0f/2, mHeight-getPaddingBottom(), mPaint);
}
/*取消使用掉的高度*/
rect.bottom=rect.bottom-mBound.height();
//在属性中已经定义,0表示fitxy,1表示center
if ( imgType==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 - mBound.height()) / 2 - mImage.getHeight() / 2;
rect.bottom = (mHeight - mBound.height()) / 2 + mImage.getHeight() / 2;
canvas.drawBitmap(mImage, null, rect, mPaint);
}
}
}
显示的结果有三种情况:
1、字体的宽度大于图片,且View宽度设置为wrap_content
2、View宽度设置为精确值,字体的长度大于此宽度
3、图片的宽度大于字体,且View宽度设置为wrap_content