Canvas中的书法家讲解与实战——Android高级UI

4,221 阅读6分钟

目录

一、前言

二、Canvas中的书法家API

三、实战

四、写在最后

一、前言

canvas 的 API 方法相当之多,小盆友本篇文章之前已经分享了 “Canvas中的裁剪师”“Canvas中的绘图师”,今天分享的是文字方面的API。

在分享前,小盆友啰嗦两句,有些童鞋说 canvas 的这几篇文章是初级文章和 “Android高级UI” 这几个字显得有些格格不入。小盆友借此解释下,canvas 的这几篇文章是作为 高级UI 文章的补充,是这系列文章中的 垫脚石,并非想做 “标题党” 来吸引流量(貌似一直也没什么流量😂)。

啰嗦了这么多,来看看今天的实战效果图

抖动的字符

二、Canvas中的书法家API

1、drawText(四个重载方法)

(1)第一个 drawText 函数

public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint)

描述: 在坐标为 (x,y) 处绘制 text 字符串。

举个例子:

private static final String CONTENT = "zinc 猛猛的小盆友";

canvas.drawText(CONTENT, -300, -500, mPaint);

效果图 (2)第二个 drawText 函数

public void drawText(@NonNull String text, int start, int end, float x, float y,
            @NonNull Paint paint)

描述: 在坐标为 (x,y) 处绘制字符串text,从下标为start的字符开始,到下标为 (end-1) 的字符终止。

举个例子:

private static final String CONTENT = "zinc 猛猛的小盆友";

// 绘制内容为从CONTENT的第四个字符开始,到CONTENT最后一个字符
canvas.drawText(CONTENT, 3, CONTENT.length(), -300, -400, mPaint);

效果图 (3)第三个 drawText 函数

public void drawText(@NonNull char[] text, int index, int count, float x, float y,
            @NonNull Paint paint)

描述: 在坐标为 (x,y) 处绘制 text,从下标为start开始,绘制count个字符。

举个例子

private static final char[] C = "https://github.com/zincPower/UI2018".toCharArray();

canvas.drawText(C, 0, C.length, -300, -100, mPaint);
canvas.drawText(C, 5, 10, -300, 0, mPaint);

效果图 (4)第四个 drawText 函数

public void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
            @NonNull Paint paint)

描述: 在坐标为 (x,y) 处绘制 text,从下标为start的字符开始,到下标为 (end-1) 的字符终止。

举个例子

private static final CharSequence SEQ = "https://blog.csdn.net/weixin_37625173";

canvas.drawText(SEQ, 0, SEQ.length(), -300, 300, mPaint);
canvas.drawText(SEQ, 6, 20, -300, 400, mPaint);

效果图

2、drawTextOnPath (两个重载方法)

(1)第一个 drawTextOnPath 函数

public void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,
            float vOffset, @NonNull Paint paint)

描述:路径path 上绘制 text。

特殊参数说明: 1)hOffset:水平偏移量 2)vOffset:垂直偏移量

举个例子

private static final String CONTENT = "zinc 猛猛的小盆友";

// mPath 是一个贝塞尔曲线绘制的路径
canvas.drawTextOnPath(CONTENT, mPath, 0, 0, mPaint);

效果图 (2)第二个 drawTextOnPath 函数

public void drawTextOnPath(@NonNull char[] text, int index, int count, @NonNull Path path,
            float hOffset, float vOffset, @NonNull Paint paint)

描述:路径path 上绘制 text。

特殊参数说明: 1)index:从下标为index的字符开始绘制 2)count:绘制字符的个数 3)hOffset:水平偏移量 4)vOffset:垂直偏移量

例子

private static final char[] C = "https://blog.csdn.net/weixin_37625173".toCharArray();

// 从下标为2的字符(即第三个字符)开始,绘制20个字符
canvas.drawTextOnPath(C, 2, 20, mPath, 0, 0, mPaint);

效果图

3、drawTextRun

这个方法不做过多的解释,因为在实际开发中使用可以说较少。简单概括这个方法的作用,他是为了处理一些语言文字(例如:阿拉伯语),当一个字在一个词语中,会受左右的字影响而进行变形的情况。

