自定义控件之绘图篇(七) —— Paint之函数大汇总

前言

基本用法

概述

经过前几篇,我们基本把PaintCanvas的基本用法就看完了,今天我们来个大汇总,列举一个Paint的所有函数,然后一个一个的过。经过这几篇,你将能学会Paint中所有处理函数的用法。

我们先来看一下Paint中基本设置的函数都有哪些

  • reset():重置画笔

  • setColor(int color):给画笔设置颜色值

  • setARGB(int a, int r, int g, int b):同样是设置颜色,但是利用ARGB分开设置

  • setAlpha(int a):设置画笔透明度

  • setStyle(Paint.Style style):设置画笔样式,取值有

    • Paint.Style.FILL:填充内部
    • Paint.Style.FILL_AND_STROKE:填充内部和描边
    • Paint.Style.STROKE:仅描边
  • setStrokeWidth(float width):设置画笔宽度

  • setAntiAlias(boolean aa):设置画笔是否抗锯齿

  • setStrokeCap(Paint.Cap cap):设置线冒样式,取值有

    • Cap.ROUND:圆形线冒
    • Cap.SQUARE:方形线冒
    • Cap.BUTT:无线冒
  • setStrokeJoin(Paint.Join join):设置线段连接处样式,取值有

    • Join.MITER:结合处为锐角
    • Join.Round:结合处为圆弧
    • Join.BEVEL:结合处为直线
  • setStrokeMiter(float miter):设置笔画的倾斜度,90度拿画笔与30拿画笔,画出来的线条样式肯定是不一样的吧(事实证明,根本看不出来什么区别好吗……囧……)

  • setPathEffect(PathEffect effect):设置路径样式,取值类型是所有派生自PathEffect的子类

    • ComposePathEffect
    • CornerPathEffect
    • DashPathEffect
    • DiscretePathEffect
    • PathDashPathEffect
    • SumPathEffect

后面四个函数中,setStrokeMiter(float miter)就不再讲了,我做过试验,没什么变化,也就是没啥用,我们分别来看看另外三个函数的具体用法。

setStrokeCap(Paint.Cap cap)

该函数使用来设置线帽样式。我先不讲什么叫做线冒,大家先来看看下面这段代码以及它的效果:

Paint paint = new Paint();  
paint.setStrokeWidth(80);  
paint.setAntiAlias(true);  
paint.setColor(Color.GREEN);  
paint.setStyle(Paint.Style.STROKE);  


paint.setStrokeCap(Paint.Cap.BUTT);  
canvas.drawLine(100,200,400,200,paint);  

paint.setStrokeCap(Paint.Cap.SQUARE);  
canvas.drawLine(100,400,400,400,paint);  

paint.setStrokeCap(Paint.Cap.ROUND);  
canvas.drawLine(100,600,400,600,paint);  

//垂直画出x=100这条线  
paint.reset();  
paint.setStrokeWidth(2);  
paint.setColor(Color.RED);  
canvas.drawLine(100,50,100,750,paint);

在这里,我们水平画了三条线,分别应用了三种线冒,最后,垂直画出x=100的那条起始线

这里写图片描述

从效果图中可以明显看出,从无线冒多出来的那块区域就是线帽!就相当于给原来的直线加上一个帽子一样,所以叫线帽。

Android的线冒样式是很少的,只有方形和圆形两种,而在Windows SDK中,线冒样式多达十几种

这里写图片描述

具体详情见博客:《GDI+学习及代码总结之—–画笔》

setStrokeJoin(Paint.Join join)

参数取值有:

  • Join.MITER:结合处为锐角

  • Join.Round:结合处为圆弧

  • Join.BEVEL:结合处为直线

网上说,他们三个的区别如下

这里写图片描述

但我运行出来的效果却不是如此,Join.RoundJoin.BEVEL没有明显的区别。

我们画出来三个锐角的Path,分别给这三段Path设置不同的连接方式

Paint paint = new Paint();  
paint.setStrokeWidth(40);  
paint.setColor(Color.GREEN);  
paint.setStyle(Paint.Style.STROKE);  
paint.setAntiAlias(true);  

