今天来一起学习一下最简单的自定义view,自己动手写一个MyTextView,当然不会像系统的TextView那么复杂,只是实现一下TextView的简单功能,包括分行显示及自定义属性的处理,主要目的是介绍自定义view的实现的基本思路和需要掌握的一些基础知识。
《一》先展示一下实现的最终效果
《二》实现步骤分析
1、创建MyTextView extends View,重写构造方法。一般是重写前三个构造方法,让前两个构造方法最终调用三个参数的构造方法,然后在第三个构造方法中进行一些初始化操作。
2、在构造方法中进行一些初始化操作,如初始化画笔及获取自定义属性等。
如何自定义属性?
(1)在values下创建attrs.xml.
//定义你的view可以在布局文件中配置的自定义属性
<declare-styleable name="MyTextViewApprence">
<attr name="textColor" format="color"></attr>
<attr name="textSize" format="dimension"></attr>
<attr name="text" format="string"></attr>
<attr name="showMode">
<enum name="left" value="0" />
<enum name="center" value="1" />
</attr>
</declare-styleable>
(2)获取自定义属性
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyTextViewApprence, defStyleAttr, 0);
mText = typedArray.getString(R.styleable.MyTextViewApprence_text);
mTextColor = typedArray.getColor(R.styleable.MyTextViewApprence_textColor, Color.BLACK);
mTextSize = (int) typedArray.getDimension(R.styleable.MyTextViewApprence_textSize, 15);
showMode = typedArray.getInt(R.styleable.MyTextViewApprence_showMode, 0);
typedArray.recycle();
3、重写OnDraw()方法,在onDraw()中使用carvas绘制文字,x,y为绘制的起点。
需要注意两点:
(1)这里的x,y不是指的左上顶点,而是左下顶点。
(2)drawText绘制文字时,是有规则的,这个规则就是基线!详细可阅读drawText()详解
//绘制每行文字的建议高度为:
Paint.FontMetrics fm = mPaint.getFontMetrics();
drawTextHeight = (int) (fm.descent - fm.ascent);
绘制文字的方法:
canvas.drawText(@NonNull String text, float x, float y, @NonNull Paint paint)
4、到第三步,其实就可以绘制出文字了,但是会发现一个问题,无论在布局文件中声明控件的宽高是wrap_content和match_parent,效果都是铺满了整个屏幕,这个时候,我们就需要重写onMesure()方法来测量控件的实际大小了,分析View的源码:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
/**MeasureSpec 封装了父控件对其孩子的布局要求
有大小和模式两种,而模式则有三种模式
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
//父控件不强加任何约束给子控件,它可以为它逍遥的任何大小
public static final int UNSPECIFIED = 0 << MODE_SHIFT; //0
//父控件给子控件一个精确的值
public static final int EXACTLY = 1 << MODE_SHIFT; //1073741824
//父控件给子控件竟可能最大的值
public static final int AT_MOST = 2 << MODE_SHIFT; //-2147483648
//设定尺寸和模式创建的统一约束规范
public static int makeMeasureSpec(in