Canvas(一) Canvas基本操作

       在Android开发中,经常会需要自定义一些自绘View,而绘制自绘View就离不开Canvas(画布),本篇主要讲解Canvas本身以及与其相关的类。

开篇

首先看一下Android源码中, Canvas.java开头的介绍:

The Canvas class holds the “draw” calls. To draw something, you need
4 basic components: A Bitmap to hold the pixels, a Canvas to host
the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect,
Path, text, Bitmap), and a paint (to describe the colors and styles for the
drawing).

       从这一段中,我们可以了解到,如果我们想要绘制视图,需要四样东西:首先是Bitmap,用来保存像素,也就是Bitmap用来呈现绘制的结果;然后是Canvas,Canvas持有各种”draw”调用,比如drawText, drawRect等,它的作用就是这些Text,Rect,Color等写入到Bitmap中;第三是drawing primitive,可以理解成绘制的素材,颜色,图片,文字,路径都是要通过Canvas绘制到Bitmap中的素材;最后是画笔,主要描述绘画时的颜色或者风格。
       从官方文档中,我们可以看出来,只要拥有了这四样东西,我们就可以绘画了。其中Canvas持有了绘画的所有调用,所以理所当然的成为绘画开始的地方,只有调用了canvas.drawXXX() 方法,才可以开启一次绘画。

Canvas的来源

       在开发中,Canvas主要有三个来源;

onDraw(Canvas canvas)

       也就是我们绘制视图的地方,我们知道视图的绘制是从ViewRootImpl开始的,那么canvas也是从ViewRootImpl中传过来的,在ViewRootImpldraw()方法中调用了drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty) ,我们看一下这个方法的代码,

/**
     * @return true if drawing was successful, false if an error occurred
     */
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {

        // Draw with software renderer.
        final Canvas canvas;
        try {
            final int left = dirty.left;
            final int top = dirty.top;
            final int right = dirty.right;
            final int bottom = dirty.bottom;

            canvas = mSurface.lockCanvas(dirty);

            // The dirty rectangle can be modified by Surface.lockCanvas()
            //noinspection ConstantConditions
            if (left != dirty.left || top != dirty.top || right != dirty.right
                    || bottom != dirty.bottom) {
                attachInfo.mIgnoreDirtyState = true;
            }

            // TODO: Do this in native
            canvas.setDensity(mDensity);
        } catch (Surface.OutOfResourcesException e) {
            handleOutOfResourcesException(e);
            return false;
        } catch (IllegalArgumentException e) {
            Log.e(mTag, "Could not lock surface", e);
            // Don't assume this is due to out of memory, it could be
            // something else, and if it is something else then we could
            // kill stuff (or ourself) for no reason.
            mLayoutRequested = true;    // ask wm for a new surface next time.
            return false;
        }

        try {
            if (DEBUG_ORIENTATION || DEBUG_DRAW) {
                Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
                        + canvas.getWidth() + ", h=" + canvas.getHeight());
                //canvas.drawARGB(255, 255, 0, 0);
            }

            // If this bitmap's format includes an alpha channel, we
            // need to clear it before drawing so that the child will
            // properly re-composite its drawing on a transparent
            // background. This automatically respects the clip/dirty region
            // or
            // If we are applying an offset, we need to clear the area
            // where the offset doesn't appear to avoid having garbage
            // left in the blank areas.
            if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }

            dirty.setEmpty();
            mIsAnimating = false;
            mView.mPrivateFlags |= View.PFLAG_DRAWN;

            if (DEBUG_DRAW) {
                Context cxt = mView.getContext();
                Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
                        ", metrics=" + cxt.getResources().getDisplayMetrics() +
                        ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
            }
            try {
                canvas.translate(-xoff, -yoff);
                if (mTranslator != null) {
                    mTranslator.translateCanvas(canvas);
                }
                canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                attachInfo.mSetIgnoreDirtyState = false;

                mView.draw(canvas);

                drawAccessibilityFocusedDrawableIfNeeded(canvas);
            } finally {
                if (!attachInfo.mSetIgnoreDirtyState) {
                    // Only clear the flag if it was not set during the mView.draw() call
                    attachInfo.mIgnoreDirtyState = false;
                }
            }
        } finally {
            try {
                surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {
                Log.e(mTag, "Could not unlock surface", e);
                mLayoutRequested = true;    // ask wm for a new surface next time.
                //noinspection ReturnInsideFinallyBlock
                return false;
            }

            if (LOCAL_LOGV) {
                Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
            }
        }
        return true;
    }

