安卓Path搭配Paint可以设置线帽,我想能不能把我自己的线条绘制Demo也加上类似的功能。
线头规则描述:
1、设一个线宽一半的线段,坐标为(0, 0)到(-lineWidth / 2, 0)。
2、设步骤1的线段有一垂直于它的向量(0,1),然后传入最近两次触摸坐标,并将第二次触摸坐标减去第一次触摸触摸坐标,得到当前画线的前进方向向量,然后求与(0, 1)向量夹角。
3、以从步骤2中得到的夹角,到该夹角+180度为止,以一定的角度步进量旋转步骤1的线段,形成一个切合线段的半圆。
4、最后添加一个(-lineWidth / 2, 0)到(lineWidth / 2, 0)的线段并旋转到步骤3的最终角度,方便和线段本体对接。
线尾规则描述:
1、只需把线头的规则中的步骤二改为,最后一次的之前一次的触摸坐标 减去 最后一次的触摸坐标作为方向向量即可(和前进方向相反)。
2、去冗余处理,每前进一次,就把之前添加的线尾半圆顶点删掉。
关键代码:
/**给线头添加符合线宽的边界,便于和纤体本身链接**/
private void lineCapAddBorder(double angle, float firstVec[], List<float[]> newVecs) {
try {
float rotatedVec0[] = rotate2d(new float[] {-mLineWidth / 2f, 0}, angle + 180);
float rotatedVec1[] = rotate2d(new float[] {mLineWidth / 2f, 0}, angle + 180);
float newVec[] = new float[6];
//偏移到对应位置
newVec[0] = rotatedVec0[0] + firstVec[0];
newVec[1] = rotatedVec0[1] + firstVec[1];
newVec[3] = rotatedVec1[0] + firstVec[0];
newVec[4] = rotatedVec1[1] + firstVec[1];
newVecs.add(newVec);
} catch (Exception e) {
e.printStackTrace();
}
}
/**绘制线头
* @param isHead 是否曲线头部添加线帽,否则视为曲线尾部添加线帽**/
private int lineCap(boolean isHead, @NonNull float firstVec[], @NonNull float secVec[], int color) {
if (null == firstVec) {
return -1;
}
if (mHeadPointBuf == null) {
mHeadCapPointByteBuffer = ByteBuffer.allocateDirect(mHeadInitVertexCount * 4); //顶点数 * sizeof(float)
mHeadCapPointByteBuffer.order(ByteOrder.nativeOrder());
mHeadPointBuf = mHeadCapPointByteBuffer.asFloatBuffer();
mHeadPointBuf.position(0);
mHeadCapPointBufferPos = 0;
}
//按初始化大小初始化RGBA字节数组和RGBA数组
if (mHeadColorBuf == null) {
mHeadCapColorByteBuffer = ByteBuffer.allocateDirect(mHeadInitColorCount * 4);
mHeadCapColorByteBuffer.order(ByteOrder.nativeOrder());
mHeadColorBuf = mHeadCapColorByteBuffer.asFloatBuffer();
mHeadColorBuf.position(0);
mHeadCapColorBufferPos = 0;
}
/**1、了解线条开始的方向,将半径线条绕旋转该方向与标准测量用向量的夹角的角度量
* 2、旋转180度时按照一定步进产生多个顶点,todo 但怎么确定旋转的方向是顺时针还是逆时针?以什么为依据判断?以传入向量方向为参考,但具体怎么做?*/
float initVert[] = new float[] { //初始时左端点的坐标,初始时在原点两侧,然后以传入的顶点作为偏移量
-mLineWidth / 2f, 0
};
//旋转并在过程中产生顶点
float actualVec[] = new float[3];
actualVec[0] = secVec[0] - firstVec[0];
actualVec[1] = secVec[1] - firstVec[1];
double angle = calcAngleOfVectorsOnXYPanel(mStandardVec, actualVec); //对比基准向量旋转了多少度
int step = 6; //改成只有90度可以得到一个尖头笔帽
List<float[]> newVecs = new LinkedList<>();
if (!isHead) {
//给曲线结尾加一段和线宽等长的边
lineCapAddBorder(angle, firstVec, newVecs);
}
//半圆线头
for (double degreeBias = angle; degreeBias <= 180 + angle; degreeBias += step) {
try {
float rotatedVec[] = rotate2d(initVert, degreeBias);
float newVec[] = new float[6];
//偏移到对应位置
newVec[0] = rotatedVec[0] + firstVec[0];
newVec[1] = rotatedVec[1] + firstVec[1];
newVec[3] += firstVec[0];
newVec[4] += firstVec[1];
newVecs.add(newVec);
} catch (Exception e) {
e.printStackTrace();
}
}
if (isHead) {
//给曲线开头加一段和线宽等长的边
lineCapAddBorder(angle, firstVec, newVecs);
}
for (float[] newVec : newVecs) {
for (int i = 0; i < newVec.length; i++) {
checkCapacity();
mPointBuf.put(mPointBufferPos++, newVec[i]);
}
for (int i = 0; i < newVec.length / 3; i++) {
checkCapacity();
//写入颜色值r,g,b,a
float alpha = (float) (((color & 0xFF000000) >> 24) & 0x000000FF) / 255f;
float blue = (float) ((color & 0x000000FF)) / 255f;
float green = (float) ((color & 0x0000FF00) >> 8) / 255f;
float red = (float) ((color & 0x00FF0000) >> 16) / 255f;
mColorBuf.put(mColorBufferPos++, red);
mColorBuf.put(mColorBufferPos++, green);
mColorBuf.put(mColorBufferPos++, blue);
mColorBuf.put(mColorBufferPos++, alpha);
}
}
return newVecs.size() * newVecs.get(0).length;
}
最后效果:
旋转步进设定为90度,因此能显示尖头效果:
设定为15度,则可以形成非常圆润的线头:
以线条方式绘制,即可看到顶点构成如下图:
基本再现了Android path + paint的大部分线条效果了。