自定义view是安卓开发的基础,本身没有太多的难点,需要考虑的知识点也不多,主要有canvas绘制相关知识、view控件的测量、自定义属性等,本案例通过一个展示文本的自定义控件,努力演示上述知识点,为自定义view打好基础。读懂这个案例需要你对自定义view有一定的基础,如果你对自定义view一无所知,建议自行查阅相关知识后再看这个案例,因为案例中没有详细的步骤介绍,只对核心的知识点做了说明和注释。
自定义的TextView类:
核心知识点已经注释,希望可以对你读懂代码有所帮助。
package hongzhen.com.defineviewdemo.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import hongzhen.com.defineviewdemo.R;
/**
* 自定义TextView控件,用于展示文本
* 实现自定义属性 src-文本内容 text_color-文本颜色 text_size-文本大小 bg_width-控件宽度 bg_height-控件高度
* 考虑padding属性
* 考虑style的设置
*/
public class TextView extends View {
private static final String TAG = "TextView";
private Paint paint;
private String mText = "";
private String textColor;
private float mHeight;
private float textSize;
private float mWidth;
private int paddingLeft;
private int paddingTop;
private int paddingRight;
private int paddingBottom;
public TextView(Context context) {
this(context, null);
}
public TextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FirstView,
defStyleAttr, R.style.myDefaultStyle);
mText = typedArray.getString(R.styleable.FirstView_src);
if (mText == null) {
mText = "";
}
textColor = typedArray.getString(R.styleable.FirstView_text_color);
mWidth = typedArray.getDimension(R.styleable.FirstView_bg_width, 100);
mHeight = typedArray.getDimension(R.styleable.FirstView_bg_height, 50);
textSize = typedArray.getDimension(R.styleable.FirstView_text_size, 50);
Log.i(TAG, "textColor-" + textColor);
Log.i(TAG, "textSize-" + textSize);
Log.i(TAG, "mWidth-" + mWidth);
Log.i(TAG, "mHeight-" + mHeight);
typedArray.recycle();
//考虑自定义view设置的padding属性,由于layout_margin相关属性是viewgroup负责的,因此只有自定义
//viewgroup时才需要考虑其子view的layout_margin属性生效,view无需考虑layout_margin属性
paddingLeft = getPaddingLeft();
paddingTop = getPaddingTop();
paddingRight = getPaddingRight();
paddingBottom = getPaddingBottom();
init();
}
/**
* 该方法defStyleRes参数需要API-21以上才支持,因此不建议重写该方法和在该方法进行初始化操作
*
* @param context
* @param attrs
* @param defStyleAttr
* @param defStyleRes
*/
public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
/**
* 初始化画笔参数
*/
private void init() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
//设置画笔大小,也就是文本的大小
paint.setTextSize(textSize);
//设置画笔的颜色,也就是文本的颜色
paint.setColor(getColorFromXML(textColor));
}
@Override
protected void onDraw(Canvas canvas) {
/* Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.GRAY);
paint.setTextSize(100);
setLayerType(View.LAYER_TYPE_SOFTWARE, paint);
paint.setShadowLayer(10, 6, 6, Color.RED);
canvas.drawText("Android Studio", 20, 120, paint);*/
//将文字放在正中间
Rect textRect = this.getTextRect();
int viewWidth = getMeasuredWidth();
int viewHeight = getMeasuredHeight();
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
int x = (viewWidth - textRect.width()) / 2;
int y = (int) (viewHeight / 2 +
(fontMetrics.descent - fontMetrics.ascent) / 2
- fontMetrics.descent);
//x,y坐标点,大概是文本左下角的位置
canvas.drawText(mText, x, y, paint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取根据文本生成的宽、高,进而确定控件需要的宽高
Rect rect = getTextRect();
int textWidth = rect.width();
int textHeight = rect.height();
int width = measureWidth(widthMeasureSpec, textWidth);
int height = measureHeight(heightMeasureSpec, textHeight);
setMeasuredDimension(width, height);
}
/**
* 获取文字所占的尺寸,矩形区域
*
* @return
*/
private Rect getTextRect() {
//根据 Paint 设置的绘制参数计算文字所占的宽度
Rect rect = new Rect();
//文字所占的区域大小保存在 rect 中
if (mText != null) {
paint.getTextBounds(mText, 0, mText.length(), rect);
}
return rect;
}
/**
* 测量组件宽度
*
* @param widthMeasureSpec
* @param textWidth 文字所占宽度
* @return
*/
private int measureWidth(int widthMeasureSpec, int textWidth) {
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
int width = 0;
if (mode == MeasureSpec.EXACTLY) {
//宽度为 match_parent 和具体值时,直接将 size 作为组件的宽度
width = size;
} else if (mode == MeasureSpec.AT_MOST) {
//宽度为 wrap_content,宽度需要计算, 此处为文字宽度
if (textWidth > mWidth) {
//设置的宽度小于文本宽度,则取文本宽度
//考虑padding的值
width = textWidth + paddingLeft + paddingRight;
} else {
width = (int) mWidth;
}
}
return width;
}
/**
* 测量组件高度
*
* @param heightMeasureSpec
* @param textHeight 文字所占高度
* @return
*/
private int measureHeight(int heightMeasureSpec, int textHeight) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
int height = 0;
if (mode == MeasureSpec.EXACTLY) {
//宽度为 match_parent 和具体值时,直接将 size 作为组件的高度
height = size;
} else if (mode == MeasureSpec.AT_MOST) {
//高度为 wrap_content,高度需要计算, 此处为文字高度
//考虑padding的值
height = textHeight + paddingTop + paddingBottom;
}
return height;
}
/**
* 将xml页面设置的颜色字符串,转换为int的颜色,#fff;#ffff;#ffffff;#ffffffff.四种格式转换为int颜色
*
* @param src
* @return
*/
private int getColorFromXML(String src) {
if (TextUtils.isEmpty(src)) {
return Color.BLACK;
} else {
if (src.contains("#")) {
src = src.replace("#", "");
StringBuffer result = new StringBuffer();
if (src.length() == 3) {
char[] chars = src.toCharArray();
result.append("#");
result.append("ff");
for (int i = 0; i < chars.length; i++) {
result.append(chars[i]);
result.append(chars[i]);
}
return Color.parseColor(result.toString());
} else if (src.length() == 4) {
char[] chars = src.toCharArray();
result.append("#");
for (int i = 0; i < chars.length; i++) {
result.append(chars[i]);
result.append(chars[i]);
}
return Color.parseColor(result.toString());
} else if (src.length() == 6) {
result.append("#");
result.append("ff");
result.append(src);
return Color.parseColor(result.toString());
} else if (src.length() == 8) {
result.append("#");
result.append(src);
return Color.parseColor(result.toString());
} else {
return Color.BLACK;
}
} else {
return Color.BLACK;
}
}
}
}
布局文件:
attrs.xml文件代码:
style.xml文件代码: