array(2) { ["docs"]=> array(10) { [0]=> array(10) { ["id"]=> string(3) "428" ["text"]=> string(77) "Visual Studio 2017 单独启动MSDN帮助(Microsoft Help Viewer)的方法" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(8) "DonetRen" ["tagsname"]=> string(55) "Visual Studio 2017|MSDN帮助|C#程序|.NET|Help Viewer" ["tagsid"]=> string(23) "[401,402,403,"300",404]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511400964" ["_id"]=> string(3) "428" } [1]=> array(10) { ["id"]=> string(3) "427" ["text"]=> string(42) "npm -v;报错 cannot find module "wrapp"" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(4) "zzty" ["tagsname"]=> string(50) "node.js|npm|cannot find module "wrapp“|node" ["tagsid"]=> string(19) "[398,"239",399,400]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511400760" ["_id"]=> string(3) "427" } [2]=> array(10) { ["id"]=> string(3) "426" ["text"]=> string(54) "说说css中pt、px、em、rem都扮演了什么角色" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(12) "zhengqiaoyin" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511400640" ["_id"]=> string(3) "426" } [3]=> array(10) { ["id"]=> string(3) "425" ["text"]=> string(83) "深入学习JS执行--创建执行上下文(变量对象,作用域链,this)" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(7) "Ry-yuan" ["tagsname"]=> string(33) "Javascript|Javascript执行过程" ["tagsid"]=> string(13) "["169","191"]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511399901" ["_id"]=> string(3) "425" } [4]=> array(10) { ["id"]=> string(3) "424" ["text"]=> string(30) "C# 排序技术研究与对比" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(9) "vveiliang" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(8) ".Net Dev" ["catesid"]=> string(5) "[199]" ["createtime"]=> string(10) "1511399150" ["_id"]=> string(3) "424" } [5]=> array(10) { ["id"]=> string(3) "423" ["text"]=> string(72) "【算法】小白的算法笔记:快速排序算法的编码和优化" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(9) "penghuwan" ["tagsname"]=> string(6) "算法" ["tagsid"]=> string(7) "["344"]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511398109" ["_id"]=> string(3) "423" } [6]=> array(10) { ["id"]=> string(3) "422" ["text"]=> string(64) "JavaScript数据可视化编程学习(二)Flotr2,雷达图" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(7) "chengxs" ["tagsname"]=> string(28) "数据可视化|前端学习" ["tagsid"]=> string(9) "[396,397]" ["catesname"]=> string(18) "前端基本知识" ["catesid"]=> string(5) "[198]" ["createtime"]=> string(10) "1511397800" ["_id"]=> string(3) "422" } [7]=> array(10) { ["id"]=> string(3) "421" ["text"]=> string(36) "C#表达式目录树(Expression)" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(4) "wwym" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(4) ".NET" ["catesid"]=> string(7) "["119"]" ["createtime"]=> string(10) "1511397474" ["_id"]=> string(3) "421" } [8]=> array(10) { ["id"]=> string(3) "420" ["text"]=> string(47) "数据结构 队列_队列实例:事件处理" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(7) "idreamo" ["tagsname"]=> string(40) "C语言|数据结构|队列|事件处理" ["tagsid"]=> string(23) "["246","247","248",395]" ["catesname"]=> string(12) "数据结构" ["catesid"]=> string(7) "["133"]" ["createtime"]=> string(10) "1511397279" ["_id"]=> string(3) "420" } [9]=> array(10) { ["id"]=> string(3) "419" ["text"]=> string(47) "久等了,博客园官方Android客户端发布" ["intro"]=> string(288) "目录 ECharts 异步加载 ECharts 数据可视化在过去几年中取得了巨大进展。开发人员对可视化产品的期望不再是简单的图表创建工具,而是在交互、性能、数据处理等方面有更高的要求。 chart.setOption({ color: [ " ["username"]=> string(3) "cmt" ["tagsname"]=> string(0) "" ["tagsid"]=> string(2) "[]" ["catesname"]=> string(0) "" ["catesid"]=> string(2) "[]" ["createtime"]=> string(10) "1511396549" ["_id"]=> string(3) "419" } } ["count"]=> int(200) } 222 自定义View系列(三) - 爱码网

上一篇着重就onDraw中的canvas方法进行了一系列绘制,相信对canvas中几个常用的方法应该都有所了解了。今天,就实例做一个自定义的控件,拿最简单的TextView开始,做一个带有背景色的TextView。直接上代码:

1,自定义属性:

<declare-styleable name="MyTextView2">
    <attr name="style" format="integer|reference"/>
    <attr name="colors" format="color|reference"/>
    <attr name="textSize" format="dimension|reference"/>
    <attr name="text" format="string|reference"/>
</declare-styleable>