我们不关系具体的实现,只关注其中两句canvas = mSurface.lockCanvas(dirty);mView.draw(canvas);,我们可以看到,显示从mSurface(绘图表面)中获取了canvas,然后传给了mView的draw方法,之后会继续讲canvas传给onDraw(Canvas canvas)方法。我们只了解大致流程,具体实现不在本篇的讨论范围之内。

Canvas canvas = new Canvas(bitmap)

       第二个来源就是Canvas的构造器,也就是我们自己创建的Canvas,根据开篇的介绍,我们可以看出来这里传入的bitmap就是要保存绘制结果的Bitmap,对于这个bitmap有特殊的要求,也就是这个bitmap必须是mutable(易变的),可以理解为这个bitmap是空的,初始化的,而不是保存了具体内容的bitmap,我们可以通过createBitmap(Bitmap source, int x, int y, int width, int height)方法创建一个bitmap,如果我们使用已经包含具体内容的bitmap就会报以下错误;
java.lang.IllegalStateException: Immutable bitmap passed to Canvas constructor
       另外需要注意的地方是,我们通过这种方式创建的Canvas最终会将绘制内容保存到传入的Bitmap中,如果我们想要绘制到View上,则需要在onDraw(Canvas sysCanvas)方法中调用sysCanvas.drawBitmap(bitmap)将这个bitmap绘制到View上。根据开篇的介绍我们知道,Canvas是把内容绘制到Bitmap上的,通过实例化自己的Canvas,我们将内容绘制到了自己的Bitmap上,而onDraw(Canvas canvas)我们可以看成是将所有的内容绘制到系统的Bitmap中去,想要让View显示绘制的内容,就必须调用canvas的drawXXX系列方法。

SurfaceHolder.lockCanvas()

       在SurfaceView中使用到,本篇不做讨论。

Canvas的drawXXX()方法

canvas.drawRect

1. drawRect(float left, float top, float right, float bottom, @NonNull Paint paint)
2. drawRect(@NonNull Rect r, @NonNull Paint paint)
3. drawRect(@NonNull RectF rect, @NonNull Paint paint)

       作用就是绘制一个矩形区域,第一个重载方法需要传入四个float类型的点,第二个传入Rect对象,这个对象保存四个int类型的顶点,第三个传入RectF对象,这个对象保存四个float类型的对象。三个重载方法的效果是完全一样的。
栗子:
自定义一个CanvasView,继承自View,重写onDraw(Canvas canvas)方法,后边的其他方法也将在此控件上修改。

@Override
    protected void onDraw(Canvas canvas) {
        Paint paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawRect(0, 0, 200, 200, paint);
    }

效果, 这里写图片描述

canvas.drawArc

drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint)
// oval绘制范围RectF,startAngle开始的角度,sweepAngle结束的角度,userCenter是否使用圆点

drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint)
// left,top,right,bottom绘制范围顶点坐标, startAngle,sweepAngle, userCenter同上

       作用是绘制一个弧形,使用起来很简单,主要注意参数userCenter,如果为true的话,绘制的结果将包含圆点,也就是弓形的首位和圆点相连组成的扇形,为false的话,则是将startAngle和sweepAngle连起来,两种效果如下;

1.userCenter为false canvas.drawArc(0, 0, 500, 500, 30, 120, true, paint);

2.userCenter为true canvas.drawArc(0, 0, 500, 500, 30, 120, false, paint);

这里写图片描述
可以看到,弧形绘制的角度是顺时针的,第二张图中蓝线描出了圆点的位置,绘制的时候没有包含圆点。

drawARGB

drawARGB(int a, int r, int g, int b)
// 参数分别是ARGB的四个分量

绘制颜色背景,没什么特别的。和drawColor功能一样。

drwaBitmap

1. drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint)
   // bitmap 将要被绘制的bitmap, left,top bitmap的左边和上边顶点

栗子:
       我们创建一个CanvasView,继承自View,加入到Activity的布局文件中,宽高为300dp,然后重写onDraw方法。

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setColor(Color.RED);
        // 画出整个View的背景
        canvas.drawColor(Color.GREEN);

        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_manage);
        // 1
        canvas.drawBitmap(bitmap, 0, 0, paint);
        // 2
        // canvas.drawBitmap(bitmap, 600, 600, paint);
    } 