Path path  = new Path();  
path.moveTo(100,100);  
path.lineTo(450,100);  
path.lineTo(100,300);  
paint.setStrokeJoin(Paint.Join.MITER);  
canvas.drawPath(path,paint);  

path.moveTo(100,400);  
path.lineTo(450,400);  
path.lineTo(100,600);  
paint.setStrokeJoin(Paint.Join.BEVEL);  
canvas.drawPath(path,paint);  

path.moveTo(100,700);  
path.lineTo(450,700);  
path.lineTo(100,900);  
paint.setStrokeJoin(Paint.Join.ROUND);  
canvas.drawPath(path,paint);

这里写图片描述

setPathEffect(PathEffect effect)

设置路径样式,取值类型是所有派生自PathEffect的子类

这里写图片描述

我们一个个来看他们的效果。

CornerPathEffect —— 圆形拐角效果

它的作用就是将原来Path生硬的直线拐角,变成圆形拐角

这里写图片描述

上面的那条是Path默认的直线拐角,下面的那条是圆形拐角,其构造函数为

public CornerPathEffect(float radius)

它只有一个参数radius,即当前连接两条直线所使用的圆的半径。

这里写图片描述

上面这个图,很清晰的展示了利用半径r=50的圆来代替原来两条直线间的夹角。我们利用代码,再来看看具体效果

Paint paint = new Paint();  
paint.setStrokeWidth(4);  
paint.setColor(Color.GREEN);  
paint.setStyle(Paint.Style.STROKE);  

Path path = new Path();  
path.moveTo(100,600);  
path.lineTo(400,100);  
path.lineTo(700,900);  

canvas.drawPath(path,paint);  

paint.setColor(Color.RED);  
paint.setPathEffect(new CornerPathEffect(100));  
canvas.drawPath(path,paint);  

paint.setColor(Color.YELLOW);  
paint.setPathEffect(new CornerPathEffect(200));  
canvas.drawPath(path,paint);

在这里,我利用Path构造了一个夹角,在同一个位置画了三遍,第一遍是没有添加任何PathEffect的,第二遍,CornerPathEffect的圆半径为100,第三遍CornerPathEffect的圆半径为200。

这里写图片描述

很明显能看出在半径不同情况下,连接位置也是不一样的。

DashPathEffect —— 虚线效果

这个功能能够实现虚线段的效果

这里写图片描述

它的函数声名如下

public DashPathEffect(float intervals[], float phase)

其中,intervals[]表示组成虚线的各个线段的长度,整条虚线就是由intervals[]中这些基本线段循环组成的。比如,我们定义new float[]{20,10},那这个虚线段就是由两段线段组成的,第一个可见的线段长为20,每二个线段不可见,长度为10

这里写图片描述

对于intervals[]数组有两个限定

  • 长度必须大于等于2;因为必须有一个实线段和一个空线段来组成虚线

  • 个数必须为偶数,如果是基数,最后一个数字将被忽略,这个很好理解,因为一组虚线的组成必然是一个实线和一个空线成对组成的

我们再做一个假设,如果我们定义intervals[]为new float[]{20, 10, 100, 100},那么这条虚线将是由四条子线段循环组成的,第一条实线长度为20,第二个空线长度为10,第三个实线长为100,第四条空线长充为100。

phase表示开始绘制的偏移值,我们来看看代码的运行效果来验证我们想的是否正确

Paint paint = getPaint();  
Path path = new Path();  
path.moveTo(100,600);  
path.lineTo(400,100);  
path.lineTo(700,900);  

canvas.drawPath(path,paint);  
paint.setColor(Color.RED);  

//使用DashPathEffect画线段  
paint.setPathEffect(new DashPathEffect(new float[]{20,10,100,100},0));  
canvas.translate(0,100);  
canvas.drawPath(path,paint);  

//画同一条线段,偏移值为15  
paint.setPathEffect(new DashPathEffect(new float[]{20,10,50,100},15));  
paint.setColor(Color.YELLOW);  
canvas.translate(0,100);  
canvas.drawPath(path,paint);