这一部分内容,上一篇介绍过,这里不再多说。

2,XML文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                xmlns:app="http://schemas.android.com/apk/res-auto">
    
    <com.example.administrator.myview1.MyTextView2
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#ffff00"
        app:colors="#ff00"
        app:textSize="36sp"
        app:text="Hello World !"/>
</RelativeLayout>

很简单,只谢了一个自定义控件而已。

3,接下来,看一下我们在MyTextView2中都做了那些工作:

public class MyTextView2 extends View {
    private int mTextSize;
    private int mColor;
    private String mText;
    private int mType;
    private Paint mPaint;
    private Rect mBound;
    private float mTextHeight;
    private float mTextWidth;

    public MyTextView2(Context context) {
        this(context,null);
    }

    public MyTextView2(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyTextView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray mTypedArray = context.obtainStyledAttributes(attrs,R.styleable.MyTextView2);
        mType = mTypedArray.getInt(R.styleable.MyTextView2_style,0);
        mColor = mTypedArray.getColor(R.styleable.MyTextView2_colors, Color.RED);
        mTextSize = mTypedArray.getDimensionPixelSize(R.styleable.MyTextView2_textSize,20);
        mText = mTypedArray.getString(R.styleable.MyTextView2_text);
        mTypedArray.recycle();
        initView();
    }
    /** 初始化 */
    private void initView(){
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);//填充类型
         mPaint.setTextSize(mTextSize);
        mPaint.setColor(mColor);
        mBound = new Rect();
        mPaint.getTextBounds(mText,0,mText.length(), mBound);
        mTextHeight = mBound.height();//测量出文字的高度
         mTextWidth = mBound.width();//测量出文字的宽度
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText(mText,getWidth()/2 - mBound.width() / 2,getHeight()/2 + mBound.height()/2,mPaint);
        }
    }
}

先是构造方法中,调用了自定义属性,用于声明,TextView的大小,颜色和文本内容。接着,就是一个初始化的方法,对画笔进行了基础的设置。然后,重写了View的onMeasure方法和onDraw()方法。onMeasure()方法,美俄进行任何操作,先不管他,直接看onDraw()方法,里面就一句代码,很简单的绘制一个文本内容。其中getWidth和getHeight是控件的宽高(和getMeasureWidth和getMeasureHeight结果一样)。结合xml内容,绘制出来的应该是背景色是黄色,内容为Hello  World !这样的一个TextView,运行一下,可以看到运行结果如图一:

                         图一:           自定义View系列(三)          图二:             自定义View系列(三)

很明显这不是我们想要的结果。在介绍View的生命周期的时候,里面对onMeasure()方法进行的说明,主要就是对控件的尺寸进行测量。而当我们在布局文件中把宽高都设置成wrap_content时,系统帮我们测量的宽高都是match_parent。只有当我们设置了明确的宽高之后测量的才是我们设置的宽高,如果我们把布局文件中的宽高分别设置为 200dp和100dp则结果就是图二。说到这,我们有必要再说一下MeasureSpec的specMode,一共三种类型(也可以参考:自定义View系列(一)):

EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT

UNSPECIFIED:表示子布局想要多大就多大,很少使用

知道了这些,我们就可以重写onMeasure()方法了,下面是我们重写的onMeasure():

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(widthMeasureSpec);

    int width;
    int height;
    if(widthMode == MeasureSpec.EXACTLY){
        width = widthSize;
    }else {
        width = (int) (getPaddingLeft() + mTextWidth + getPaddingRight());
    }
        if(heightMode == MeasureSpec.EXACTLY){
        height = heightSize;
    }else {
        height = (int) (getPaddingTop() + mTextHeight + getPaddingBottom());
    }
    setMeasuredDimension(width,height);
}

重新运行一下,得到如图三:

             图三:                         自定义View系列(三)

这样子,就达到了预期的效果。但是仔细看一下,就会发现,文本内容最后的叹号,好像只显示了一半,没能完全显示。不用想,肯定是我们的宽度设置的不够啊!上文中,有一行代码测量了文本内容的的宽度为:mTextWidth = mBound.width()。没错这样也确实可以得到文本内容的宽度,只不过不精确,测量出的是粗略值。改进方法为:mTextWidth = mPaint.measureText(mText),这样就得到了文本的精确宽度,再次运行可以看到如图四:

              图四:                         自定义View系列(三)

这样宽度就没问题了!那高度呢,肯定也是粗略的,这样子还不容易看,我们把文本内容加上中文,并且在onDraw()里面添加两行代码:

mPaint.setColor(Color.BLUE);
canvas.drawLine(0,getHeight()/2,getWidth(),getHeight()/2,mPaint);

