定义自View学的啥的英文无非就两种:绘制文字和绘制图像
通过上篇的学习,了解到Paint类中有很多最方法关于属性设置的方法。本篇就记录我学习绘制文字的过程。

学习资料:
- 爱哥写得非常非常好的系列:自定义控件其实很简单1/4
- 涂料的源码API
1.简单效果

代码:
public class DrawTextView extends View {
    private Paint mPaint ;
    private String text = "英勇青铜5+abcdefg";
    public DrawTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }
    /**
     * 初始化画笔设置
     */
    private void initPaint() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.parseColor("#FF4081"));
        mPaint.setTextSize(90f);
    }
    /**
     * 绘制
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText(text, 0 ,getHeight()/2 ,mPaint);
    }
    /**
     * 测量
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(300,300);
        }else  if (wSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(300,hSpecSize);
        }else if (hSpecMode ==  MeasureSpec.AT_MOST) {
            setMeasuredDimension(wSpecSize,300);
        }
    }
}
代码比较简单。
再看效果图,文字此时的y坐标虽然设置了getHeight()/2,但很明显,文字的所处y轴的位置不是控件的高的一半。很简单,文字本身也有高度,在绘制的时候,计算坐标并没有考虑文字本身的宽高。
现在首先解决的需求,让就是文字在这个自定义的DrawTextView控件中居中
2.在X轴居中

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //拿到字符串的宽度
    float stringWidth = mPaint.measureText(text);
    float x =(getWidth()-stringWidth)/2;
    canvas.drawText(text, x ,getHeight()/2 ,mPaint);
}
利用measureText(String text)这个方法,很容易拿到要绘制文字的宽度,根据再(getWidth()-stringWidth)/2简单计算,可以就在得到X轴起始绘制坐标
源码中,measureText(String text)调用了measureText(text, 0, text.length())
- measureText(String text, int start, int end)
文本要测量的文本。不能为null。
start开始测量
结束1的第一个字符的索引超过要测量的最后一个字符的索引
方法中传入字符串,并指定开始测量角标和结束角标,查询查询结果报道查看为float型的值
2.在ÿ轴居中
在想要Y轴居中,确定就要出绘制文字baseline时的所在Y轴的坐标。
在Android中,文字状语从句:相关高度的信息都存在FontMetrics对象中..
2.1 FontMetrics字体度量
FontMetrics的英文Paint的一个静态内部类
    /**
     * Class that describes the various metrics for a font at a given text size.
     * Remember, Y values increase going down, so those values will be positive,
     * and values that measure distances going up will be negative. This class
     * is returned by getFontMetrics().
     */
    public static class FontMetrics {
        /**
         * The maximum distance above the baseline for the tallest glyph in
         * the font at a given text size.
         */
        public float   top;
        /**
         * The recommended distance above the baseline for singled spaced text.
         */
        public float   ascent;
        /**
         * The recommended distance below the baseline for singled spaced text.
         */
        public float   descent;
        /**
         * The maximum distance below the baseline for the lowest glyph in
         * the font at a given text size.
         */
        public float   bottom;
        /**
         * The recommended additional space to add between lines of text.
         */
        public float   leading;
    }

在FontMetrics有五个float类型值:
- 
leading留给文字音标符号的距离
- 
ascent从baseline线到最高的字母顶点到距离,负值
- 
top从baseline线到字母最高点的距离加上ascent
- 
descent从baseline线到字母最低点到距离
- 
bottom状语从句:top类似,系统为一些极少数符号留下的空间。top状语从句:bottom总会比ascent状语从句:descent大一点一的就是这些少到忽略的特殊符号
baseline上为负,下为正。理解可以文字为系坐标中的x轴,实际的日志打印的值

文字绘制的的英文从baseline开始的
2.2文字确定Y轴的坐标

文字由于绘制的英文从baseline开始,想要所以文字的正中心状语从句:DrawTextView的中心重合,baseline就不能状语从句:getHeight()/2重合,而且baseline要在getHeight()/2下方。
但要在下方多少?就是2号线和3号线之间的距离。
|ascent|= descent+ 2 *(2号线和3号线之间的距离)
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //文字的x轴坐标
    float stringWidth = mPaint.measureText(text);
    float x = (getWidth() - stringWidth) / 2;
    //文字的y轴坐标
    Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
    float y = getHeight() / 2 + (Math.abs(fontMetrics.ascent) - fontMetrics.descent) / 2;
    canvas.drawText(text, x, y, mPaint);
}

