Android 自定义view

一、总体思路

关于android中自定义view的文字描述(觉得比较全面):

1.确定view的属性和公开的方法。

2.在attrs.xml中定义上面1的属性。

3.在java文件(即自定义view的类中)通过AttrbuteSet获得属性,使用TyepeArray得到每个参数(设置在xml中的)值,并将它们初始化到view的字段中(记得回收array)。

4.如果view中包括其他View(就是viewGroup),就必须设onLayout方法。

5.实现onMessure()方法,告诉系统计算view的宽高策略。测量流程是:

父控件确定自己的子view应该以什么模式,什么大小显示,然后通过MeasureSpec吧这两个参数传递给子View,子view通过这两个参数和自己想要显示的大小,

作比较,确定自己到底需要多大,然后使用setMeasureDimension()保存这个大小。

6.实现onDraw方法,决定自己如何被绘制出来。离不开paint和canvas两个类:

paint相当于画笔:控制绘制的类型,颜色等,canvas决定决定具体绘制什么图形。

7.如果view还需要监听点击事件,还需要处理onTouchEvent方法,复杂的话还需要处理手势操作等。

二、关于text的baseLine

        首先需要知道的是:在画圆、画图片、画线等等之类的canvas.drawXxx()方法时,我们指定了坐标或点位置后,画笔就会按照我们设定的坐标在canvas上进行绘制。但是drawText()却是个例外,比如我们使用

    /**
     * Draw the text, with origin at (x,y), using the specified paint. The origin is interpreted
     * based on the Align setting in the paint.
     *
     * @param text The text to be drawn
     * @param x The x-coordinate of the origin of the text being drawn
     * @param y The y-coordinate of the baseline of the text being drawn
     * @param paint The paint used for the text (e.g. color, size, style)
     */
    public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
        super.drawText(text, x, y, paint);
    }

        文字的绘制却不会按照我们指定的x和y进行绘制,是因为这里的x、y并不是我们理解的绘制文字的起点坐标。看其注释也可以发现:

The x-coordinate of the origin of the text being drawn 
                                                ————正在绘制的文本原点的x坐标
        
The y-coordinate of the baseline of the text being drawn 
                                                ————正在绘制的文本基线的y坐标

        这里也就引出了baseLine概念,关于它,要借助两个东西,一个是坐标系,一个是四线三格。

         如图,在学英语字母或者我们的汉语拼音时,会用到这种格子。

         另外是坐标系,android界面的坐标系,以左上角为左边原点,向右为x轴,想下为y轴(当然,还有朝我们这个方向为z轴)。但text却是以baseLine为y轴原点。所以baseline以上为负值,以下为正值。

        

简单看就是这样,那到底怎么计算baseLine,那就需要知道上main图中每条线代表什么意思,如下:

 

         所以,可以知道,top、leading、accent都是负值;而bottom、descent为正值。关于这些变量代表的意义,可以看源码,canvas的静态内部类:FontMetrics

    /**
     * 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;
    }

top —— The maximum distance above the baseline for the tallest glyph in the font at a given text                 size.

                     给定的文本大小显示字体的最高字形基线上方的最大距离。

ascent —— The recommended distance above the baseline for singled spaced text

                    单个间距文字的基线上方的建议距离

descent —— The recommended distance below the baseline for singled spaced text

bottom ——  The maximum distance below the baseline for the lowest glyph in the font at a                        given  text size.

                     在给定文本大小下,字体中最低字形的基线以下的最大距离。

leading —— The recommended additional space to add between lines of text

                        建议在文本行之间添加的额外空间。

        可以理解为:top 是 accent 可能取到的最大值;bottom 为 descent 可能取到的最大值。而leading解释的比较笼统,可以理解为写特殊东西的一个空间(想拼音的音调,不知道对不对,但可以这么理解)。

        关于计算baseLine,可以记住一个公式:

int dy = (int) ((fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom);
int baseLine = getHeight() / 2 + dy;

更多的drawText也是固定的写法:

    private void drawText(Canvas canvas, Paint paint, int start, int end) {
        String text = getText().toString();
        if (!TextUtils.isEmpty(text)) {
            canvas.save();
            Rect rect = new Rect(start, 0, end, getHeight());
            canvas.clipOutRect(rect);
            paint.getTextBounds(text, 0, text.length(), rect);
            // 获取x坐标(这里设为了居中,可以个性化设置)
            int dx = getWidth() / 2 - rect.width() / 2;
            // 获取基线
            Paint.FontMetrics fontMetrics = paint.getFontMetrics();
            Log.e("----","-------------------------------------");
            Log.e("baseLine——", "top=" + fontMetrics.top);
            Log.e("baseLine——", "bottom=" + fontMetrics.bottom);
            Log.e("baseLine——", "ascent=" + fontMetrics.ascent);
            Log.e("baseLine——", "descent=" + fontMetrics.descent);
            Log.e("baseLine——", "leading=" + fontMetrics.leading);
            int dy = (int) ((fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom);
            int baseLine = getHeight() / 2 + dy;
            Log.e("baseLine——", "baseLine=" + baseLine);
            Log.e("----","-------------------------------------");

            canvas.drawText(getText().toString(), dx, baseLine, paint);
            canvas.restore();
        }
    }

三、关于画布的裁切

 会涉及到canvas的几个方法:save()、restrore()、clipRect()

    /**
     * Saves the current matrix and clip onto a private stack.
     * <p>
     * Subsequent calls to translate,scale,rotate,skew,concat or clipRect,
     * clipPath will all operate as usual, but when the balancing call to
     * restore() is made, those calls will be forgotten, and the settings that
     * existed before the save() will be reinstated.
     *
     * @return The value to pass to restoreToCount() to balance this save()
     */
    public int save() {
        return nSave(mNativeCanvasWrapper, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
    }



    /**
     * Intersect the current clip with the specified rectangle, which is
     * expressed in local coordinates.
     *
     * @param rect The rectangle to intersect with the current clip.
     * @return true if the resulting clip is non-empty
     */
    public boolean clipRect(@NonNull Rect rect) {
        return nClipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom,
                Region.Op.INTERSECT.nativeInt);
    }



    /**
     * This call balances a previous call to save(), and is used to remove all
     * modifications to the matrix/clip state since the last save call. It is
     * an error to call restore() more times than save() was called.
     */
    public void restore() {
        if (!nRestore(mNativeCanvasWrapper)
                && (!sCompatibilityRestore || !isHardwareAccelerated())) {
            throw new IllegalStateException("Underflow in restore - more restores than saves");
        }
    }

        这三个配合使用,理解也简单,以画图软件为例来理解:首先,知道canvas是画布,就相当于打开了画图软件,新建了一张图片界面,save就相当于先保存了一下这张图片(可以理解为手机屏幕),之后的 clipRect(),就是在这张保存过的图片上指定了某一块矩形区域,说之后的操作都在这个矩形区域内操作(区域外操作无法显示,即无效),当restrore()后,这张图片就恢复到之前的save状态,仍可以进行下一次的绘画。

        

       

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值