经过上一篇的介绍,大家对于自定义View一定有了一定的认识,接下来我们就以实现一个图片下显示文字的自定义View来练习一下。废话不多说,下面进入我们的正题,首先看一下我们的思路,1、我们需要通过在values文件夹下添加一个attrs的文件,里面设置我们的自定义属性;2、通过重写View类,来获得我们设置的自定义属性的参数,并进行绘制;3、在我们的视图文件中进行引用。好了到这里我们的基本思路就已经形成,下面我们开始进行我们的实战编码操作。
第一步:在res目录下,values文件夹下,新建一个attrs.xml文件:
<?xml version="1.0" encoding="utf-8"?> <resources> <attr name="TitleText" format="string" /> <attr name="TitleColor" format="color" /> <attr name="TitleSize" format="dimension" /> <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="TitleColor" /> <attr name="TitleSize" /> <attr name="image" /> <attr name="imageScaleType" /> </declare-styleable> </resources>
提示一下:format对应的是该参数的值类型
第二步:重写我们的View类:
public class MySelfImageView extends View { /* * 图片区域 */ Rect imageRect; /* * 文字区域 */ Rect titleRect; /* * 画笔对象 */ Paint mPaint; /* * 图片标题文字 */ String titleText; /* * 图片标题文字颜色 */ int titleColor; /* * 图片标题文字大小 */ int titleSize; /* * 图片资源 */ Bitmap image; /* * 图片资源显示样式 */ int imageFillXY; int mWidth = 0; int mHeight = 0; public MySelfImageView(Context context) { this(context, null); } public MySelfImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MySelfImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //获取自定义设置的属性 TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomImageView, defStyleAttr, 0); int n = typedArray.getIndexCount(); for(int i=0; i<n; i++){ int att = typedArray.getIndex(i); //分别取出自定义属性设置的值 switch (att) { case R.styleable.CustomImageView_TitleText: titleText = typedArray.getString(att); break; case R.styleable.CustomImageView_TitleColor: titleColor = typedArray.getColor(att, Color.RED); break; case R.styleable.CustomImageView_TitleSize: titleSize = typedArray.getDimensionPixelSize(att, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics())); break; case R.styleable.CustomImageView_image: image = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(att, 0)); break; case R.styleable.CustomImageView_imageScaleType: imageFillXY = typedArray.getInt(att, 0); break; } } typedArray.recycle(); imageRect = new Rect(); mPaint = new Paint(); titleRect = new Rect(); mPaint.setTextSize(titleSize); // 计算描绘字体需要的范围 mPaint.getTextBounds(titleText, 0, titleText.length(), titleRect); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { /** * 设置宽度 */ int specMode = MeasureSpec.getMode(widthMeasureSpec); int specSize = MeasureSpec.getSize(widthMeasureSpec); if(specMode == MeasureSpec.EXACTLY){ mWidth = specSize; }else{ // 由图片决定的宽 int desireByImg = getPaddingLeft() + getPaddingRight() + image.getWidth(); // 由字体决定的宽 int desireByTitle = getPaddingLeft() + getPaddingRight() + titleRect.width(); 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() + image.getHeight() + titleRect.height(); if (specMode == MeasureSpec.AT_MOST){// wrap_content mHeight = Math.min(desire, specSize); } } setMeasuredDimension(mWidth, mHeight); } @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); imageRect.left = getPaddingLeft(); imageRect.right = mWidth - getPaddingRight(); imageRect.top = getPaddingTop(); imageRect.bottom = mHeight - getPaddingBottom(); mPaint.setColor(titleColor); mPaint.setStyle(Style.FILL); /** * 当前设置的宽度小于字体需要的宽度,将字体改为xxx... */ if (titleRect.width() > mWidth) { TextPaint paint = new TextPaint(mPaint); String msg = TextUtils.ellipsize(titleText, paint, (float) mWidth - getPaddingLeft() - getPaddingRight(), TextUtils.TruncateAt.END).toString(); canvas.drawText(msg, getPaddingLeft(), mHeight - getPaddingBottom(), mPaint); } else { //正常情况,将字体居中 canvas.drawText(titleText, mWidth / 2 - titleRect.width() * 1.0f / 2, mHeight - getPaddingBottom(), mPaint); } //取消使用掉的块 imageRect.bottom -= titleRect.height(); if (imageFillXY == 0) { canvas.drawBitmap(image, null, imageRect, mPaint); } else { //计算居中的矩形范围 imageRect.left = mWidth / 2 - image.getWidth() / 2; imageRect.right = mWidth / 2 + image.getWidth() / 2; imageRect.top = (mHeight - titleRect.height()) / 2 - image.getHeight() / 2; imageRect.bottom = (mHeight - titleRect.height()) / 2 + image.getHeight() / 2; canvas.drawBitmap(image, null, imageRect, mPaint); } } }
第三步:布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:zhy="http://schemas.android.com/apk/res/com.example.myselfview" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.example.myselfview.view.MySelfImageView 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:TitleColor="#ff0000" zhy:TitleSize="30sp" /> <com.example.myselfview.view.MySelfImageView 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:TitleColor="#00ff00" zhy:TitleSize="20sp" /> <com.example.myselfview.view.MySelfImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dp" android:padding="10dp" zhy:image="@drawable/im" zhy:imageScaleType="center" zhy:TitleText="山水美景" zhy:TitleColor="#ff0000" zhy:TitleSize="12sp" /> </LinearLayout>
最后效果图:
相对第一、三步,第二步相对更复杂一些,下面我就对第二步里面的具体内容进行一下解析:
//获取自定义设置的属性 TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomImageView, defStyleAttr, 0); int n = typedArray.getIndexCount(); for(int i=0; i<n; i++){ int att = typedArray.getIndex(i); //分别取出自定义属性设置的值 switch (att) { case R.styleable.CustomImageView_TitleText: titleText = typedArray.getString(att); break; case R.styleable.CustomImageView_TitleColor: titleColor = typedArray.getColor(att, Color.RED); break; case R.styleable.CustomImageView_TitleSize: titleSize = typedArray.getDimensionPixelSize(att, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics())); break; case R.styleable.CustomImageView_image: image = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(att, 0)); break; case R.styleable.CustomImageView_imageScaleType: imageFillXY = typedArray.getInt(att, 0); break; } } typedArray.recycle();
作用是获取我们在attrs中设置的自定义参数,在布局文件中进行的赋值。
onMeasure()方法用于设置控件的宽度,控件的长宽值如何取得呢?下面我们就来进行一下解析:
最后是我们绘图方法onDraw(Canvas canvas):
@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); imageRect.left = getPaddingLeft();//获取图片左上角坐标 imageRect.right = mWidth - getPaddingRight();//获取图片右上角坐标 imageRect.top = getPaddingTop();//获取图片距控件顶部距离 imageRect.bottom = mHeight - getPaddingBottom();//确定图片底部坐标 mPaint.setColor(titleColor);//设置文本的颜色 mPaint.setStyle(Style.FILL);//设置文本内容的填充方式 /** * 当前设置的宽度小于字体需要的宽度,将字体改为xxx... */ if (titleRect.width() > mWidth) { TextPaint paint = new TextPaint(mPaint); String msg = TextUtils.ellipsize(titleText, paint, (float) mWidth - getPaddingLeft() - getPaddingRight(), TextUtils.TruncateAt.END).toString(); canvas.drawText(msg, getPaddingLeft(), mHeight - getPaddingBottom(), mPaint); } else { //正常情况,将字体居中 canvas.drawText(titleText, mWidth / 2 - titleRect.width() * 1.0f / 2, mHeight - getPaddingBottom(), mPaint); } //取消使用掉的块 imageRect.bottom -= titleRect.height();//因为文字内容占用一定的高度,所以图片的底部坐标需要上移。 if (imageFillXY == 0) { canvas.drawBitmap(image, null, imageRect, mPaint); } else { //计算居中的矩形范围 imageRect.left = mWidth / 2 - image.getWidth() / 2; imageRect.right = mWidth / 2 + image.getWidth() / 2; imageRect.top = (mHeight - titleRect.height()) / 2 - image.getHeight() / 2; imageRect.bottom = (mHeight - titleRect.height()) / 2 + image.getHeight() / 2; canvas.drawBitmap(image, null, imageRect, mPaint); } }
好了,到这里关于自定义View的初步学习就介绍完毕,如果你有更好的关于自定义View的文章,欢迎留言交流。