这里只需要自己在画图板自己画一次,就差不多可以理解。
文字在中心需求已经完成。
3.其他的方法
3.1 setTextAlign(Align align)设置对齐方式
- Paint.Align.LEFT左对齐
- Paint.Align.CENTER中心对齐,绘制从
- Paint.Align.RIGHT右对齐
这个方法影响的是两端的绘制起始。LEFT就是从左端开始,所以使用这三个属性时,在drawText(test,x,y,paint);要注意x坐标,否则,绘制会出现错乱
LEFT对应0,CENTER对应getWidth()/2,RIGHT对应getWidth()
3.2 setTypeface(Typeface typeface)设置字体
系统提供了五种字体DEFAULT:DEFAULT_BOLD,SANS_SERIF,SERIF, ,MONOSPACE,除了粗体,没看出有太大区别
这个对象可以用来加载自定义的字体
- 
Typeface createFromAsset(AssetManager mgr, String path)从assets资源中加载字体
- 
Typeface createFromFile(String path)通过路径加载字体文件
- 
Typeface createFromFile(File file)通过指定文件加载字体
可以也。通过Typefacecreate(Typeface family, int style)方法拿到字体样式
Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
textPaint.setTypeface( font );
- Typeface.NORMAL默认
- Typeface.BOLD粗体
- Typeface.ITALIC斜体
- Typeface.BOLD_ITALIC粗斜体
3.3 setStyle设置画笔样式
- 
Paint.Style.FILL实心充满
 ![]() 填 填
- 
Paint.Style.STROKE
 ![]() 行程 行程
- 
Paint.Style.FILL_AND_STROKE暂时这个没发现状语从句:FILL有啥不同
3.4 setFlags(int flags)设置画笔的flag
- 
ANTI_ALIAS_FLAG抗锯齿
- 
DITHER_FLAG防抖动
其他还有一堆,试了试,没看出太大区别。常见的就是抗锯齿,遇到特殊的需求,再来深入了解
可以也。在直接Paint的构造方法中指定
3.5 PathEffect setPathEffect(PathEffect effect)设置路径效果
<P> 
这个方法感觉不应该放在本篇,应该算作图像。不过,代码写好了,也就放在这了。

public class DrawTextView extends View {
   
