26
2017
09

自定义View从入门到懵逼系列(上)

传送门

系列文章传送门

为了以后更快更好地回忆利用此系列内容,在对此系列进行了深入地学习之后对其进行一定的总结,方便日后对自定义View知识的串联。

自定义View系列知识点总结出来两篇:
上篇包括:坐标系和角度弧度、Canvas-基本图形、Canvas-图片文字、Canvas-基础绘画辅助、Path基础、PathMeasure、贝塞尔曲线、Matrix原理、Matrix详解、Camera实现3D效果、Region实现特殊形状。
下篇包括:Paint类、绘制顺序、自定义View的分类和流程概述、事件的分发、属性动画。

此连接时根据日常View学习所做的View的demo,供大家参考。


1. 坐标系和角度弧度

1.1 屏幕坐标系

以屏幕的左上角为原点,X轴向右为正,Y轴向下为正,用一个图来清晰地展示。

这里写图片描述

1.2 View坐标系

其坐标系是相对于父控件而言的,特别注意getRight()和getBottom()。

这里写图片描述

1.3 MotionEvent中的getX()、getRawX()

getX()相对于父控件而言的距离,getRawX()是在屏幕上的绝对距离。

1.4 角度和弧度

角度deg的360°表示一周,弧度rad的2π表示一周,所以360°=2π,所以deg/360=rad/2π,我们可以根据此公式求对应的值。

这里写图片描述


2. Canvas-基本图形

基本图形有很多用法很简单所以我们只列出其方法不再阐述其用法。

drawColor()-颜色、drawPoint()-点、drawLine()-线、drawRect()-矩形、drawRoundRect()-圆角矩形、drawOval()-椭圆、drawCircle()-圆、drawArc()-圆弧等。


3. Canvas-图片文字

3.1 drawPicture

这个名字乍一看像是绘制图片其实并不是,我们可以把它理解成一个录像机,如果我们需要重复绘制一套图形就可以将这套绘制的Canvas保存在Picture录像机中,每次需要时直接调用录像机绘制即可。
就像录像机一样,Picture提供了beginRecording()开始录制和endRecording()结束录制的方法。我们通过beginRecording获取一个Canvas对象,然后在利用这个Canvas绘制出我们需要的东西,再停止绘制,最终调用drawPicture将录制内容显示出来。
区域缩放适应,可以利用PictureDrawable的setBounds来控制缩放。

3.2 drawBitmap

drawBitmap()方法就是将各种途径获取的bitmap绘制到画布上,它有这样三类方法。

  • 1:drawBitmap(Bitmap b, Matrix m, Paint p);
    此处Matrix为矩阵变换,我们暂且只用单位矩阵(new Matrix);
  • 2:drawBitmap(Bitmap b, float left, float top, Paint p);
    此处就是规定图片左上角的位置。
  • 3:drawBitmap(Bitmap b, Rect src, Rect dst, Paint p);
    src代表我们要绘制图片的区域,比如src为(0, 0, b.w/2, b.h/2)就代表我们只想绘制图片的左上角的1/4;
    dst代表应该将图片绘制到屏幕的哪个区域,比如dst(0, 0, 200, 200)代表我们需要将图片绘制到这个矩形中;
    需要注意的一点就是,要绘制的图片需要根据屏幕中划定的区域进行自定缩放来适应这个区域。
    我们可以利用这个特性让一个图像一点点显示从而展现出动画效果。

3.3 drawText

总共分为三类:drawText()、drawPosText()、drawTextOnPath(),绘制文字需要注意的就是文字的基准线是在起始文字的左下方偏左一点点。


4. Canvas-绘画辅助

4.1 范围的裁切

范围的裁切clipRect()、clipPath()等方法,注意要配合save(),restore()方法使用。

4.2 几何变换

几何变化分三种:

  • 利用封装好的Matrix实现平移、缩放、旋转、错切,对应的方法translate()、scale()、rotate()、skew();
  • 利用自定义的Matrix矩阵来实现自定义的变化,后面会专门讲到;
  • 利用Camera来实现3D效果的转换,后面也会专门讲到。

5. Path-基础

