Android作为全球最大的操作系统,其界面UI历经这么多版本的改良,现在已经可以说不输于iOS系统了,今天本节内容就来分析UI界面中很关键的一部分——绘图。
Android中实现绘图有两种机制:
①利用Path来绘制2D界面图形;
②利用OpenGL ES来绘制3D图形。
(PS:利用OPENGL ES完全可以处理2D 但是要更复杂)
我们都知道Canva提供了一系列的drawXXX方法,可以实现很多现实中常见的图形,比如,drawPoint, drawPoints,drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc,那为什么有这些方法之后,我们还要单独学习Path类呢?其实很简单,上面的方法确实对于处理一些常见的图形而言非常便利,但其本身没有延展性,如果需要实现更复杂的图形,比如五角星、心形等这些图形就要依靠Path类来实现了。
再来看一下官方文档中的描述:
The Path class encapsulates compound(multiple contour) geometric paths consisting of straight line segments, quadraticcurves, and cubic curves. It canbe drawn with canvas.drawPath(path, paint), either filled or stroked (based onthe paint's Style), or it can be used for clipping or to draw text on a path.
从文档中,我们可以知道,Path即可以绘制直线也可以绘制曲线(二次贝塞尔曲线、三次贝塞尔曲线)。你能用Canvas中的drawPath来把这条路径画出来(同样支持Paint的不同绘制模式),也可以用于剪裁画布和根据路径绘制文字。
这里给出所有常见常用的Path类的方法,如下表:
方法 |
作用 |
备注 |
moveTo |
移动起点 |
移动下一次操作的起点位置 |
lineTo |
连接直线 |
连接上一个点到当前点之间的直线 |
setLastPoint |
设置终点 |
重置最后一个点的位置 |
close |
闭合路劲 |
从最后一个点连接最初的一个点,形成一个闭合区域 |
addRect |
添加矩形 |
添加矩形到当前Path |
addRoundRect |
添加圆角矩形 |
添加圆角矩形到当前Path |
addOval |
添加椭圆 |
添加椭圆到当前Path |
addCircle |
添加圆 |
添加圆到当前Path |
addPath |
添加路径 |
添加路径到当前Path |
addArc |
添加圆弧 |
添加圆弧到当前Path |
arcTo |
圆弧 |
绘制圆弧,注意和addArc的区别 |
isEmpty |
是否为空 |
判定Path是否为空 |
isRect |
是否为矩形 |
判定Path是否是一个矩形 |
set |
替换路径 |
用新的路劲替换当前路径的所有内容 |
offset |
偏移路径 |
对当前的路径进行偏移 |
quadTo |
贝塞尔曲线 |
二次贝塞尔曲线的方法 |
cubicTo |
贝塞尔曲线 |
三次贝塞尔曲线的方法 |
rMoveTo,rlineTo, |
rXxx方法 |
不带r的方法是基于原点坐标系(偏移量),带r的基于当前点坐标系(偏移量) |
op |
布尔操作 |
对两个Path进行布尔运算(交集,并集)等操作 |
setFillType |
填充模式 |
设置Path的填充模式 |
getFillType |
填充模式 |
获取Path的填充 |
isInverseFillType |
是否逆填充 |
判断是否是逆填充模式 |
toggleInverseFillType |
相反模式 |
切换相反的填充模式 |
getFillType |
填充模式 |
获取Path的填充 |
incReserve |
提示方法 |
提示Path还有多少个点等待加入 |
computeBounds |
计算边界 |
计算Path的路劲 |
reset,rewind |
重置路劲 |
清除Path中的内容(reset相当于new Path , rewind 会保留Path的数据结构) |
transform |
矩阵操作 |
矩阵变换 |
|
(1)moveTo、 setLastPoint、 lineTo 和 close
lineTo:
|
这个方法很简单,从字面上我们也可以理解到这个方法是用来画线的,两点确定一条直线,但是方法里面只传入一个参数,这个参数是线的终点的坐标,线的起点坐标要么是默认的坐标原点,要么是使用moveTo方法确定好的坐标起点。
|
两个方法看似传入的参数类似,但实际上是完全不同的东西,第一个方法是用于设置起点坐标的,和前面的lingTo搭配使用,后者是用于重置终点坐标的,即尽管使用过lingTo确认过一次终点坐标,但使用过setLastPoint之后还是会以后者中设置的终点坐标为准。
|
Close()方法用于连接当前最后一个点和最初的一个点(如果不重合的话),形成一个封闭的图形。
|
注意:close的作用是封闭路径,与连接当前最后一个点和第一个点并不等价。如果连接了最后一个点和第一个点仍然无法形成封闭图形,则close什么 也不做。
(2)addRect、addRoundRect、addOval、addCircle、addPath、addArc和arcTo
总共是6个addXXX方法和1个arcTo方法,这里我们分成三组进行分析:
|
这些方法就是添加基本的形状,和使用Canvas的drawXXX方法是一样使用的,只不过在使用Path时多了一个参数Path.Direction dir,这个参数用于指定画图的方向:
CW——顺时针
CCW——逆时针
我们通过指定画图的方向指定绘制点的坐标的顺序,所以这个参数非常重要。
|
这个相对比较简单,也很容易理解,就是将两个Path合并成为一个。
第三个方法是将src添加到当前path之前先使用Matrix进行变换。
第二个方法比第一个方法多出来的两个参数是将src进行了位移之后再添加进当前path中。
|
首先我们新建地方两个Path(矩形和圆形)中心都是坐标原点,我们在将包含圆形的path添加到包含矩形的path之前将其进行移动了一段距离,最终绘制出来的效果就如上面所示。
|
参数说明:
RectF oval——圆弧的外切矩形。
startAngle——开始角度
sweepAngle——扫过角度(-360<= sweepAngle <360)
forceMoveTo——是否强制使用MoveTo
addArc与arcTo两个方法都是用于在路径中添加圆弧,其区别在于:前者直接将圆弧添加到路径中去,不用管二者的位置关系;而arcTo方法通过布尔型参数forceMoveTo的指定,关联起圆弧和路径的位置关系。
(3)isEmpty、 isRect、isConvex、set 和 offset
这五个方法比较简单,前面三个方法用于做出判断,后面的set方法用于重置整条路径,而offset用法用于对路径进行平移。
最后,还有两个关键的曲线相关方法二阶贝塞尔曲线方法quadTo 、三阶贝塞尔曲线方法cubicTo,难度有蛮大,笔者计划单列出来讲解,那么就不在本篇文章中涉及了。
如上文所述,Android的Path类是一个非常有用的类,它可以预先在View上将N个点连成一条“路径”,然后调用Canvas的drawPath(path, paint)方法沿着路径绘制图形。我们观察上面的例子就会发现,形状我们已经可以做出来了,之后我们对画出来的路径做特效处理,当然可以Android还为路径绘制提供了PathEffect来定义绘制特效,PathEffect包含了如下子类(每个子类代表一种绘制效果):
CornerPathEffect——圆滑路径线
DiscretePathEffect——杂点路径线
DashPathEffect——杂点间隔路径线
PathDashPathEffec——规则点间隔路径线
ComposePathEffect——特效组合线
实现代码如下,具体参考自《Android群英传》:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyView(this));
}
class MyView extends View{
float phase;
PathEffect[] pathEffects = new PathEffect[7];
int[] colors;
private Paint paint;
Path path;
public MyView(Context context) {
super(context);
paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(4);
//创建并初始化Path
path = new Path();
path.moveTo(0, 0);
for(int i=1; i<=40; i++){
//生成40个点,随机生成他们的Y坐标,并将他们连城一条Path
path.lineTo(i*20, (float)Math.random()*60);
}
//初始化7个一颜色
colors = new int[]{Color.RED, Color.YELLOW,
Color.GREEN, Color.BLACK, Color.BLUE, Color.LTGRAY,Color.CYAN};
}
@Override
protected void onDraw(Canvas canvas) {
//将背景填充成白色
canvas.drawColor(Color.WHITE);
//---------------下面开始画7种路径效果------------
pathEffects[0] = null;
pathEffects[1] = new CornerPathEffect(10);
pathEffects[2] = new DiscretePathEffect(3.0f, 5.0f);
pathEffects[3] = new DashPathEffect(new float[]{20, 10, 5, 10}, phase);
Path p = new Path();
p.addRect(0, 0, 8, 8, Path.Direction.CCW);
pathEffects[4] = new PathDashPathEffect(p, 12, phase, PathDashPathEffect.Style.ROTATE);
//初始化ComposePathEffect
pathEffects[5] = new ComposePathEffect(pathEffects[2], pathEffects[4]);
pathEffects[6] = new SumPathEffect(pathEffects[4], pathEffects[3]);
//将画布移动到(8, 8)处开始绘制,后面的操作都以(8,8)作为参照点,默认原点为(0,0)
canvas.translate(80, 80);
//依次使用7种不同的路径效果、7种不同的颜色来绘制
for(int i=0; i<7; i++){
paint.setPathEffect(pathEffects[i]);
paint.setColor(colors[i]);
canvas.drawPath(path, paint);
canvas.translate(0, 200);
}
//改变phase值,形成动画效果
phase += 1;
invalidate();
}
}
}