    private Paint textPaint;
    private Paint pathPaint;
    private Path mPath;
    private String[] pathEffectName = {
            "默认", "CornerPathEffect", "DashPathEffect", "PathDashPathEffect",
            "SumPathEffect", "DiscretePathEffect", "ComposePathEffect"
    };
    private PathEffect[] mPathEffect;
    public DrawTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }
    /**
     * 初始化画笔设置
     */
    private void initPaint() {
        //文字画笔
        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setColor(Color.WHITE);
        textPaint.setTextSize(40f);
        //路径画笔
        pathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        pathPaint.setColor(Color.parseColor("#FF4081"));
        pathPaint.setStrokeWidth(5F);
        pathPaint.setStyle(Paint.Style.STROKE);
        mPath = new Path();
        //设置起点
        mPath.moveTo(0, 0);
        //路径连接的点
        for (int i = 1; i < 37; i++) {
            mPath.lineTo(i * 30, (float) (Math.random() * 100));
        }
        //初始化PathEffect
        mPathEffect = new PathEffect[7];
        mPathEffect[0] = new PathEffect();//默认
        mPathEffect[1] = new CornerPathEffect(10f);
        mPathEffect[2] = new DashPathEffect(new float[]{10f, 5f, 20f, 15f},10);
        mPathEffect[3] = new PathDashPathEffect(new Path(), 10, 10f, PathDashPathEffect.Style.ROTATE);
        mPathEffect[4] = new SumPathEffect(mPathEffect[1], mPathEffect[2]);
        mPathEffect[5] = new DiscretePathEffect(5f, 10f);
        mPathEffect[6] = new ComposePathEffect(mPathEffect[3], mPathEffect[5]);
        
    }
    /**
     * 绘制路径
     *
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0; i < mPathEffect.length; i++) {
            pathPaint.setPathEffect(mPathEffect[i]);
            canvas.drawPath(mPath,pathPaint);
            canvas.drawText(pathEffectName[i], 0, 130, textPaint);//每个画布的最上面,就是y轴的0点
            // 每绘制一条将画布向下平移180个像素
            canvas.translate(0, 180);//控件的高度要足够大才能平移
        }
        invalidate();//绘制刷新
    }
    /**
     * 测量
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(300, 300);
        } else if (wSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(300, hSpecSize);
        } else if (hSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(wSpecSize, 300);
        }
    }
}
这里借鉴了爱哥的思路,把7种效果都画了出来,还加上了名字。
需要注意的是,绘制路径时,pathPaint.setStyle(Paint.Style.STROKE)画笔的风格要空心,否则,后果画出的不是线,而是一个不规则的区域。
这7种路径效果,暂时还不能区分,先暂时知道有这么7种效果,等到实现具体需求了再深入了解
9月8号补充
- CornerPathEffect拐角处变圆滑
- DashPathEffect可以用来绘制虚线,用一个数组来设置各个点之间的间隔,- phase控制绘制时数组的偏移量
- PathDashPathEffect和- DashPathEffect类似,可以设置显示的点的图形,例如圆形的点
- DisCreatePathEffect线段上会有许多杂点
- ComposePathEffect组合两个- PathEffect,将两个组合成一个效果
从Android的群英传摘抄
3.6breakText(CharSequence text, int start, int end,boolean measureForwards,float maxWidth, float[] measuredWidth)
测量文本,如果测量的宽度超过maxWidth,则提前停止。
返回被测量的字符数,如果measuredWidth
不为空,则返回测量的实际宽度。
@参数文本要测量的文本。不能为null。
@param start在
@param end 开始测量的文本偏移量要测量的文本切片的结尾。
@param measureForwards如果为true,则从起始处开始向前测量。否则,向后测量,从结束处开始。
@param maxWidth要积累的最大宽度。
@参数measuredWidth可选。如果不为空,则返回测量的实际宽度。
@return测量的字符数。将始终为<= abs(结束 - 开始)。
这个方法,暂时没有测试出啥效果。
先引用爱哥的描述:
这个方法让我们设置一个最大宽度在不超过这个宽度的范围内返回实际测量值否则停止测量,参数很多但是都很好理解,文本表示我们的字符串,就表示从第几个字符串开始测量,端表示从测量到第几个字符串为止,measureForwards表示向前还是向后测量,maxWidth表示一个给定的最大宽度在这个宽度内能测量出几个字符,measuredWidth可以为一个可选项,可以为空,不为空时返回真实的测量值。同样的方法还有breakText(String text,boolean measureForward,float maxWidth,float [] measuredWidth)和breakText(char [] text,int index,int count,float maxWidth,float [ ] measuredWidth)。这些方法在一些结合文本处理的应用里比较常用,比如文本阅读器的翻页效果,我们需要在翻页的时候动态折断或生成一行字符串,这就派上用场了~~~
3.7其余
剩下的方法,试一下就晓得效果了
- 
setTextScaleX(float f)设置缩放,0F到1F为缩小,大于1F为放大
- 
setUnderlineText(booelan b)设置下划线
- 
setStrikeThruText (boolean strikeThruText)设置文本删除线
- 
setTextSize(float f)设置文字字体大小
- 
getFontSpacing()得到行间距
- 
descent()得到descent的值
- 
ascent()得到asccent的值
- 
getLetterSpacing()字母间距
关于字体的常用的方法差不多就这些了。漏掉的,用到了再补充。
4.最后
在于重点FontMetrics的学习。先用画图板画出来,个人感觉比较有助于记忆。然后,尝试用代码在程序中把各条线画出来。
学习下篇Paint类中关于画图像的属性方法。
作者:英勇青铜5
链接:https://www.jianshu.com/p/1728b725b4a6
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 
                    
                     
                    
                 
                    
                 
         