将一段路径保存到Path变量中,利用canvas统一处理。

5.1 基础方法

moveTo()-将点移动到某个点;
lineTo(),当前操作点和目标操作点连接成线;
close(),闭合路线;
setLasstPoint(),重置上次操作的最后一个点;
诸如rMoveTo()以r开头的方法表示以当前点为参照,没有r以坐标原点为参照。

5.2 基本形状

addCircle()、addOval()、addRect()和Canvas的形状相对应,不过利用Path绘制出的图形就是方向的,CW代表顺时针,CCW代表逆时针,这和Path的填充模式是相关联的。
addArc,直接添加圆弧;
arcTo(),最后参数为true表示不连接最后的点到圆弧,为false表示连接。

5.3 填充模式

填充模式分为两种,奇偶规则和非零环绕数规则。
奇偶规则(EVEN_ODD):点开始射线和Path相交的点的个数是奇数表示该点在Path内,为偶数表示在Path外。
非零环绕数规则(WINDING):以0位基数,点射线和Path相交的点,从左向右相交+1,从右往左相交-1,最终的数非0表示点在Path内,否则在外。

5.4 其它方法

isEmpty()、isRect()、set()、reSet()、computeBounds()计算边界。

5.5 布尔操作op

两个Path之间可以进行布尔操作,布尔操作分为5种:path1.op(path2, Op.mode)。

  • DIFFERENCE,差,path1减去path2剩下的部分;
  • REVERSE_DIFFERENCE,反差path2减去path1剩下的部分;
  • INTERSECT,相交,path1和path2相交的部分;
  • UNION,并集,path1和path2的所有部分;
  • XOR,异或,包括path1和path2所有部分,但是减去其相交的部分。

6. Path-PathMeasure

一个用来测量Path的类,利用它我们可以玩出很多花样,记住在自定义View中PathMeasure 大有用处

6.1 构造函数和一般方法

PathMeasure(),创建一个空的对象,然后利用setPath()设置。
PathMeasure(Path path, boolean b),创建一个和path相关联的对象,b为true闭合该Path路径但是不改变Path的值。
setPath()、isClose()。

6.2 其它方法

  • getLength()-路径的总长度;
  • getSegment(float startD, float stopD, Path dst, boolean b)获取Path片段;
    startD开始截取的位置到起点的长度;
    stopD截取结束的位置到起点的长度;
    dst截图后的路径添加(而非替换)至dst中;
    b为false代表将startD位置移动到dst的起点,为true保持不变。
  • nextContour,Path多条曲线不想连,它用于跳转;
  • getPosTan,这个方法是用于得到路径上某一长度的位置以及该位置的正切值;
  • getMatrix,用于得到路径上某一长度的位置以及该位置的正切值的矩阵。

利用getLength和getSegment配合可以做出很多效果,比如一个点绕圆旋转,利用圆的长度不停地改变点的位置即可,在代码中有View09放大镜就是这一原理实现的。


6. Path-贝塞尔曲线

有关贝塞尔曲线的讲解需要阅读原文,很难提炼知识点。


7. Matrix-矩阵原理

Matrix它的作用就是将内容区坐标转换为屏幕坐标,在将Canvas画布操作的时候讲到Matrix可以实现一些变换诸如平移、旋转、缩放、错切等,它们是如何实现的。

7.1 矩阵的含义

Matrix三阶矩阵,它对应着九个位置,如下所示:

这里写图片描述

前两行的6位分别控制着坐标的缩放、错切、旋转、平移等操作,所以我们可以利用Matrix来对坐标进行转换。

7.2 矩阵乘积

矩阵的乘积A*B等于,A位置上的改行和B位置上改行对应的列数一一乘积的和。
a11、a12
a21、a22

b11、b12
b21、b22
结果为
a11*b11+a12*b21、a11*b12+a12*b22
a21*b11+a22*b21、a21*b12+a22*b22

7.3 前乘(右乘)、后乘(左乘)

