最近在看何红辉老师的《Android开发进阶从小工到专家》,觉得挺不错的,对于想从初级安卓工程师走向高级工程师的人来说非常合适。现在想介绍下里面一个自定义View的入门例子,可以当做是笔记。
这是一个简单的自定义ImageView,功能是控件大小可以按照图片大小设置,也可以让图片按照控件大小设置。
先看下效果,对于同一张图片来说,当布局文件设置宽高为wrap_content时,效果如图:
当布局文件设置宽高为match_parent时,效果如图:
当布局文件设置宽高为match_parent时,效果如图:
实现这个自定义View的步骤如下:
1.在res文件夹中定义一个专门用于获取图片资源的属性。
2.创建一个类继承View,并初始化画笔和从布局属性中获取相应的图片。
3.判断布局文件赋给控件的尺寸规格种类结合图片大小确定控件大小。
4.绘制控件。
首先第一步在项目工程的res/value文件夹下创建attr文件,添加自定义属性:
属性集名称为MyImageView,只有一个属性src,因为要指向图片资源的id,所以类型为integer。
(对自定义属性不熟悉的朋友可以看鸿洋的博客Android 深入理解Android中的自定义属性)
OK,第一步轻松搞定。
第二步,创建一个类继承View,初始化画笔和获得图片:
public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
mBitmapPaint = new Paint();
mBitmapPaint.setAntiAlias(true);
initAttr(attrs);
}
private void initAttr(AttributeSet attrs) {
TypedArray array = null;
try {
//获得属性集合,并从属性集合中获得对应属性的资源
array = getContext().obtainStyledAttributes(attrs,R.styleable.MyImageView);
mDrawable = array.getDrawable(R.styleable.MyImageView_src);
measureDrawble();
}finally {
if(array != null){
array.recycle();
}
}
}
这一个步对于对自定义属性和Paint类使用熟悉的朋友都很简单吧。mDrawable就是我们想要显示的图片。
第三步,首先看到上面代码的measureDrawable(),它测量出图片的宽高:
private void measureDrawble() {
if(mDrawable != null){
mWidth = mDrawable.getIntrinsicWidth();
mHeight = mDrawable.getIntrinsicHeight();
}
}
mWidth和mheight是MyImageView的成员变量,记录控件的宽高。
然后重写onMeasure(),设置我们希望控件的大小。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
measuredWidth(widthMode,widthSize);
measureHeight(heightMode,heightSize);
setMeasuredDimension(mWidth,mHeight);
}
如果对于控件测量不熟悉的朋友可以看下郭霖老师的 Android自定义View的实现方法,带你一步步深入了解View(四)
重点看下measuredWidth和measuredHeight这两个方法:
private void measuredWidth(int mode,int size) {
switch (mode){
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
break;
case MeasureSpec.EXACTLY:
mWidth = size;
break;
}
}
当父View传给我们的控件的MeasureSpec的mode为EXACTLY时,即布局文件设置的宽属性为match_parent或者具体数值时,就将此时
MeasureSpec的size赋给mWidth,如果为AT_MOST(
布局文件设置的宽属性为wrap_content)或者UNSPECIFIED时则
mWidth为图片的宽。
measuredHeigh也是一样。
这样我们当布局文件设置控件的宽高为wrap_content时控件宽高为图片的宽高,设置设置控件的宽高为match_parent或者具体数值
时控件宽高为铺满所在ViewGroup或者布局文件中定义的具体数字。
setMeasuredDimension(mWidth,mHeight);
最终确定控件的测量值。
第四步,重写onDraw()绘制控件,老司机也是很熟悉了。
@Override
protected void onDraw(Canvas canvas) {
if(mDrawable != null){
if(mBitmap == null){
mBitmap = Bitmap.createScaledBitmap(ImageUtils.drawableToBitmap(mDrawable),getMeasuredWidth()
,getMeasuredHeight(),true);
}
canvas.drawBitmap(mBitmap,getLeft(),getTop(),mBitmapPaint);
// canvas.drawCircle(getLeft()-100,getTop()-100,mWidth,mBitmapPaint);
}
}
这里根据测量得到的宽高重新创建一个相应尺寸的Bitmap,这里需要用到将原来资源的Drawable转化为Bitmap,然后通过
Bitmap的createScaleBitmap创建。最后调用canvas和传入Paint画出Bitmap。
最后看下布局文件如何使用我们的自定义控件:
只要在你想要添加我们的控件的地方想添加普通控件一样添加就可以了。这里app:src正是我们的自定义属性,app是我们在布局
文件中定义的命名空间,它可以引用到自定义的属性。
xmlns:app="http://schemas.android.com/apk/res-auto"
在eclipse中就要将上面的res-auto替换为控件
所在的包名。
一个很简单的例子,不过作为自定义View的入门也是不错。