转载爱哥自定义View系列--文字详解

FontMetrics

FontMetrics意为字体测量,这么一说大家是不是瞬间感受到了这玩意的重要性?那这东西有什么用呢?我们通过源码追踪进去可以看到FontMetrics其实是Paint的一个内部类,而它里面呢就定义了top,ascent,descent,bottom,leading五个成员变量其他什么也没有:

这五个成员变量除了top和bottom我们较熟悉外其余三个都很陌生是做什么用的呢?首先我给大家看张图:

这张图很简单但是也很扼要的说明了top,ascent,descent,bottom,leading这五个参数。首先我们要知道Baseline基线,在Android中,文字的绘制都是从Baseline处开始的,Baseline往上至字符最高处的距离我们称之为ascent(上坡度),Baseline往下至字符最底处的距离我们称之为descent(下坡度),而leading(行间距)则表示上一行字符的descent到该行字符的ascent之间的距离,top和bottom文档描述地很模糊,其实这里我们可以借鉴一下TextView对文本的绘制,TextView在绘制文本的时候总会在文本的最外层留出一些内边距,为什么要这样做?因为TextView在绘制文本的时候考虑到了类似读音符号,可能大家很久没写过拼音了已经忘了什么叫读音符号了吧……下图中的A上面的符号就是一个拉丁文的类似读音符号的东西:

然而根据世界范围内已入案的使用语言中能够标注在字符上方或者下方的除了类似的符号肯定是数不胜数的……哥不是语言专家我母鸡啊……而top的意思其实就是除了Baseline到字符顶端的距离外还应该包含这些符号的高度,bottom的意思也是一样,一般情况下我们极少使用到类似的符号所以往往会忽略掉这些符号的存在,但是Android依然会在绘制文本的时候在文本外层留出一定的边距,这就是为什么top和bottom总会比ascent和descent大一点的原因。而在TextView中我们可以通过xml设置其属性android:includeFontPadding="false"去掉一定的边距值但是不能完全去掉。下面我们在Canvas上绘制一段文本并尝试打印文本的top,ascent,descent,bottom和leading:

public class FontView extends View {
    private static final String TEXT = "ap爱哥ξτβбпшㄎㄊěǔぬも┰┠№@↓";
    private Paint mPaint;// 画笔
    private FontMetrics mFontMetrics;// 文本测量对象

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

    public FontView(Context context, AttributeSet attrs) {
        super(context, attrs);

        // 初始化画笔
        initPaint();
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        // 实例化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setTextSize(50);
        mPaint.setColor(Color.BLACK);

        mFontMetrics = mPaint.getFontMetrics();

        Log.d("Aige", "ascent:" + mFontMetrics.ascent);
        Log.d("Aige", "top:" + mFontMetrics.top);
        Log.d("Aige", "leading:" + mFontMetrics.leading);
        Log.d("Aige", "descent:" + mFontMetrics.descent);
        Log.d("Aige", "bottom:" + mFontMetrics.bottom);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText(TEXT, 0, Math.abs(mFontMetrics.top), mPaint);
    }
}

logcat输出如下:

注:Baseline上方的值为负,下方的值为正

如图我们得到了top,ascent,descent,bottom和leading的值,因为只有一行文本所以leading恒为0,那么此时的显示效果是如何的呢?上面我们说到Android中文本的绘制是从Baseline开始的,在屏幕上的体现便是Y轴坐标,所以在

canvas.drawText(TEXT, 0, Math.abs(mFontMetrics.top), mPaint);

中我们将文本绘制的起点Y坐标向下移动Math.abs(mFontMetrics.top)个单位(注:mFontMetrics.top是负数),相当于把文本的Baseline向下移动Math.abs(mFontMetrics.top)个单位,此时文本的顶部刚好会和屏幕顶部重合:

从代码中我们可以看到一个很特别的现象,在我们绘制文本之前我们便可以获取文本的FontMetrics属性值,也就是说我们FontMetrics的这些值跟我们要绘制什么文本是无关的,而仅与绘制文本Paint的size和typeface有关我们来分别更改这两个值看看:

mPaint.setTextSize(70);

如图所示所有值都改变了,我们再为Paint设置一个typeface:

mPaint.setTypeface(Typeface.SERIF);


同样所有的值也改变了,那么我们知道这样的一个东西有什么用呢?如上所说文本的绘制是从Baseline开始,并且Baseline并非文本的分割线,当我们想让文本绘制的时候居中屏幕或其他的东西时就需要计算Baseline的Y轴坐标,,比如我们让我们的文本居中画布:

public class FontView extends View {
    private static final String TEXT = "ap爱哥ξτβбпшㄎㄊ";
    private Paint textPaint, linePaint;// 文本的画笔和中心线的画笔

    private int baseX, baseY;// Baseline绘制的XY坐标

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

    public FontView(Context context, AttributeSet attrs) {
        super(context, attrs);

        // 初始化画笔
        initPaint();
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        // 实例化画笔
        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setTextSize(70);
        textPaint.setColor(Color.BLACK);

        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint.setStyle(Paint.Style.STROKE);
        linePaint.setStrokeWidth(1);
        linePaint.setColor(Color.RED);
    }

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

        // 计算Baseline绘制的起点X轴坐标
        baseX = (int) (canvas.getWidth() / 2 - textPaint.measureText(TEXT) / 2);

        // 计算Baseline绘制的Y坐标
        baseY = (int) ((canvas.getHeight() / 2) - ((textPaint.descent() + textPaint.ascent()) / 2));

        canvas.drawText(TEXT, baseX, baseY, textPaint);

        // 为了便于理解我们在画布中心处绘制一条中线
        canvas.drawLine(0, canvas.getHeight() / 2, canvas.getWidth(), canvas.getHeight() / 2, linePaint);
    }
}

效果如图:

Baseline绘制的起点x坐标为画布宽度的一半(中点x坐标)减去文本宽度的一半(这里我们的画布大小与屏幕大小一样),这个很好理解,而y坐标为画布高度的一半(中点y坐标)减去ascent和descent绝对值之差的一半,这一点很多朋友可能不是很好理解,其实很简单,如果直接以画布的中心为Baseline:

也就是说Baseline和屏幕中线重合,而这样子绘制出来的文本必定不在屏幕中心,因为ascent的距离大于descent的距离(大多数情况下我们没有考虑top和bottom),那么我们就需要将Baseline往下移使绘制出来的文本能在中心

那么该下移多少呢?这是一个问题,很多童鞋的第一反应是下移ascent的一半高度,但是你要考虑到已经在中线下方的descent的高度,所以我们应该先在ascent的高度中减去descent的高度再除以二再让屏幕的中点Y坐标(也就是高度的一半)加上这个偏移值

baseY = (int) ((canvas.getHeight() / 2) + ((Math.abs(textPaint.ascent()-Math.abs(textPaint.descent()))) / 2));

计算偏移距离公式 ascent-(ascent+descent)/2 = (asent-descent)/2

画个图就清楚了,文字中间位置为(ascent+descent)/2

这个公式跟我们上面代码中的是一样的,不信大家可以自己算算这里就不多说了。这里我们的需求是让文本绘制在某个区域的中心,实际情况中有很多不同的需求不如靠近某个区域离某个区域需要多少距离等等,熟练地去学会计算文本测绘中的各个值就显得很有必要了!

Paint有一个唯一的子类TextPaint就是专门为文本绘制量身定做的“笔”,而这支笔就如API所描述的那样能够在绘制时为文本添加一些额外的信息,这些信息包括:baselineShift,bgColor,density,drawableState,linkColor,这些属性都很简单大家顾名思义或者自己去尝试下即可这里就不多说了,那么这支笔有何用呢?最常用的用法是在绘制文本时能够实现换行绘制!在正常情况下Android绘制文本是不能识别换行符之类的标识符的,这时候如果我们想实现换行绘制就得另辟途径使用StaticLayout结合TextPaint实现换行,StaticLayout是android.text.Layout的一个子类,很明显它也是为文本处理量身定做的,其内部实现了文本绘制换行的处理,该类不是本系列重点我们不再多说直接Look一下它是如何实现换行的:

public class StaticLayoutView extends View {
    private static final String TEXT = "This is used by widgets to control text layout. You should not need to use this class directly unless you are implementing your own widget or custom display object, or would be tempted to call Canvas.drawText() directly.";
    private TextPaint mTextPaint;// 文本的画笔
    private StaticLayout mStaticLayout;// 文本布局

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

    public StaticLayoutView(Context context, AttributeSet attrs) {
        super(context, attrs);

        // 初始化画笔
        initPaint();
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        // 实例化画笔
        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setTextSize(50);
        mTextPaint.setColor(Color.BLACK);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mStaticLayout = new StaticLayout(TEXT, mTextPaint, canvas.getWidth(), Alignment.ALIGN_NORMAL, 1.0F, 0.0F, false);
        mStaticLayout.draw(canvas);
        canvas.restore();
    }
}