1放开,如图;
这里写图片描述
bitmap的图片完全贴着左上角,
2放开,如图;
这里写图片描述
bitmap的左上角移动,向下向右移动,并且如果bitmap长宽加上左上角坐标大于View的宽高会导致显示不全。

 2. drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst, @Nullable Paint paint)
 ```
// bitmap被绘制的bitmap, src bitmap的子集区域, dst将要将bitmap绘制到的区域,会被拉伸或者缩放

栗子:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setColor(Color.RED);

        canvas.drawColor(Color.GREEN);

        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_manage);
        // 左上坐标为(0, 0)绘制的bitmap
        canvas.drawBitmap(bitmap, 0, 0, paint);
        // 本方法绘制的bitmap, (0, 0, 100, 100)是相对于bitmap而言的区域,相当于bitmap的子集,(200, 200, 500, 500)是相对于View的区域,这个方法的目的就是将bitmap的某个子集绘制到View的指定区域上
        canvas.drawBitmap(bitmap, new Rect(0, 0, 100, 100), new Rect(200, 200, 500, 500), paint);
    }

效果如图,这里写图片描述

另外这个方法还有一个类似的重载方法

drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst, @Nullable Paint paint)

不同点就是dst一个是RectF,一个是Rect

3. drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint)
// bitmap 源bitmap matrix对这个bitmap做变换的变换矩阵

栗子:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setColor(Color.RED);

        canvas.drawColor(Color.GREEN);

        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_manage);
        canvas.drawBitmap(bitmap, 0, 0, paint);

        Matrix matrix = new Matrix();
        matrix.postTranslate(300, 300);
        canvas.drawBitmap(bitmap, matrix, paint);
    }

效果,
这里写图片描述
第二个bitmap由于使用了矩阵,导致发生了位移,Matrix就是变换矩阵,具体用法以后再写。

drawCircle

drawCircle(float cx, float cy, float radius, @NonNull Paint paint)
// cx,cy圆点坐标,radius半径

栗子:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setColor(Color.RED);

        canvas.drawColor(Color.GREEN);

        canvas.drawCircle(200, 200, 100, paint);

    }

效果:
这里写图片描述

drawLine

drawLine(float startX, float startY, float stopX, float stopY,
@NonNull Paint paint)
// startX,startY 划线开始的坐标, stopX, stopY线结束的坐标         

栗子:

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setColor(Color.RED);

        canvas.drawColor(Color.GREEN);

        canvas.drawLine(0, 0, 300, 300, paint);
    }

效果,这里写图片描述

drawLines

1. drawLines(@Size(multiple=4) @NonNull float[] pts, int offset, int count, @NonNull Paint paint)
// pts 线段开始和结束的坐标,每四个点确定一个线段比如
// float[] pts = new float[]{
    //  0, 0,
    // 100, 100,
    // 200, 200, 
    // 300, 300,
//}这个数组可以确定两个线段,分别为开始坐标为(0, 0),结束为(100, 100)和开始为(200, 200),结束为(300, 300)的线段
// offset为跳过的点的个数, count为除了跳过的点以外可以用于绘制线段的点
// 其中有这样的关系 pts.length >= offset + count,如果两者之和超过pts.length
// 就会报数组越界
// 另外,由于必须由4个点确定一个线段,所以如果count<4,那么就不会绘制线段,并且count必须为4的倍数,那么线段数就是count >> 2
// 如上,如果offset=3,那么最后的300就会被抛弃

栗子:

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setColor(Color.RED);

        canvas.drawColor(Color.GREEN);

        float[] points = new float[] {
                0, 0,
                100, 100,
                200, 200,
                300, 300,
                400, 400,
                500, 500
        };
        canvas.drawLines(points, 4, 8, paint);
    }

效果:这里写图片描述

另外一个绘制线段的方法;

public void drawLines(@Size(multiple=4) @NonNull float[] pts, @NonNull Paint paint) {
        drawLines(pts, 0, pts.length, paint);
    }
    // 只是offset == 0, count == pts.length

drawOval

drawOval(@NonNull RectF oval, @NonNull Paint paint)
drawOval(float left, float top, float right, float bottom, @NonNull Paint paint)
// 绘制椭圆, 和drawRect类似

drawPath

drawPath(@NonNull Path path, @NonNull Paint paint)
// 绘制一个path,没啥特别的

drawPoint和drawPoints

drawPoint(float x, float y, @NonNull Paint paint)
// 画单个点,没啥特别的
drawPoints(@Size(multiple=2) @NonNull float[] pts, @NonNull Paint paint)
drawPoints(@Size(multiple=2) float[] pts, int offset, int count,@NonNull Paint paint)
// 使用和drawLines基本一样,不过drawLines需要四个值确定一条线,drawPoints需要两个值确定一个点,所以count必须为2的倍数,否则最后不成对的点会被抛弃,点的总数等于conut>>1

栗子:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setStrokeWidth(10);
        paint.setColor(Color.RED);

        canvas.drawColor(Color.GREEN);

        float[] points = new float[] {
                0, 0,
                100, 100,
                200, 200,
                300, 300,
                400, 400,
                500, 500
        };
        canvas.drawPoints(points, 3, 8, paint);
    }

效果,这里写图片描述

drawRoundRect

drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, @NonNull Paint paint)
// left, top, right, bottom为四个顶点, rx,ry为圆角的圆点,值越大则圆角越明显
drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint)
// rect为圆角矩形范围,rx,ry同上

栗子:

  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setStrokeWidth(10);
        paint.setColor(Color.RED);

        canvas.drawColor(Color.GREEN);


        canvas.drawRoundRect(new RectF(0, 0, 300, 300), 10, 10, paint);
        canvas.drawRoundRect(300, 300, 600, 600, 50, 50, paint);

    }

效果,这里写图片描述

drawText

1. drawText(@NonNull char[] text, int index, int count, float x, float y,@NonNull Paint paint)
// text 要绘制的字符数组,index数组开始的下标,count绘制的字符个数, x为开始绘制字符串的x坐标,y需要注意一下是基线的y坐标
2. drawText(@NonNull String text, float x, float y, @NonNull Paint paint)
// text 要绘制的字符串,x,y同上
3. drawText(@NonNull String text, int start, int end, float x, float y,
            @NonNull Paint paint)
// text同上, start开始的字符索引,end-1为结束的字符索引,需要注意一下, x, y同上
4. drawText(@NonNull CharSequence text, int start, int end, float x, float y,@NonNull Paint paint)
// text要绘制的字符串,start,end同上,x, y同上

基线不好解释,还是画出来比较方便

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setStrokeWidth(2);
        paint.setTextSize(50);
        paint.setColor(Color.RED);

        int baselineX = 0;
        int baselineY = 100;

        // 画出x坐标为0-1000,y为100的线
        canvas.drawLine(0, baselineY, 1000, baselineY, paint);

        paint.setColor(Color.BLUE);
        canvas.drawText("Talk is cheap, show me your code!", baselineX, baselineY, paint);
    }

效果,这里写图片描述
可以看到,红线的Y坐标和drawText的Y坐标相同,这条线就是基线,基线有点类似于写英文单词时的四线三格,用来规范字母的位置。这样只要我们只要知道了基线位置,绘制字符串开始的X坐标,还有字体大小就可以确定字体的位置了。

FontMetrics

       在Android中,为了规范字体的位置,除了基线还有其他的几条线,如下,
这里写图片描述
图片来源感谢作者

  1. top 可以绘制的最高位置所在的线
  2. ascent 建议的绘制的最高位置所在的线
  3. baseline 基线
  4. descent建议的绘制的最低位置所在的线
  5. bottom 可绘制的最低位置所在的线

那么,如何得到这些线的坐标那?
系统给我们提供了FontMetric类,它是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;
    }

       从源码中我们可以看出来,类中的变量描述的都是以baseLine(基线)为基础的,top表示baseLine上方最高距离所在的线,ascent表示baseLine上方系统建议距离所在的线,descent表示baseLine下方系统建议距离所在的线,bottom表示baseLine下方最低距离所在的线, leading表示文本线之间建议的附加空间。这里我们需要注意一点,这些变量都是描述的distance(距离,有正负),而且以baseLine为基础,所以我们应该通过这两个特点计算出各个线的真实y坐标。
       我们可以通过getFontMetrics()方法获得FontMetrics对象,然后获得各个变量,根据上图的位置,我们可以得出以下结论;

  1. top的y坐标 = baseLine的y坐标 + fontMetrics.top
  2. ascent的y坐标 = baseLine的y坐标 + fontMetric.ascent
  3. descent的y坐标 = baseLine的y坐标 + fontMetric.descent
  4. bottom的y坐标 = baseLine的y坐标 + fontMetric.bottom

根据以上的结论,我们画出这四条线,如下;

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setTextSize(150);
        paint.setColor(Color.RED);

        int baselineY = 200;
        int baselineX = 0;

        canvas.drawText("Talk is Cheap", baselineX, baselineY, paint);

        //计算各线在位置
        Paint.FontMetrics fm = paint.getFontMetrics();
        float ascent = baselineY + fm.ascent;
        float descent = baselineY + fm.descent;
        float top = baselineY + fm.top;
        float bottom = baselineY + fm.bottom;
//
        //画基线
        paint.setColor(Color.RED);
        canvas.drawLine(baselineX, baselineY, 3000, baselineY, paint);
        paint.setColor(Color.BLUE);
        canvas.drawLine(baselineX, top, 1000, top, paint); // top线
        paint.setColor(Color.GREEN);
        canvas.drawLine(baselineX, ascent, 1000, ascent, paint); // ascent
        paint.setColor(Color.YELLOW);
        canvas.drawLine(baselineX, descent, 1000, descent, paint); // descent
        paint.setColor(Color.RED);
        canvas.drawLine(baselineX, bottom, 1000, descent, paint); // bottom
    }

效果,这里写图片描述
注意一点,最后的descent所在的线和bottom距离很近,几乎重合在了一起,可以看到颜色混在了一起。

drawTextOnPath

1. /**
     * Draw the text, with origin at (x,y), using the specified paint, along
     * the specified path. The paint's Align setting determins where along the
     * path to start the text.
     *
     * @param text     The text to be drawn
     * @param path     The path the text should follow for its baseline
     * @param hOffset  The distance along the path to add to the text's
     *                 starting position
     * @param vOffset  The distance above(-) or below(+) the path to position
     *                 the text
     * @param paint    The paint used for the text (e.g. color, size, style)
     */
drawTextOnPath(@NonNull char[] text, int index, int count, @NonNull Path path,float hOffset, float vOffset, @NonNull Paint paint)

2./**
     * Draw the text, with origin at (x,y), using the specified paint, along
     * the specified path. The paint's Align setting determins where along the
     * path to start the text.
     *
     * @param text     The text to be drawn
     * @param path     The path the text should follow for its baseline
     * @param hOffset  The distance along the path to add to the text's
     *                 starting position
     * @param vOffset  The distance above(-) or below(+) the path to position
     *                 the text
     * @param paint    The paint used for the text (e.g. color, size, style)
     */
 drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,float vOffset, @NonNull Paint paint)

       两个重载方法的不同之处在于接收的内容不同,1接收字符数组,index表示开始绘制的字符下标,count表示绘制字符的数量,其他参数的意义是一样的,我们一个一个分析;

  • path: The path the text should follow for its baseline
    也就是说以这个path作为text的基线绘制
  • hOffset: The distance along the path to add to the text’s starting position
    在path上绘制文本的时候开始的位移,也就是先空出hOffset的距离,然后再开始绘制,有点类似于段落开头的空格。
  • vOffset:The distance above(-) or below(+) the path to position
    顾名思义,vOffset代表了垂直方向上的位移,参考物为path,也就是基线,有符号,在path以上为负号,在path以下为正号。

       另外,在方法的注释中我们可以看到,开始绘制的位置受paint的Align影响

栗子:

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setStyle(Paint.Style.STROKE);
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setTextSize(50);
        canvas.drawColor(Color.parseColor("#8EE5EE"));

        paint.setColor(Color.BLACK);
        Path path = new Path();
        path.addCircle(300, 300, 200, Path.Direction.CCW);
        canvas.drawPath(path, paint);

//        1
        paint.setColor(Color.RED);
        canvas.drawTextOnPath("Talk is cheap, show me your code!", path, 0, 0, paint);

//        2
//        paint.setColor(Color.BLUE);
//        canvas.drawTextOnPath("Talk is cheap, show me your code!", path, 200, 0, paint);

//        3
//        paint.setColor(Color.BLACK);
//        canvas.drawTextOnPath("Talk is cheap, show me your code!", path, 0, -30, paint);
//
//        4
//        paint.setColor(Color.BLACK);
//        canvas.drawTextOnPath("Talk is cheap, show me your code!", path, 0, 30, paint);
    }

放开这是1下的代码,效果如下;这里写图片描述
可以看出,path在基线的位置上。

放开2的代码,效果如下;这里写图片描述
对比上图,可以看出来,文字开始的位置向后移动了一段距离

放开3的代码,效果如下;这里写图片描述
可以看出来,相对于基线,文字向上移动了一段距离,图中看似是向下,主要是因为Path是圆形导致的。

放开4的代码,效果如下;这里写图片描述
对比3可以看出,相对于基线,文字向下移动了一段距离。

以上就是Canvas相关的基本操作。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旅行者40

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值