其中把Paint封装成一个getPaint()方法来获取基本的画笔设置

private Paint getPaint(){  
    Paint paint = new Paint();  
    paint.setStrokeWidth(4);  
    paint.setColor(Color.GREEN);  
    paint.setStyle(Paint.Style.STROKE);  
    paint.setAntiAlias(true);  
    return paint;  
}

效果图如下

这里写图片描述

从这个效果图中可以看到两点

  • 第一,红线段的基本组成部分,分别长度为{20, 10, 100, 100}实线段和空线段组成的

  • 第二,黄线段位移了15,从开始处就可以明显看出效果。原来20的线段,只剩5,所以看起来就像一个点一样。大家也可以发挥想象,利用动画设置偏移量让这条虚线段动起来:

这里写图片描述

给大家一个提示,使用ValueAnimator,动画长度值设为一个虚线的一个基线的长度,这里的基线是由{20, 10, 100, 50}组成的,所以一个基线长度是180,然后设置成无限循环,把拿到的值设置为DashPathEffect偏移量即可。

如果看完了我的动画系列,这个小动画是不会成问题的,这里就不再讲了,不懂的同学,看源码吧,源码里有这个动画。

DiscretePathEffect —— 离散路径效果

这里写图片描述

同样,图中第一条线是原生的,第二条线加上离散路径效果后的样式。DiscretePathEffect就是将原来路径分隔成定长的线段,然后将每条线段随机偏移一段位置,我们可以用它来模拟一种类似生锈铁丝的效果,它的构造函数如下

public DiscretePathEffect(float segmentLength, float deviation)

  • segmentLength:表示将原来的路径切成多长的线段。如果值为2,那么这个路径就会被切成一段段由长度为2的小线段。所以这个值越小,所切成的小线段越多,这个值越大,所切成的小线段越少。

  • deviation:表示被切成的每个小线段的向外偏移距离。值越大,就表示每个线段的可偏移距离就越大,就显得越凌乱,值越小,每个线段的可偏移原位置的距离就越小。

我们看下代码效果

Paint paint = getPaint();  
Path path = getPath();  
//第一条原生Path  
canvas.drawPath(path,paint);  
//第二条Path  
canvas.translate(0,200);  
paint.setPathEffect(new DiscretePathEffect(2,5));  
canvas.drawPath(path,paint);  
//第三条Path  
canvas.translate(0,200);  
paint.setPathEffect(new DiscretePathEffect(6,5));  
canvas.drawPath(path,paint);  
//第四条Path  
canvas.translate(0,200);  
paint.setPathEffect(new DiscretePathEffect(6,15));  
canvas.drawPath(path,paint);

我们这里涉及到一个函数getPath()函数就是随机生成一条路径。我们先来看效果,然后再回来看getPath()的实现

这里写图片描述

从第二条和第三条相比,可以明显看出,在仅增大segmentLength的情况下,很明显第三条线段所切的子线段要大一些,所以就没有第二条曲线的那么多线段相交所产生的折点,所以相比第二条更顺滑一些,当然铁锈效果就没有第二条那么明显了。

第三条和第四条相比,在segmentLength都是6的情况下,在第四条仅增大了deviation参数(偏移距离),从效果图中也明显可以看出每个子线段向外偏移的距离也增大了。

从效果图中很明显可以看出各个参数的作用,这里就不多讲了,下面回过头来看看getPath()得到一个随机路径的方法