好了,对Paint绘制文本的一个简单了解就先到这,我们来看看Paint中到底提供了哪些实用的方法来绘制文本

ascent()

顾名思义就是返回上坡度的值,我们已经用过了,注意是负数

descent()

同上,不多说了

breakText (CharSequence text, int start, int end, boolean measureForwards, float maxWidth, float[] measuredWidth)

这个方法让我们设置一个最大宽度在不超过这个宽度的范围内返回实际测量值否则停止测量,参数很多但是都很好理解,text表示我们的字符串,start表示从第几个字符串开始测量,end表示从测量到第几个字符串为止,measureForwards表示向前还是向后测量,maxWidth表示一个给定的最大宽度在这个宽度内能测量出几个字符,measuredWidth为一个可选项,可以为空,不为空时返回真实的测量值。同样的方法还有breakText (String text, boolean measureForwards, float maxWidth, float[] measuredWidth)和breakText (char[] text, int index, int count, float maxWidth, float[] measuredWidth)。这些方法在一些结合文本处理的应用里比较常用,比如文本阅读器的翻页效果,我们需要在翻页的时候动态折断或生成一行字符串,这就派上用场了~~~

getFontMetrics (Paint.FontMetrics metrics)

这个和我们之前用到的getFontMetrics()相比多了个参数,getFontMetrics()返回的是FontMetrics对象而getFontMetrics(Paint.FontMetrics metrics)返回的是文本的行间距,如果metrics的值不为空则返回FontMetrics对象的值。

getFontMetricsInt()

该方法返回了一个FontMetricsInt对象,FontMetricsInt和FontMetrics是一样的,只不过FontMetricsInt返回的是int而FontMetrics返回的是float

getFontMetricsInt(Paint.FontMetricsInt fmi)

不扯了

getFontMetricsInt()

该方法返回了一个FontMetricsInt对象,FontMetricsInt和FontMetrics是一样的,只不过FontMetricsInt返回的是int而FontMetrics返回的是float

getFontMetricsInt(Paint.FontMetricsInt fmi)

不扯了

getFontSpacing()

返回字符行间距

setUnderlineText(boolean underlineText)

设置下划线

setTypeface(Typeface typeface)

设置字体类型,上面我们也使用过,Android中字体有四种样式:BOLD(加粗),BOLD_ITALIC(加粗并倾斜),ITALIC(倾斜),NORMAL(正常);而其为我们提供的字体有五种:DEFAULT,DEFAULT_BOLD,MONOSPACE,SANS_SERIF和SERIF,这些什么类型啊、字体啊之类的都很简单大家自己去试试就知道就不多说了。但是系统给我们的字体有限我们可不可以使用自己的字体呢?答案是肯定的!Typeface这个类中给我们提供了多个方法去个性化我们的字体

defaultFromStyle(int style)

最简单的,简而言之就是把上面所说的四种Style封装成Typeface

create(String familyName, int style)和create(Typeface family, int style)

两者大概意思都一样,比如

textPaint.setTypeface(Typeface.create("SERIF", Typeface.NORMAL));
textPaint.setTypeface(Typeface.create(Typeface.SERIF, Typeface.NORMAL));

两者效果是一样的

createFromAsset(AssetManager mgr, String path)、createFromFile(String path)和createFromFile(File path)
// 获取字体并设置画笔字体
Typeface typeface = Typeface.createFromAsset(context.getAssets(), "kt.ttf");
textPaint.setTypeface(typeface);

这三者也是一样的,它们都允许我们使用自己的字体比如我们从asset目录读取一个字体文件:

// 获取字体并设置画笔字体
Typeface typeface = Typeface.createFromAsset(context.getAssets(), "kt.ttf");
textPaint.setTypeface(typeface);

我们将会得到如下效果:


这里我用了一个卡通的字体,而另外两个方法也类似的我就不讲了。

说到文本大家第一时间想到的应该是TextView,其实在TextView里我们依然可以找到上面很多方法的影子,比如我们可以从TextView中获取到TextPaint:

TextPaint paint = mTextView.getPaint();

当然也可以设置TextView的字体等等:

Typeface typeface = Typeface.createFromAsset(getAssets(), "kt.ttf");
mTextView.setTypeface(typeface);

更多的雷同点还是留给大家去发掘,下面继续来看