画出控件的中线,运行,看到效果图五:

                 图五:                       自定义View系列(三)

   看到了吧,高度明显偏小了。高度怎么改进呢,这个有点麻烦,先看一个图:

自定义View系列(三)  出自网络

看一下这个图,里面有五个参数,top,ascent,basline,descent,bottom:

Baseline是基线,在Android中,文字的绘制都是从Baseline处开始的,Baseline往上至字符“最高处”的距离我们称之为ascent(上坡度),Baseline往下至字符“最低处”的距离我们称之为descent(下坡度);leading(行间距)则表示上一行字符的descent到该行字符的ascent之间的距离;top和bottom可以理解为文本绘制时,留出的内边距。之所以留出内边距,是因为因为TextView在绘制文本的时候考虑到了类似读音符号,下图中的A上面的符号就是一个拉丁文的类似读音符号的东西:

自定义View系列(三)

如上图,Baseline是基线,Baseline以上是负值,以下是正值,因此 ascent,top是负值, descent和bottom是正值。 

至此,我们算是了解清楚了,具体怎么修改呢?这里要用到FontMetrics 字体度量,它是Paint的一个今天内部类。这里不做介绍,只解释其用法。原来的高度为:mTextHeight = mBound.height();

修改后为:

Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
mTextHeight = Math.abs((fontMetrics.bottom - fontMetrics.top));

运行程序,可以看到:

                    自定义View系列(三)

发现文本内容没有居中,我们想让文本居中,然后看效果。

我们知道,中心线到底部的距离为文本高度的一半,也就是(descent - ascent)/2,这个由上面的图可以得到,这是文本的高度;中心线与基线的间距为:(descent - ascent)/2 - descent ;这样只需要在绘制的时候,将y值向上提高中心线与基线的距离就行了,即:

canvas.drawText(mText,getWidth()/2 - mTextWidth / 2,
        getHeight()/2  - (mFontMetrics.descent - (-mFontMetrics.ascent + mFontMetrics.descent)/2),mPaint);

运行一下,可以看到:

                    自定义View系列(三)

有图可以看出,文本的高度算了出来,并且居中了。由此可以得到文本垂直居中的计算方式方式

Paint.FontMetrics   mFontMetrics = mPaint.getFontMetrics();

y: getHeight/2 - mFontMetrics.descent + (mFontMetrics.descent - mFontMetrics.ascent)/ 2;

好了,最终终于达到了想要的结果!今天就说到这吧,下一篇将会说一下,自定义布局。

参考:http://blog.csdn.net/u014702653/article/details/51985821

最后,奉上最终的代码:

public class MyTextView2 extends View {
    private int mTextSize;
    private int mColor;
    private String mText;
    private int mType;
    private Paint mPaint;
    private Rect mBound;
    private float mTextHeight;
    private float mTextWidth;
    private Paint.FontMetrics mFontMetrics;

    public MyTextView2(Context context) {
        this(context,null);
    }

    public MyTextView2(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyTextView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray mTypedArray = context.obtainStyledAttributes(attrs,R.styleable.MyTextView2);
        mType = mTypedArray.getInt(R.styleable.MyTextView2_style,0);
        mColor = mTypedArray.getColor(R.styleable.MyTextView2_colors, Color.RED);
        mTextSize = mTypedArray.getDimensionPixelSize(R.styleable.MyTextView2_textSize,20);
        mText = mTypedArray.getString(R.styleable.MyTextView2_text);
        mTypedArray.recycle();
        initView();
    }
    /** 初始化 */
    private void initView(){
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);//填充类型
        mPaint.setTextSize(mTextSize);
        mPaint.setColor(mColor);
        mBound = new Rect();
        mPaint.getTextBounds(mText,0,mText.length(), mBound);
        mFontMetrics = mPaint.getFontMetrics();
        mTextHeight = Math.abs((mFontMetrics.bottom - mFontMetrics.top));//测量出文字的高度
        mTextWidth = mPaint.measureText(mText);//测量出文字的宽度
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(widthMeasureSpec);

        int width;
        int height;
        if(widthMode == MeasureSpec.EXACTLY){
            width = widthSize;
        }else {
            width = (int) (getPaddingLeft() + mTextWidth + getPaddingRight());
        }
            if(heightMode == MeasureSpec.EXACTLY){
            height = heightSize;
        }else {
            height = (int) (getPaddingTop() + mTextHeight + getPaddingBottom());
        }
        setMeasuredDimension(width,height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText(mText,getWidth()/2 - mTextWidth / 2,
                getHeight()/2  - (mFontMetrics.descent - (-mFontMetrics.ascent + mFontMetrics.descent)/2),mPaint);

        mPaint.setColor(Color.BLUE);
        canvas.drawLine(0,getHeight()/2,getWidth(),getHeight()/2,mPaint);
    }
}

相关文章: