View 是 Android 中各种组件的基类,我们经常用到的 TextView,ImageView,Button 等都是继承自View。
当我们想使用的 View 在 Android 库中无法找到时候,我们就需要自己写一个类继承 View,重写里面的方法,自己绘制出想要的View。
(1)自定义 View 第一步首先是创建一个类并继承 View,
public MyTextView001(Context context) {
super(context);
}
public MyTextView001(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyTextView001(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
一般我们会重写上面的三个构造方法,第一个构造方法用于在代码中创建此 View 时使用
MyTextView001 view001=new MyTextView001(this);
第二个和第三个构造函数都是用于在布局中使用此 View 时调用,不同在于方法三在有自定义属性时使用。
(2)第二步是要重写View中的两个重要方法,
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
onMeasure 方法适用于自定义 View 的测量,onDraw 方法适用于自定义 View 的绘制。在 onMeasure 方法中可以通过
int width_mode = MeasureSpec.getMode(widthMeasureSpec);
int height_mode = MeasureSpec.getMode(heightMeasureSpec);
获取当前 View 的宽高模式,宽高模式具体有 UNSPECIFIED, EXACTLY, AT_MOST 三种:
UNSPECIFIED :为最不常用的一种,具体含义为尽可能的大,如果看过 ListView 源码的同学可以在其中找到此模式的使用;
EXACTLY :则是在布局中指定了确切的值或者是 match_parent, fill_parent;
AT_MOST :在布局中指定了 wrap_content。
onDraw 方法则是用于View的绘制过程,可以使用 canvas.drawCircle(),canvas.drawText() 等方法进行绘制。
(3)大概讲了下基本方法及构造函数,更多的内容让我们在实战自定义 TextView 中去学习吧。
首先在 values 文件夹下创建 attrs 文件,
在其中定义自定义属性,declare-styleable 的 name 属性使用自定义 View 的类名,attr 标签则是自定义属性的具体属性。我们这里设置了三个属性,分别是内容 text01,内容的颜色 text_color01,以及字体大小 text_size01。
新建好属性资源文件后,我们需要在自定义 View 中去获取这些属性,并创建画笔对象,
TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.MyTextView001);
mTextSize = (int) typedArray.getDimension(R.styleable.MyTextView001_text_size01,12);
mText = typedArray.getString(R.styleable.MyTextView001_text01);
mText_color = typedArray.getColor(R.styleable.MyTextView001_text_color01, Color.BLACK);
mPaint = new Paint();
mPaint.setTextSize(mTextSize);
mPaint.setColor(mText_color);
//抗锯齿
mPaint.setAntiAlias(true);
接着我们需要在 onMeasure 方法中进行 View 的测量,
//获取宽高模式
int width_mode = MeasureSpec.getMode(widthMeasureSpec);
int height_mode = MeasureSpec.getMode(heightMeasureSpec);
//获取宽度
int width = MeasureSpec.getSize(widthMeasureSpec);
if (width_mode == MeasureSpec.AT_MOST) {
//如果使用 wrap_content 模式,则重新计算宽度
Rect rect = new Rect();
mPaint.getTextBounds(mText, 0, mText.length(), rect);
width = rect.width();
}
//获取高度
int height = MeasureSpec.getSize(heightMeasureSpec);
if (height_mode == MeasureSpec.AT_MOST) {
//如果使用 wrap_content 模式,则重新计算高度
Rect rect = new Rect();
mPaint.getTextBounds(mText, 0, mText.length(), rect);
height = rect.height();
}
//将最后的宽高赋值
setMeasuredDimension(width, height);
接着在 onDraw 方法中进行绘制,绘制之前先讲一个概念,
baseline 是基线,在 Android 中绘制文本都是从 baseline 处开始的,从 baseline 往上至至文本最高处的距离称之为 ascent (上坡度),baseline 至文本最低处的距离称之为 descent (下坡度)。top 和 bottom 是绘制文本时在最外层留出的一些内边距。baseline 是基线,baseline 以上是负值,baseline 以下是正值,因此 ascent 和 top 都是负值,descent 和 bottom 都是正值。文本的实际高度应该就是 descent-asscent, 但是一般都是以 top-bottom 作为文本的高度。
Paint.FontMetricsInt fontMetricsInt = mPaint.getFontMetricsInt();
//具体计算 baseLine
int dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
canvas.drawText(mText1, 0, getHeight() / 2 + dy, mPaint);
onDraw 方法也完成后,就在布局中使用这个自定义 View
然后看一下效果
但是我们发现一个问题,就是内容贴边太近了,接下来我们来个她设置个 padding 值。将布局中代码修改如下:
将 onMeasure 方法中计算宽高的方法修改如下:
width = rect.width()+getPaddingLeft()+getPaddingRight();
height = rect.height()+getPaddingTop()+getPaddingBottom();
最后修改 onDraw 方法中的绘制部分:
canvas.drawText(mText, getPaddingLeft(), getHeight() / 2 + dy, mPaint);
修改完成后,我们再来看下效果:
这样看起来就舒服多啦!
自定义 View 的入门篇就讲到这里了,源码可见https://github.com/keven0632/MyView201805