这方面的语言小盆友不懂,所以没法举出严谨的例子,请有需要的童鞋移步Demo中自行体会。

public void drawTextRun(@NonNull char[] text, int index, int count, int contextIndex,
            int contextCount, float x, float y, boolean isRtl, @NonNull Paint paint) 
            
public void drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart,
            int contextEnd, float x, float y, boolean isRtl, @NonNull Paint paint)

4、drawPosText(两个重载方法)

(1)第一个 drawPosText 函数

public void drawPosText(@NonNull String text, @NonNull @Size(multiple = 2) float[] pos,
            @NonNull Paint paint)

描述: 在pos对应的坐标上绘制text。

举个例子

private static final String CONTENT = "猛猛的小盆友";
private static final float[] pos1 = new float[]{
        -300, -600,
        -250, -500,
        -200, -400,
        -150, -300,
        -100, -200,
        -50, -100,
};

// 每个字符 和 pos的坐标要一一对应的上,否则crash
canvas.drawPosText(CONTENT, pos1, mPaint);

效果图 (2)第二个 drawPosText 函数

public void drawPosText(@NonNull char[] text, int index, int count,
            @NonNull @Size(multiple = 2) float[] pos,
            @NonNull Paint paint)

描述: 在pos对应的坐标上绘制text,从下标为index的字符开始,绘制count个。

举个例子

private static final String CONTENT = "猛猛的小盆友";
private static final float[] pos2 = new float[]{
        -300, 100,
        -250, 200,
        -200, 300,
        -150, 400,
        -100, 500,
};

canvas.drawPosText(CONTENT.toCharArray(), 1, 4, pos2, mPaint);

效果图

三、实战

抖动的字符

Github入口:传送门

编码思路 在这一实战中,其实 drawTextOnPath 反倒不是主角,他只是负责在我们的 path 上将文字绘出即可,所以童鞋们知道了最主要的是 path 的确定。

获取 path 上的点,是通过如下的函数获取

private float calculateY(float x) {
    double a = Math.pow(4 / (4 + Math.pow(4 * x / mLength, 4)), 2.5f) * mA;
    return (float) (a * Math.sin(Math.PI * x / 200 - m));
}

具体的函数图形如下

公式的最初原型来源于此博客,在此谢谢博主。

看完这函数,可能有些童鞋比较懵逼,小盆友稍微给一些简单解释(毕竟难的我也说不清😂),我们将此公式抽象一下,便是如下形状:

A*sin(w*x+m)+k

这就是我们在初中学的三角函数:正弦函数sin。我们罗列下几个参数的作用:

  • A:管理 正弦函数 的振幅,即上下摆动的幅度;
  • w:管理 正弦函数 的水平收缩幅度
  • m:管理 正弦函数 的水平偏移量
  • k:管理 正弦函数 的垂直偏移量

在我们这里的场景中,主要控制两个参数:

  1. A 的振幅变动,上面函数图中在靠近 x=0 的地方函数的震动幅度变大,所以我们将 x的值 考虑进 A的计算中,并且以分母的形式(分母越大,数值越小;分母越小,数值越大)
  2. m 的水平偏移,正弦的一个周期是 2π,所以我们这里的曲直只需要从 [0-2π] 的一个范围即可。

让字符串摇摆起来 经过上面的简单分析,我们需要的各种零件也都准备好了,最后加入我们再熟悉不过的 属性动画,就可以让这条 路径path 动起来。路径path 动起来,会导致绘制在上面文字也动起来。

mAnimator = ValueAnimator.ofFloat(0, (float) (2 * Math.PI));
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float progress = (float) animation.getAnimatedValue();
        m = progress;
        mA = (float) (1 - progress / (2 * Math.PI)) * A;
        invalidate();
    }
});
mAnimator.setDuration(1000);

四、写在最后

这次的文章较为简单和基础,只是小盆友有点强迫症,必须要把 canvas 的每个API都过一遍和记录一下。

如果觉得文章对你有所启发,请给我个赞吧,如果发现有那些欠妥的地方,请留言区与我讨论,我们共同进步。

高级UI系列的Github地址:请进入传送门,如果喜欢的话给我一个star吧😄