在Matrix转换中,有preXXX和postXXX函数,有很多教程又将其简单地概括为前后或者顺序倒叙执行,其实这种理解是错误的。
pre对应着前乘也叫右乘,M.pre(T) = M*T,将T放到M的右侧;
post对应着后乘也叫左乘,M.post(T) = T*M,将T放到M的左侧。
矩阵相乘支持结合定律,不支持交换定律,和单位矩阵相乘其结果不变。
我们来放几个例子感受一下。
m.post(R).pre(T) = R*m*T = R*T;
m.pre(R).pre(T) = M*R*T = R*T;


8. Matrix-矩阵详解

通过对矩阵原理的理解,我们来看单位矩阵
1 0 0
0 1 0
0 0 1
有前两行我们知道单位矩阵的变化就是,x/y轴的缩放比例是1,错切是0,平移也是0所以坐标经过单位矩阵的变换后是没有改变的。

8.1 数值操作

  • reset,重置为单元矩阵;
  • setValues(float[] f),将f中的9点的值赋给矩阵。
  • getValues(float[] f),将当前矩阵的9个值赋予f,单位矩阵的赋值结果为{1, 0, 0, 0, 1, 0, 0, 0, 1};

8.2 数值变换

  • mapPoints(),点的变换,将matrix.setScale(0.5, 1),这时候矩阵的第一个值由1变成0.5所以,点的x轴坐标为0.5x,这就相当于将点的X坐标变成原来的一半;
  • mapRadius(),测量半径,由于圆可能会因为画布变换变成椭圆,所以此处测量的是平均半径。
  • mapRect(),测量矩形变换后位置。
  • mapVectors(),测量向量,该测量不收位移的影响。

8.3 重要的知识点

  • 一开始从Canvas中获取到到Matrix并不是初始矩阵,而是经过偏移后到矩阵,且偏移距离就是距离屏幕左上角的位置。
    这个可以用于判定View在屏幕上的绝对位置,View可以根据所处位置做出调整。
  • 构造Matrix时使用的是矩阵乘法,前乘(pre)与后乘(post)结果差别很大;
  • 受矩阵乘法影响,后面的执行的操作可能会影响到之前的操作,使用时需要注意构造顺序。

8.4 特殊方法

setPolyToPoly控制多点形变;
setRectToRect源矩形的内容填充到目标矩形中,填充模式。

8.5 应用

一般情况我们自定义View会利用canvas.translate将坐标系的原点移动到View的正中心,因为这样更加方便我们去计算绘制的位置。View的坐标系改变了,但是我们的触摸坐标系即屏幕坐标系并没有改变,所以说这时候可能我们点击了View的某个区域了,但是通过event.getX方法获取的坐标并没有在View的区域上。

我们在距离屏幕左上角原点100的位置开始画自定义View,假设View原本的坐标是(0, 0)它是个宽高100的矩形,View坐标系(0,0)对应着屏幕坐标(100, 100),这时候我们触摸了屏幕坐标(180, 180)的位置,显然其转换为View坐标系就是(80, 80),显然这个点是在View内的。

当我们将View的坐标系平移到View的正中央时,这时候再触摸屏幕坐标(180, 180)的位置,屏幕坐标系并不知道View的坐标系有变化,所以依然认为这个位置在View中的坐标为(80, 80),但此时View的原点已经是(50, 50)了那么在(50,50)的位置再偏移(80,80)显然不在该View内了,这就是因为平移坐标时单位矩阵发生了变化,所以我们需要将获取平移之前的矩阵,canvas.getMatrix获取的是平移后的矩阵,canvas.getMatrix.invert(matrix)会得到当前矩阵的逆矩阵,我们通过逆矩阵的变换就能将屏幕触摸坐标转换为当前坐标值。

伪代码:

onDraw(Canvas canvas){
    canvas.translate(w/2, h/2);
    if(matrix.isIdentity()){
        canvas.getMatrix().invert(matrix);
    }
}

onTouchEvent(MotionEvent event){
    float[] f = new float[2];
    f[0] = event.getX();
    f[1] = event.getY();
    matrix.mapPoints(f); // 此时的f就是可以拿到判别是否在Path中
}

9. Matrix-Camera


上一篇:渐变圆环进度条实现 下一篇:#字节流转文件