setTextSkewX(float skewX) 

这个方法可以设置文本在水平方向上的倾斜,效果类似下图:

// 设置画笔文本倾斜
textPaint.setTextSkewX(-0.25F);


这个倾斜值没有具体的范围,但是官方推崇的值为-0.25可以得到比较好的倾斜文本效果,值为负右倾值为正左倾,默认值为0

setTextSize (float textSize)

不说了但是要注意该值必需大于零

setTextScaleX (float scaleX)

将文本沿X轴水平缩放,默认值为1,当值大于1会沿X轴水平放大文本,当值小于1会沿X轴水平缩放文本

大家注意哦!setTextScaleX不仅放大了文本宽度同时还拉伸了字符!这是亮点~~ 

setTextLocale (Locale locale)

设置地理位置,这个不讲,我们会在屏幕适配系列详解什么是Locale,这里如果你要使用,直接传入Locale.getDefault()即可

setTextAlign (Paint.Align align)

设置文本的对其方式,可供选的方式有三种:CENTER,LEFT和RIGHT,其实从这三者的名字上看我们就知道其意思,但是问题是这玩意怎么用的?好像没什么用啊……我们的文本大小是通过size和typeface确定的(其实还有其他的因素但这里影响不大忽略~~),一旦baseline确定,对不对齐好像不相干吧……但是,你要知道一点,文本的绘制是从baseline开始没错,但是是从哪边开始绘制的呢?左端还是右端呢?而这个Align就是为我们定义在baseline绘制文本究竟该从何处开始,上面我们在进行对文本的水平居中时是用Canvas宽度的一半减去文本宽度的一半:

实际上我们大可不必这样计算,我们只需设置Paint的文本对齐方式为CENTER,drawText的时候起点x = canvas.getWidth() / 2即可:

textPaint.setTextAlign(Align.CENTER);
canvas.drawText(TEXT, canvas.getWidth() / 2, baseY, textPaint);

当我们将文本对齐方式设置为CENTER后就相当于告诉Android我们这个文本绘制的时候从文本的中点开始向两端绘制,如果设置为LEFT则从文本的左端开始往右绘制,如果为RIGHT则从文本的右端开始往左绘制:

setSubpixelText (boolean subpixelText)

设置是否打开文本的亚像素显示,什么叫亚像素显示呢?你可以理解为对文本显示的一种优化技术,如果大家用的是Win7+系统可以在控制面板中找到一个叫ClearType的设置,该设置可以让你的文本更好地显示在屏幕上就是基于亚像素显示技术。具体我们在设计色彩系列将会细说,这里就不扯了

setStrikeThruText (boolean strikeThruText)

文本删除线,不扯

setLinearText (boolean linearText)

设置是否打开线性文本标识,这玩意对大多数人来说都很奇怪不知道这玩意什么意思。想要明白这东西你要先知道文本在Android中是如何进行存储和计算的。在Android中文本的绘制需要使用一个bitmap作为单个字符的缓存,既然是缓存必定要使用一定的空间,我们可以通过setLinearText (true)告诉Android我们不需要这样的文本缓存。

setFakeBoldText (boolean fakeBoldText)

设置文本仿粗体

measureText (String text),measureText (CharSequence text, int start, int end),measureText (String text, int start, int end),measureText (char[] text, int index, int count)

测量文本宽度,上面我们已经使用过了,这四个方法都是一样的只是参数稍有不同这里就不撤了!Paint对文本的绘制方法就上面那些,API 21中还新增了两个方法这里就先不讲了,大家可以看到虽然说这些方法很多很多但是效果都是显而易见的,很多方法大家一试就知道所以哥也没有做太多的测试之类什么什么的,这样讲东西是很累的,关于文本也没有什么有趣的Demo可以玩~~~~~so~~~~~Fuck……

下面我们来看一个比较深奧的东西

setDither(boolean dither)

这玩意用来设置我们在绘制图像时的抗抖动,也称为递色,那什么叫抗抖动呢?在Android中我确实不好拿出一个明显的例子,我就在PS里模拟说明一下

大家看到的这张七彩渐变图是一张RGB565模式下图片,即便图片不是很大我们依然可以很清晰地看到在两种颜色交接的地方有一些色块之类的东西感觉很不柔和,因为在RGB模式下只能显示2^16=65535种色彩,因此很多丰富的色彩变化无法呈现,而Android呢为我们提供了抗抖动这么一个方法,它会将相邻像素之间颜色值进行一种“中和”以呈现一个更细腻的过渡色:

放大来看,其在很多相邻像素之间插入了一个“中间值”:

setMaskFilter(MaskFilter maskfilter)

MaskFilter类中没有任何实现方法,而它有两个子类BlurMaskFilter和EmbossMaskFilter,前者为模糊遮罩滤镜(比起称之为过滤器哥更喜欢称之为滤镜)而后者为浮雕遮罩滤镜,我们先来看第一个

BlurMaskFilter

Android中的很多自带控件都有类似软阴影的效果,比如说Button

它周围就有一圈很淡的阴影效果,这种效果看起来让控件更真实,那么是怎么做的呢?其实很简单,使用BlurMaskFilter就可以得到类似的效果,需要关闭硬件加速

EmbossMaskFilter

浮雕效果

setRasterizer (Rasterizer rasterizer)

设置光栅,光栅这东西涉及太多太多物理知识,不讲了一讲又是一大堆,而且该方法同样不支持HW在API 21中遗弃了~~~我们还是来看看对我们来说更好玩有趣的方法

setPathEffect(PathEffect effect)

PathEffect见文知意很明显就是路径效果的意思~~那这玩意肯定跟路径Path有关咯?那是必须的撒!PathEffect跟上面的很多类一样没有具体的实现,但是其有六个子类:

这六个子类分别可以实现不同的路径效果:

上图从上往下分别是没有PathEffect、CornerPathEffect、DiscretePathEffect、DashPathEffect、PathDashPathEffect、ComposePathEffect、SumPathEffect的效果,代码的实现也非常简单:

public class PathEffectView extends View {
    private float mPhase;// 偏移值
    private Paint mPaint;// 画笔对象
    private Path mPath;// 路径对象
    private PathEffect[] mEffects;// 路径效果数组

    public PathEffectView(Context context, AttributeSet attrs) {
        super(context, attrs);

        /*
         * 实例化画笔并设置属性
         */
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setColor(Color.DKGRAY);

        // 实例化路径
        mPath = new Path();

        // 定义路径的起点
        mPath.moveTo(0, 0);

        // 定义路径的各个点
        for (int i = 0; i <= 30; i++) {
            mPath.lineTo(i * 35, (float) (Math.random() * 100));
        }

        // 创建路径效果数组
        mEffects = new PathEffect[7];
    }

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

        /*
         * 实例化各类特效
         */
        mEffects[0] = null;
        mEffects[1] = new CornerPathEffect(10);
        mEffects[2] = new DiscretePathEffect(3.0F, 5.0F);
        mEffects[3] = new DashPathEffect(new float[] { 20, 10, 5, 10 }, mPhase);
        Path path = new Path();
        path.addRect(0, 0, 8, 8, Path.Direction.CCW);
        mEffects[4] = new PathDashPathEffect(path, 12, mPhase, PathDashPathEffect.Style.ROTATE);
        mEffects[5] = new ComposePathEffect(mEffects[2], mEffects[4]);
        mEffects[6] = new SumPathEffect(mEffects[4], mEffects[3]);

        /*
         * 绘制路径
         */
        for (int i = 0; i < mEffects.length; i++) {
            mPaint.setPathEffect(mEffects[i]);
            canvas.drawPath(mPath, mPaint);

            // 每绘制一条将画布向下平移250个像素
            canvas.translate(0, 250);
        }

        // 刷新偏移值并重绘视图实现动画效果
        mPhase += 1;
        invalidate();
    }
}

当我们不设置路径效果的时候路径的默认效果就如上图第一条线那样直的转折生硬;而CornerPathEffect则可以将路径的转角变得圆滑如图第二条线的效果,这六种路径效果类都有且只有一个含参的构造方法,CornerPathEffect的构造方法只接受一个参数radius,意思就是转角处的圆滑程度,我们尝试更改一下上面的代码:

mEffects[1] = new CornerPathEffect(50);

Look Pic是不是更平滑了呢?CornerPathEffect相对于其他的路径效果来说最简单了;DiscretePathEffect离散路径效果相对来说则稍微复杂点,其会在路径上绘制很多“杂点”的突出来模拟一种类似生锈铁丝的效果如上图第三条线,其构造方法有两个参数,第一个呢指定这些突出的“杂点”的密度,值越小杂点越密集,第二个参数呢则是“杂点”突出的大小,值越大突出的距离越大反之反之,大家可以去自己去试下我就不演示了;DashPathEffect的效果相对与上面两种路径效果来说要略显复杂,其虽说也是包含了两个参数,但是第一个参数是一个浮点型的数组,那这个数组有什么意义呢?其实是这样的,我们在定义该参数的时候只要浮点型数组中元素个数大于等于2即可,也就是说上面我们的代码可以写成这样的:

mEffects[3] = new DashPathEffect(new float[] {20, 10}, mPhase);

从图中我们可以看到我们之前的那种线条变成了一长一短的间隔线条,而float[] {20, 10}的偶数参数20(注意数组下标是从0开始哦)定义了我们第一条实线的长度,而奇数参数10则表示第一条虚线的长度,如果此时数组后面不再有数据则重复第一个数以此往复循环,比如我们20,10后没数了,那么整条线就成了[20,10,20,10,20,10…………………………]这么一个状态,当然如果你无聊,也可以:

mEffects[3] = new DashPathEffect(new float[] {20, 10, 50, 5, 100, 30, 10, 5}, mPhase);

而DashPathEffect的第二个参数我称之为偏移值,动态改变其值会让路径产生动画的效果,上面代码已给出大家可以自己去试试;PathDashPathEffect和DashPathEffect是类似的,不同的是PathDashPathEffect可以让我们自己定义路径虚线的样式,比如我们将其换成一个个小圆组成的虚线:

Path path = new Path();
path.addCircle(0, 0, 3, Direction.CCW);
mEffects[4] = new PathDashPathEffect(path, 12, mPhase, PathDashPathEffect.Style.ROTATE);

ComposePathEffect和SumPathEffect都可以用来组合两种路径效果,唯一不同的是组合的方式,ComposePathEffect(PathEffect outerpe, PathEffect innerpe)会先将路径变成innerpe的效果,再去复合outerpe的路径效果,即:outerpe(innerpe(Path));而SumPathEffect(PathEffect first, PathEffect second)则会把两种路径效果加起来再作用于路径,具体区别大家去试试吧…………哥累了睡会~~~囧……

记得在1/12中我们绘制了了一个圆环并让其实现动画的效果,当时我们使用了线程来使其产生动画,但是我们是不是也可以像上面的例子一样直接在onDraw中invalidate()来产生动画呢?这个问题留给大家。

在1/12中我们还说过尽量不要在onDraw中使用new关键字来生成对象,但是上例的代码中我们却在频繁地使用,但是六个PathEffect的子类中除了构造方法什么都没有,我们该如何避免频繁地去new对象呢?这个问题也留给大家思考。

Path应用的广泛性注定了PathEffect应用的广泛,所谓一人得道鸡犬升天就是这么个道理,只要是Path能存在的地方都可以考虑使用,下面我们来模拟一个类似心电图的路径小动画:

这种效果呢也是非常非常地简单,说白了就是无数条短小精悍的小“Path”连接成一条完整的心电路径

我们在onSizeChanged(int w, int h, int oldw, int oldh)方法中获取屏幕的宽高,该方法的具体用法我们会在7/12学习View的测绘时具体说明,这里就先不说了

上面在设置Paint属性的时候我们使用到了一个

setStrokeCap(Paint.Cap cap)

方法,该方法用来设置我们画笔的笔触风格,上面的例子中我使用的是ROUND,表示是圆角的笔触,那么什么叫笔触呢,其实很简单,就像我们现实世界中的笔,如果你用圆珠笔在纸上戳一点,那么这个点一定是个圆,即便很小,它代表了笔的笔触形状,如果我们把一支铅笔笔尖削成方形的,那么画出来的线条会是一条弯曲的“矩形”,这就是笔触的意思。除了ROUND,Paint.Cap还提供了另外两种类型:SQUARE和BUTT,具体大家自己去try~~

setStrokeJoin(Paint.Join join)

这个方法用于设置结合处的形态,就像上面的代码中我们虽说是花了一条心电线,但是这条线其实是由无数条小线拼接成的,拼接处的形状就由该方法指定。

上面的例子中我们还使用到了一个方法

setShadowLayer(float radius, float dx, float dy, int shadowColor)

该方法为我们绘制的图形添加一个阴影层效果:

radius表示阴影的扩散半径,而dx和dy表示阴影平面上的偏移值,shadowColor就不说了阴影颜色,最后提醒一点setShadowLayer同样不支持硬件加速哦!

原文地址:https://www.cnblogs.com/krislight1105/p/5094352.html