private Path getPath(){  
    Path path = new Path();  
    // 定义路径的起点  
    path.moveTo(0, 0);  

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

就是利用Math.random()产生一个随机数,然后每隔35px构造出一个点,代码难度不大,不再细讲。

PathDashPathEffect —— 印章路径效果

这个名字是我自己取的,因为这个功能就相当于PhotoShop中的印章功能。

它的作用就是用另一个路径图案做为印章,沿着指定路径一个个盖上去

我们先来看看PathDashPathEffect的声明

public PathDashPathEffect(Path shape, float advance, float phase,Style style)

其中

  • shape:表示印章路径,比如我们下面示例中的三角形加右上角一个点

  • advance:表示两个印章路径间的距离,很容易理解,印章间距离越大,间距就越大

  • phase:路径绘制偏移距离,与上面DashPathEffect中的float phase参数意义相同

  • style:表示在遇到转角时,如何操作印章以使转角平滑过渡,取值有

    • Style.ROTATE:表示通过旋转印章来过渡转角
    • Style.MORPH:表示通过变形印章来过渡转角
    • Style.TRANSLATE:表示通过位移来过渡转角

然后我们看看如何来实现下面这个效果

这里写图片描述

如上图所示,印章路径就是一个三角形加右上角一个点,然后就拿这个印章路径在原来的路径上每隔一定距离盖章,上图的对应代码为

Paint paint = getPaint();  

//画出原始路径  
Path path  = new Path();  
path.moveTo(100,600);  
path.lineTo(400,100);  
path.lineTo(700,900);  
canvas.drawPath(path,paint);  

//构建印章路径  
Path stampPath  = new Path();  
stampPath.moveTo(0,20);  
stampPath.lineTo(10,0);  
stampPath.lineTo(20,20);  
stampPath.close();  
stampPath.addCircle(0,0,3, Path.Direction.CCW);  

//使用印章路径效果  
canvas.translate(0,200);  
paint.setPathEffect(new PathDashPathEffect(stampPath,35,0, PathDashPathEffect.Style.TRANSLATE));  
canvas.drawPath(path,paint);

最重要的是最后几句

//构建印章路径  
Path stampPath  = new Path();  
stampPath.moveTo(0,20);  
stampPath.lineTo(10,0);  
stampPath.lineTo(20,20);  
stampPath.close();  
stampPath.addCircle(0,0,3, Path.Direction.CCW);  

//使用印章路径效果  
canvas.translate(0,200);  
paint.setPathEffect(new PathDashPathEffect(stampPath,35,0, PathDashPathEffect.Style.TRANSLATE));

首先构造一个印章路径,然后利用

paint.setPathEffect(new PathDashPathEffect(stampPath,35,0, PathDashPathEffect.Style.TRANSLATE));

来构造一个印章画笔,然后就利用这个画笔在指定路径上画图

canvas.drawPath(path,paint);

在示例中我们有两个路径,大家一定要把路径和印章路径分清楚,一个路径是path,这个是原始路径,另一个路径是stampPath,这个是印章路径。印章路径是用来构造画笔的(paint),而原始的路径就是用这个画笔来做画的。

下面我们就来看看,在Style不同的情况下,在转角处都如何处理的

private void drawPathDashPathEffect(Canvas canvas){  
    Paint paint = getPaint();  
    Path path = getPath();  
    canvas.drawPath(path,paint);  

    canvas.translate(0,200);  
    paint.setPathEffect(new PathDashPathEffect(getStampPath(),35,0, PathDashPathEffect.Style.MORPH));  
    canvas.drawPath(path,paint);  

    canvas.translate(0,200);  
    paint.setPathEffect(new PathDashPathEffect(getStampPath(),35,0, PathDashPathEffect.Style.ROTATE));  
    canvas.drawPath(path,paint);  

    canvas.translate(0,200);  
    paint.setPathEffect(new PathDashPathEffect(getStampPath(),35,0, PathDashPathEffect.Style.TRANSLATE));  
    canvas.drawPath(path,paint);  
}  

private Path getStampPath(){  
    Path path  = new Path();  
    path.moveTo(0,20);  
    path.lineTo(10,0);  
    path.lineTo(20,20);  
    path.close();  

    path.addCircle(0,0,3, Path.Direction.CCW);  
    return path;  
}

这段代码通过getPath()函数随机生成一条路径,并将原始路径和各个Style的路径画出来。第一条是原始路径,第二条的StyleStyle.MORPH,第三条是Style.ROTATE,第四条是Style.TRANSLATE

这里写图片描述

大家第一眼估计就看得眼花缭乱了,仔细看一下红线处的三角形的样式

这里写图片描述

同样是转角,如上图,当Style.MORPH时,就是通过对印章进行变形来过渡转角的

这里写图片描述

StyleStyle.ROTATE时就是靠旋转印章角度来过渡转角的

这里写图片描述

StyleStyle.TRANSLATE时,即不会对印章变形也不会对旋转印章角度,而只是通过变改印章的位置来过渡。

其中的一个参数float phase,表示路径绘制偏移距离我们还没讲,这个与上面的偏移距离的意义一样,通过改变偏移距离同样可以实现动画

这里写图片描述

ComposePathEffect与SumPathEffect

这两个都是用来合并两个特效的,但它们之间是有区别的

public ComposePathEffect(PathEffect outerpe, PathEffect innerpe)

ComposePathEffect合并两个特效是有先后顺序的,它会先将第二个参数PathEffect innerpe的特效作用于路径上,然后再在此加了特效的路径上作用第二个特效。

public SumPathEffect(PathEffect first, PathEffect second)

SumPathEffect是分别对原始路径分别作用第一个特效和第二个特效,然后再将这两条路径合并,做为最终结果。 我们来看看效果

//画原始路径  
Paint paint = getPaint();  
Path path = getPath();  
canvas.drawPath(path,paint);  

//仅应用圆角特效的路径  
canvas.translate(0,200);  
CornerPathEffect cornerPathEffect = new CornerPathEffect(100);  
paint.setPathEffect(cornerPathEffect);  
canvas.drawPath(path,paint);  

//仅应用虚线特效的路径  
canvas.translate(0,200);  
DashPathEffect dashPathEffect = new DashPathEffect(new float[]{2,5,10,10},0);  
paint.setPathEffect(dashPathEffect);  
canvas.drawPath(path,paint);  

//利用ComposePathEffect先应用圆角特效,再应用虚线特效  
canvas.translate(0,200);  
ComposePathEffect composePathEffect = new ComposePathEffect(dashPathEffect,cornerPathEffect);  
paint.setPathEffect(composePathEffect);  
canvas.drawPath(path,paint);  

//利用SumPathEffect,分别将圆角特效应用于原始路径,然后将生成的两条特效路径合并  
canvas.translate(0,200);  
paint.setStyle(Paint.Style.STROKE);  
SumPathEffect sumPathEffect = new SumPathEffect(cornerPathEffect,dashPathEffect);  
paint.setPathEffect(sumPathEffect);  
canvas.drawPath(path,paint); 

代码很简单,就是画几条特效路径 ,效果图如下

这里写图片描述

特别注意路径D和路径E,路径D的生成方法为

ComposePathEffect(dashPathEffect,cornerPathEffect);

表示先将圆角特效应用于原始路径,得到路径B,然后再在路径B的基础上应用虚线特效得到最终的效果D。而路径E的生成方法则比较弱智

SumPathEffect(cornerPathEffect,dashPathEffect);

它的原理是,先将圆角特效应用于原始路径A得到路径B,然后将虚线特效应依然应用于原始路径,得到路径C,然后将路径B和路径C合并(即画在一起……囧),就得到路径E。

好了,到这里,所有的路径效果都讲完了。

字体相关

  • setTextSize(float textSize):设置文字大小

  • setFakeBoldText(boolean fakeBoldText):设置是否为粗体文字

  • setStrikeThruText(boolean strikeThruText):设置带有删除线效果

  • setUnderlineText(boolean underlineText):设置下划线

  • setTextAlign(Paint.Align align):设置开始绘图点位置

  • setTextScaleX(float scaleX):水平拉伸设置

  • setTextSkewX(float skewX):设置字体水平倾斜度,普通斜体字是-0.25,可见往右斜

  • setTypeface(Typeface typeface):字体样式

上面的这些方法,我们在《自定义控件之绘图篇(二) —— 路径及文字》《自定义控件之绘图篇(五) —— drawText()详解》已经详细讲过,这里就不再细讲,下面补充几个没讲到的函数。

setLinearText(boolean linearText)

设置是否打开线性文本标识。由于文本想要快速绘制出来,必然是需要提前缓存在显存中的,一般而言每个文字需要一个字节的大小来存储它(当然具体需要多少字节与编码方式有关),那如果是长篇文章,所需的大小可想而知。我们可以通过setLinearText(true)告诉Android我们不需要这样的文本缓存。但如果我们不用文本缓存,虽然能够省去一些内存空间,但这是以显示速度为代价的。

由于这个是API 1的函数,由于当时的Android手机的内存大小还是很小的,所以尽量减少内存使用是每个应用的头等大事,在当时的的环境下这个函数还是很有用的。

但在今天,内存动不动就是4G以上了,文本缓存的所占的那点内存就微不足道了,没有哪个APP会牺牲性能来减少这点这内存占用了,所以这个函数基本没用了。

setSubpixelText(boolean subpixelText)

表示是否打开亚像素设置来绘制文本。亚像素的概念比较难理解,首先,我们都知道像素,比如一个Android手机的分辨率是1280*720,那就是指它的屏幕在垂直方向有1280个像素点,水平方向上有720个像素点。我们知道每个像素点都是一个独立显示一个颜色的个体。所以如果一副图片,在一个屏幕上用了300*100个相素点,而在另一个屏幕上却用了450*150个像素来显示。那么,请问在哪个屏幕上这张图片显示的更清晰?当然是第二个屏幕,因为它使用的像素点更多,所显示的细节更精细。

那么问题来了,Android设置在出厂时,设定的像素显示都是固定的几个范围:320*480,480*800,720*1280,1080*1920等等。那么如何在同样的分辨率的显示器中增强显示清晰度呢?

亚像素的概念就油然而生了,亚像素就是把相邻的两个像素之间的距离再细分,再插入一些像素,这些通过程序加入的像素就是亚像素。在两个像素间插入的像素个数是通过程序计算出来的,一般是插入两个、三个或四个。

所以打开亚像素显示,是可以在增强文本显示清晰度的,但由于插入亚像素是通过程序计算而来的,所以会耗费一定的计算机性能。

亚像素是通过程序计算出来模拟插入的,在没有改变硬件构造的情况下,来改善屏幕分辨率大小。

亚像素显示,是仅在液晶显示器上使用的一种增强字体清晰度的技术。但这种技术有时会出现问题,用投影仪投射到白色墙壁上,会出现字体显示不正常的情况,而且对于老式的CRT显示器是根本不支持的。

在Android还没有出现时,Windows已经能够支持亚像素显示了,在Windows机器中,这个功能叫做ClearType,在以前讲述Windows的GDI绘图时,也曾经讲过ClearType的应用效果。

这里写图片描述

这个图片中的最后一行文字,就是在Windows上应用CLearType技术后的显示效果。此图片摘自《 GDI+学习及代码总结之——文本与字体》,可见在Windows的显示中,还是有明显差别的,但我放在Android里面来试,并未发现明显的差别,可能是我的显示器分辨率太高了……汗……

Paint paint = new Paint();  
paint.setColor(Color.GREEN);  
String text = "乌龟&梦想";  
paint.setTextSize(200);  

paint.setSubpixelText(false);  
canvas.drawText(text,0,200,paint);  

canvas.translate(0,300);  
paint.setSubpixelText(true);  
canvas.drawText(text,0,200,paint); 

效果图如下

这里写图片描述

其它

接下来还剩几个跟图片和测量相关的函数,我们接下来分篇慢慢讲解。

图像处理
  • setShader(Shader shader)

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

  • setDither(boolean dither)

  • setColorFilter(ColorFilter filter)

  • setXfermode(Xfermode xfermode)

  • setFilterBitmap(boolean filter)

  • clearShadowLayer()

measure测量相关
  • breakText(char[] text, int index, int count, float maxWidth, float[] measuredWidth)

  • measureText(String text)

好了,本文就到这了。

原文链接:点击这里

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值