1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Android SVG矢量图形打造中国地图

Android SVG矢量图形打造中国地图

时间:2019-03-16 23:53:27

相关推荐

Android SVG矢量图形打造中国地图

文章目录

svg 概念svg 优势path 支持的指令实现中国地图1 raw2 PathParser3 ProviceItem4 MapView5 两个问题6 缩放实现7 选中改变颜色

SVG矢量图形打造不规则的自定义控件-手写中国地图

技术点:svg技术起源详解

svg解决复杂控件与动画的解决之路

在不规则的控件中如何判断事件边界 ??

svg实现控件自动缩放

svg 概念

矢量图形

SVG 指可伸缩矢量图形 (Scalable Vector Graphics)

SVG 用来定义用于网络的基于矢量的图形

SVG 使用 XML 格式定义图形

SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失

SVG 是万维网联盟的标准

SVG 与诸如 DOM和 XSL 之类的W3C标准是一个整体

svg 优势

与其他图像格式相比,使用 SVG 的优势在于:

SVG 可被非常多的工具读取和修改(比如记事本)

SVG 与 JPEG 和 GIF 图像比起来,尺寸更小,且可压缩性更强。

SVG 是可伸缩的

SVG 图像可在任何的分辨率下被高质量地打印

SVG 可在图像质量不下降的情况下被放大

SVG 图像中的文本是可选的,同时也是可搜索的(很适合制作地图)

SVG 可以与 Java 技术一起运行

SVG 是开放的标准

SVG 文件是纯粹的 XML

svg的图像格式 一般是由 UI设计师来做

编辑Svg地址

http://editor.method.ac/

在线编辑Svg

/sp/svg/

svg语法教程

/svg/

地图数据

/download/

path 支持的指令

path支持的指令有:

M = moveto(M X,Y) :将画笔移动到指定的坐标位置L = lineto(L X,Y) :画直线到指定的坐标位置H = horizontal lineto(H X):画水平线到指定的X坐标位置V = vertical lineto(V Y):画垂直线到指定的Y坐标位置C = curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三次贝赛曲线S = smooth curveto(S X2,Y2,ENDX,ENDY)Q = quadratic Belzier curve(Q X,Y,ENDX,ENDY):二次贝赛曲线T = smooth quadratic Belzier curveto(T ENDX,ENDY):映射A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧线Z = closepath():关闭路径

Android 系统也会存在加载一个svg图片

工具类PathParese.java

\android-6.0.0_r1\frameworks\base\core\java\android\util

或者

/android-6.0.1_r81/xref/frameworks/base/core/java/android/util/PathParser.java

重写哪些方法?

onDraw()onTouchEvent()

实现中国地图

Github:

/345166018/AndroidUI/tree/master/HxChinaMapView

1 raw

添加china.svg文件到 res/raw 目录下。

2 PathParser

下载PathParser.java文件,PathParser具体代码放在本文最后。

3 ProviceItem

我们在ProviceItem中设置每个省份的画笔,包括边界和填充,不同的省份需要设置不同的颜色,所以填充颜色也需要不同。

public class ProviceItem {//path对象private Path path;//绘制的颜色private int drawColor;public ProviceItem(Path path) {this.path = path;}public void setDrawColor(int drawColor) {this.drawColor = drawColor;}void drawItem(Canvas canvas, Paint paint, boolean isSelect) {// 设置边界paint.setStrokeWidth(2);paint.setColor(Color.BLACK);paint.setStyle(Paint.Style.FILL);paint.setShadowLayer(8, 0, 0, 0xffffff);canvas.drawPath(path, paint);//后面是填充paint.clearShadowLayer();paint.setColor(drawColor);paint.setStyle(Paint.Style.FILL);paint.setStrokeWidth(2);canvas.drawPath(path, paint);}}

4 MapView

public class MapView extends View {//上下文private Context context;//画笔private Paint paint;//所有的省份的集合private List<ProviceItem> itemList;//绘制地图的颜色private int[] colorArray = new int[]{0xFF239BD7, 0xFF30A9E5, 0xFF80CBF1, 0xFFFFFFFF};//适配比例private float scale = 1.0f;public MapView(Context context) {super(context);}public MapView(Context context, AttributeSet attrs) {super(context, attrs);init(context);}public MapView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}/*** 定义init方法 用来初始化我们paint对象*/private void init(Context context) {this.context = context;paint = new Paint();paint.setAntiAlias(true);//开启解析XML文件的线程loadThread.start();}/*** 创建线程 用来解析XML文件*/private Thread loadThread = new Thread() {@Overridepublic void run() {//定义输入流加载中国地图XML文件InputStream inputStream = context.getResources().openRawResource(R.raw.china);//定义一个集合List<ProviceItem> list = new ArrayList<>();try {//取得DocumentBuilderFactory实例DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();DocumentBuilder builder = null;builder = factory.newDocumentBuilder();Document doc = builder.parse(inputStream);//获取到xml文件的根目录Element rootElement = doc.getDocumentElement();//获取根据节点下面的某些节点NodeList items = rootElement.getElementsByTagName("path");//遍历所有的path节点for (int x = 0; x < items.getLength(); x++) {//获取到每一个path节点Element element = (Element) items.item(x);//获取到path节点中的android:pathData属性值String pathData = element.getAttribute("android:pathData");//将path字符串转为path对象Path path = PathParser.createPathFromPathData(pathData);ProviceItem proviceItem = new ProviceItem(path);list.add(proviceItem);}itemList = list;handler.sendEmptyMessage(1);} catch (Exception e) {e.printStackTrace();}}};/*** 设置画省份的颜色*/Handler handler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {if (itemList == null) {return;}int totalNumber = itemList.size();for (int i = 0; i < totalNumber; i++) {int color = Color.WHITE;int flag = i % 4;switch (flag) {case 1:color = colorArray[0];break;case 2:color = colorArray[1];break;case 3:color = colorArray[2];break;default:color = Color.CYAN;break;}//将颜色设置给每个省份的封装对象itemList.get(i).setDrawColor(color);}requestLayout();postInvalidate();}};/*** 绘制的方法*/@Overrideprotected void onDraw(Canvas canvas) {//先判断itemList是否Wie空if (itemList != null && itemList.size() > 0) {canvas.save();canvas.scale(scale, scale);for (ProviceItem proviceItem : itemList) {proviceItem.drawItem(canvas, paint, false);}}}}

运行后查看效果,如下:

5 两个问题

1 按照比例缩放地图2 选中的省份改变颜色

6 缩放实现

获取中国地图最左、右、上、下的坐标

//首先 定义四个点float left = -1;float right = -1;float top = -1;float bottom = -1;//遍历所有的path节点for (int x = 0; x < items.getLength(); x++) {//获取到每一个path节点Element element = (Element) items.item(x);//获取到path节点中的android:pathData属性值String pathData = element.getAttribute("android:pathData");//将path字符串转为path对象Path path = PathParser.createPathFromPathData(pathData);ProviceItem proviceItem = new ProviceItem(path);list.add(proviceItem);//获取控件的宽高RectF rect = new RectF();//获取到每个省份的边界puteBounds(rect, true);//遍历取出每个path中的left取所有的最小值left = left == -1 ? rect.left : Math.min(left, rect.left);//遍历取出每个path中的right取所有的最大值right = right == -1 ? rect.right : Math.max(right, rect.right);//遍历取出每个path中的top取所有的最小值top = top == -1 ? rect.top : Math.min(top, rect.top);//遍历取出每个path中的bottom取所有的最大值bottom = bottom == -1 ? rect.bottom : Math.max(bottom, rect.bottom);}//创建整个地图的矩形totalRect = new RectF(left, top, right, bottom);

在onMeasure方法中,通过Rect宽度和控件宽度获取比例值

/*** 重新测量 做适配使用*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//获取到当前控件宽高值int width = MeasureSpec.getSize(widthMeasureSpec);int height = MeasureSpec.getSize(heightMeasureSpec);if (totalRect != null) {//获取到地图的矩形的宽度double mapWidth = totalRect.width();//获取到比例值scale = (float) (width / mapWidth);}setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));}

在onDraw方法中,通过canvas的scale方法按比例缩放图形。

/*** 绘制的方法*/@Overrideprotected void onDraw(Canvas canvas) {//先判断itemList是否Wie空if (itemList != null && itemList.size() > 0) {canvas.save();canvas.scale(scale, scale);for (ProviceItem proviceItem : itemList) {proviceItem.drawItem(canvas, paint, false);}}}

7 选中改变颜色

ProviceItem的drawItem方法需要设置选中和未选中的paint,isTouch方法用于判断省份是否选中。

void drawItem(Canvas canvas, Paint paint, boolean isSelect) {if (isSelect) {//选中时,绘制描边效果paint.clearShadowLayer();paint.setStrokeWidth(1);paint.setStyle(Paint.Style.FILL);paint.setColor(drawColor);canvas.drawPath(path, paint);paint.setStyle(Paint.Style.STROKE);paint.setColor(Color.BLACK);canvas.drawPath(path, paint);} else {//这是不选中的情况下 设置边界paint.setStrokeWidth(2);paint.setColor(Color.BLACK);paint.setStyle(Paint.Style.FILL);paint.setShadowLayer(8, 0, 0, 0xffffff);canvas.drawPath(path, paint);//后面是填充paint.clearShadowLayer();paint.setColor(drawColor);paint.setStyle(Paint.Style.FILL);paint.setStrokeWidth(2);canvas.drawPath(path, paint);}}public boolean isTouch(float x, float y) {//创建一个矩形RectF rectF = new RectF();//获取到当前省份的矩形边界puteBounds(rectF, true);//创建一个区域对象Region region = new Region();//将path对象放入到Region区域对象中region.setPath(path, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom));//返回是否这个区域包含传进来的坐标return region.contains((int) x, (int) y);}

在MapView 中覆写onTouchEvent方法,处理手指触摸的坐标,判断选中的哪个省份。在onDraw方法中重新绘制选中的省份。

@Overridepublic boolean onTouchEvent(MotionEvent event) {//将当前手指触摸到位置传过去 判断当前点击的区域handlerTouch(event.getX(), event.getY());return super.onTouchEvent(event);}/*** 判断区域*/private void handlerTouch(float x, float y) {//判空if (itemList == null || itemList.size() == 0) {return;}//定义一个空的被选中的省份ProviceItem selectItem = null;for (ProviceItem proviceItem : itemList) {//入股点击的是这个省份的范围之内 就把当前省份的封装对象绘制的方法 传一个trueif (proviceItem.isTouch(x / scale, y / scale)) {selectItem = proviceItem;}}if (selectItem != null) {select = selectItem;postInvalidate();}}/*** 绘制的方法*/@Overrideprotected void onDraw(Canvas canvas) {//先判断itemList是否Wie空if (itemList != null && itemList.size() > 0) {canvas.save();canvas.scale(scale, scale);for (ProviceItem proviceItem : itemList) {if (select != proviceItem) {proviceItem.drawItem(canvas, paint, false);}}if (select != null) {select.drawItem(canvas, paint, true);}}}

效果如下:

PathParser.java代码如下:

/** Copyright (C) The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except* in compliance with the License. You may obtain a copy of the License at** /licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software distributed under the License* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express* or implied. See the License for the specific language governing permissions and limitations under* the License.*/import android.graphics.Path;import android.util.Log;import java.util.ArrayList;import java.util.Arrays;/*** @hide*/public class PathParser {static final String LOGTAG = PathParser.class.getSimpleName();/*** @param pathData The string representing a path, the same as "d" string in svg file.* @return the generated Path object.*/public static Path createPathFromPathData(String pathData) {Path path = new Path();PathDataNode[] nodes = createNodesFromPathData(pathData);if (nodes != null) {try {PathDataNode.nodesToPath(nodes, path);} catch (RuntimeException e) {throw new RuntimeException("Error in parsing " + pathData, e);}return path;}return null;}/*** @param pathData The string representing a path, the same as "d" string in svg file.* @return an array of the PathDataNode.*/public static PathDataNode[] createNodesFromPathData(String pathData) {if (pathData == null) {return null;}int start = 0;int end = 1;ArrayList<PathDataNode> list = new ArrayList<PathDataNode>();while (end < pathData.length()) {end = nextStart(pathData, end);String s = pathData.substring(start, end).trim();if (s.length() > 0) {float[] val = getFloats(s);addNode(list, s.charAt(0), val);}start = end;end++;}if ((end - start) == 1 && start < pathData.length()) {addNode(list, pathData.charAt(start), new float[0]);}return list.toArray(new PathDataNode[list.size()]);}/*** @param source The array of PathDataNode to be duplicated.* @return a deep copy of the <code>source</code>.*/public static PathDataNode[] deepCopyNodes(PathDataNode[] source) {if (source == null) {return null;}PathDataNode[] copy = new PathDataNode[source.length];for (int i = 0; i < source.length; i ++) {copy[i] = new PathDataNode(source[i]);}return copy;}/*** @param nodesFrom The source path represented in an array of PathDataNode* @param nodesTo The target path represented in an array of PathDataNode* @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code>*/public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) {if (nodesFrom == null || nodesTo == null) {return false;}if (nodesFrom.length != nodesTo.length) {return false;}for (int i = 0; i < nodesFrom.length; i ++) {if (nodesFrom[i].mType != nodesTo[i].mType|| nodesFrom[i].mParams.length != nodesTo[i].mParams.length) {return false;}}return true;}/*** Update the target's data to match the source.* Before calling this, make sure canMorph(target, source) is true.** @param target The target path represented in an array of PathDataNode* @param source The source path represented in an array of PathDataNode*/public static void updateNodes(PathDataNode[] target, PathDataNode[] source) {for (int i = 0; i < source.length; i ++) {target[i].mType = source[i].mType;for (int j = 0; j < source[i].mParams.length; j ++) {target[i].mParams[j] = source[i].mParams[j];}}}private static int nextStart(String s, int end) {char c;while (end < s.length()) {c = s.charAt(end);// Note that 'e' or 'E' are not valid path commands, but could be// used for floating point numbers' scientific notation.// Therefore, when searching for next command, we should ignore 'e'// and 'E'.if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))&& c != 'e' && c != 'E') {return end;}end++;}return end;}private static void addNode(ArrayList<PathDataNode> list, char cmd, float[] val) {list.add(new PathDataNode(cmd, val));}private static class ExtractFloatResult {// We need to return the position of the next separator and whether the// next float starts with a '-' or a '.'.int mEndPosition;boolean mEndWithNegOrDot;}/*** Parse the floats in the string.* This is an optimized version of parseFloat(s.split(",|\\s"));** @param s the string containing a command and list of floats* @return array of floats*/private static float[] getFloats(String s) {if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') {return new float[0];}try {float[] results = new float[s.length()];int count = 0;int startPosition = 1;int endPosition = 0;ExtractFloatResult result = new ExtractFloatResult();int totalLength = s.length();// The startPosition should always be the first character of the// current number, and endPosition is the character after the current// number.while (startPosition < totalLength) {extract(s, startPosition, result);endPosition = result.mEndPosition;if (startPosition < endPosition) {results[count++] = Float.parseFloat(s.substring(startPosition, endPosition));}if (result.mEndWithNegOrDot) {// Keep the '-' or '.' sign with next number.startPosition = endPosition;} else {startPosition = endPosition + 1;}}return Arrays.copyOf(results, count);} catch (NumberFormatException e) {throw new RuntimeException("error in parsing \"" + s + "\"", e);}}/*** Calculate the position of the next comma or space or negative sign* @param s the string to search* @param start the position to start searching* @param result the result of the extraction, including the position of the* the starting position of next number, whether it is ending with a '-'.*/private static void extract(String s, int start, ExtractFloatResult result) {// Now looking for ' ', ',', '.' or '-' from the start.int currentIndex = start;boolean foundSeparator = false;result.mEndWithNegOrDot = false;boolean secondDot = false;boolean isExponential = false;for (; currentIndex < s.length(); currentIndex++) {boolean isPrevExponential = isExponential;isExponential = false;char currentChar = s.charAt(currentIndex);switch (currentChar) {case ' ':case ',':foundSeparator = true;break;case '-':// The negative sign following a 'e' or 'E' is not a separator.if (currentIndex != start && !isPrevExponential) {foundSeparator = true;result.mEndWithNegOrDot = true;}break;case '.':if (!secondDot) {secondDot = true;} else {// This is the second dot, and it is considered as a separator.foundSeparator = true;result.mEndWithNegOrDot = true;}break;case 'e':case 'E':isExponential = true;break;}if (foundSeparator) {break;}}// When there is nothing found, then we put the end position to the end// of the string.result.mEndPosition = currentIndex;}/*** Each PathDataNode represents one command in the "d" attribute of the svg* file.* An array of PathDataNode can represent the whole "d" attribute.*/public static class PathDataNode {private char mType;private float[] mParams;private PathDataNode(char type, float[] params) {mType = type;mParams = params;}private PathDataNode(PathDataNode n) {mType = n.mType;mParams = Arrays.copyOf(n.mParams, n.mParams.length);}/*** Convert an array of PathDataNode to Path.** @param node The source array of PathDataNode.* @param path The target Path object.*/public static void nodesToPath(PathDataNode[] node, Path path) {float[] current = new float[6];char previousCommand = 'm';for (int i = 0; i < node.length; i++) {addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);previousCommand = node[i].mType;}}/*** The current PathDataNode will be interpolated between the* <code>nodeFrom</code> and <code>nodeTo</code> according to the* <code>fraction</code>.** @param nodeFrom The start value as a PathDataNode.* @param nodeTo The end value as a PathDataNode* @param fraction The fraction to interpolate.*/public void interpolatePathDataNode(PathDataNode nodeFrom,PathDataNode nodeTo, float fraction) {for (int i = 0; i < nodeFrom.mParams.length; i++) {mParams[i] = nodeFrom.mParams[i] * (1 - fraction)+ nodeTo.mParams[i] * fraction;}}private static void addCommand(Path path, float[] current,char previousCmd, char cmd, float[] val) {int incr = 2;float currentX = current[0];float currentY = current[1];float ctrlPointX = current[2];float ctrlPointY = current[3];float currentSegmentStartX = current[4];float currentSegmentStartY = current[5];float reflectiveCtrlPointX;float reflectiveCtrlPointY;switch (cmd) {case 'z':case 'Z':path.close();// Path is closed here, but we need to move the pen to the// closed position. So we cache the segment's starting position,// and restore it here.currentX = currentSegmentStartX;currentY = currentSegmentStartY;ctrlPointX = currentSegmentStartX;ctrlPointY = currentSegmentStartY;path.moveTo(currentX, currentY);break;case 'm':case 'M':case 'l':case 'L':case 't':case 'T':incr = 2;break;case 'h':case 'H':case 'v':case 'V':incr = 1;break;case 'c':case 'C':incr = 6;break;case 's':case 'S':case 'q':case 'Q':incr = 4;break;case 'a':case 'A':incr = 7;break;}for (int k = 0; k < val.length; k += incr) {switch (cmd) {case 'm': // moveto - Start a new sub-path (relative)path.rMoveTo(val[k + 0], val[k + 1]);currentX += val[k + 0];currentY += val[k + 1];currentSegmentStartX = currentX;currentSegmentStartY = currentY;break;case 'M': // moveto - Start a new sub-pathpath.moveTo(val[k + 0], val[k + 1]);currentX = val[k + 0];currentY = val[k + 1];currentSegmentStartX = currentX;currentSegmentStartY = currentY;break;case 'l': // lineto - Draw a line from the current point (relative)path.rLineTo(val[k + 0], val[k + 1]);currentX += val[k + 0];currentY += val[k + 1];break;case 'L': // lineto - Draw a line from the current pointpath.lineTo(val[k + 0], val[k + 1]);currentX = val[k + 0];currentY = val[k + 1];break;case 'h': // horizontal lineto - Draws a horizontal line (relative)path.rLineTo(val[k + 0], 0);currentX += val[k + 0];break;case 'H': // horizontal lineto - Draws a horizontal linepath.lineTo(val[k + 0], currentY);currentX = val[k + 0];break;case 'v': // vertical lineto - Draws a vertical line from the current point (r)path.rLineTo(0, val[k + 0]);currentY += val[k + 0];break;case 'V': // vertical lineto - Draws a vertical line from the current pointpath.lineTo(currentX, val[k + 0]);currentY = val[k + 0];break;case 'c': // curveto - Draws a cubic Bézier curve (relative)path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],val[k + 4], val[k + 5]);ctrlPointX = currentX + val[k + 2];ctrlPointY = currentY + val[k + 3];currentX += val[k + 4];currentY += val[k + 5];break;case 'C': // curveto - Draws a cubic Bézier curvepath.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],val[k + 4], val[k + 5]);currentX = val[k + 4];currentY = val[k + 5];ctrlPointX = val[k + 2];ctrlPointY = val[k + 3];break;case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)reflectiveCtrlPointX = 0;reflectiveCtrlPointY = 0;if (previousCmd == 'c' || previousCmd == 's'|| previousCmd == 'C' || previousCmd == 'S') {reflectiveCtrlPointX = currentX - ctrlPointX;reflectiveCtrlPointY = currentY - ctrlPointY;}path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,val[k + 0], val[k + 1],val[k + 2], val[k + 3]);ctrlPointX = currentX + val[k + 0];ctrlPointY = currentY + val[k + 1];currentX += val[k + 2];currentY += val[k + 3];break;case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)reflectiveCtrlPointX = currentX;reflectiveCtrlPointY = currentY;if (previousCmd == 'c' || previousCmd == 's'|| previousCmd == 'C' || previousCmd == 'S') {reflectiveCtrlPointX = 2 * currentX - ctrlPointX;reflectiveCtrlPointY = 2 * currentY - ctrlPointY;}path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,val[k + 0], val[k + 1], val[k + 2], val[k + 3]);ctrlPointX = val[k + 0];ctrlPointY = val[k + 1];currentX = val[k + 2];currentY = val[k + 3];break;case 'q': // Draws a quadratic Bézier (relative)path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);ctrlPointX = currentX + val[k + 0];ctrlPointY = currentY + val[k + 1];currentX += val[k + 2];currentY += val[k + 3];break;case 'Q': // Draws a quadratic Bézierpath.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);ctrlPointX = val[k + 0];ctrlPointY = val[k + 1];currentX = val[k + 2];currentY = val[k + 3];break;case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)reflectiveCtrlPointX = 0;reflectiveCtrlPointY = 0;if (previousCmd == 'q' || previousCmd == 't'|| previousCmd == 'Q' || previousCmd == 'T') {reflectiveCtrlPointX = currentX - ctrlPointX;reflectiveCtrlPointY = currentY - ctrlPointY;}path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,val[k + 0], val[k + 1]);ctrlPointX = currentX + reflectiveCtrlPointX;ctrlPointY = currentY + reflectiveCtrlPointY;currentX += val[k + 0];currentY += val[k + 1];break;case 'T': // Draws a quadratic Bézier curve (reflective control point)reflectiveCtrlPointX = currentX;reflectiveCtrlPointY = currentY;if (previousCmd == 'q' || previousCmd == 't'|| previousCmd == 'Q' || previousCmd == 'T') {reflectiveCtrlPointX = 2 * currentX - ctrlPointX;reflectiveCtrlPointY = 2 * currentY - ctrlPointY;}path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,val[k + 0], val[k + 1]);ctrlPointX = reflectiveCtrlPointX;ctrlPointY = reflectiveCtrlPointY;currentX = val[k + 0];currentY = val[k + 1];break;case 'a': // Draws an elliptical arc// (rx ry x-axis-rotation large-arc-flag sweep-flag x y)drawArc(path,currentX,currentY,val[k + 5] + currentX,val[k + 6] + currentY,val[k + 0],val[k + 1],val[k + 2],val[k + 3] != 0,val[k + 4] != 0);currentX += val[k + 5];currentY += val[k + 6];ctrlPointX = currentX;ctrlPointY = currentY;break;case 'A': // Draws an elliptical arcdrawArc(path,currentX,currentY,val[k + 5],val[k + 6],val[k + 0],val[k + 1],val[k + 2],val[k + 3] != 0,val[k + 4] != 0);currentX = val[k + 5];currentY = val[k + 6];ctrlPointX = currentX;ctrlPointY = currentY;break;}previousCmd = cmd;}current[0] = currentX;current[1] = currentY;current[2] = ctrlPointX;current[3] = ctrlPointY;current[4] = currentSegmentStartX;current[5] = currentSegmentStartY;}private static void drawArc(Path p,float x0,float y0,float x1,float y1,float a,float b,float theta,boolean isMoreThanHalf,boolean isPositiveArc) {/* Convert rotation angle from degrees to radians */double thetaD = Math.toRadians(theta);/* Pre-compute rotation matrix entries */double cosTheta = Math.cos(thetaD);double sinTheta = Math.sin(thetaD);/* Transform (x0, y0) and (x1, y1) into unit space *//* using (inverse) rotation, followed by (inverse) scale */double x0p = (x0 * cosTheta + y0 * sinTheta) / a;double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;double x1p = (x1 * cosTheta + y1 * sinTheta) / a;double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;/* Compute differences and averages */double dx = x0p - x1p;double dy = y0p - y1p;double xm = (x0p + x1p) / 2;double ym = (y0p + y1p) / 2;/* Solve for intersecting unit circles */double dsq = dx * dx + dy * dy;if (dsq == 0.0) {Log.w(LOGTAG, " Points are coincident");return; /* Points are coincident */}double disc = 1.0 / dsq - 1.0 / 4.0;if (disc < 0.0) {Log.w(LOGTAG, "Points are too far apart " + dsq);float adjust = (float) (Math.sqrt(dsq) / 1.99999);drawArc(p, x0, y0, x1, y1, a * adjust,b * adjust, theta, isMoreThanHalf, isPositiveArc);return; /* Points are too far apart */}double s = Math.sqrt(disc);double sdx = s * dx;double sdy = s * dy;double cx;double cy;if (isMoreThanHalf == isPositiveArc) {cx = xm - sdy;cy = ym + sdx;} else {cx = xm + sdy;cy = ym - sdx;}double eta0 = Math.atan2((y0p - cy), (x0p - cx));double eta1 = Math.atan2((y1p - cy), (x1p - cx));double sweep = (eta1 - eta0);if (isPositiveArc != (sweep >= 0)) {if (sweep > 0) {sweep -= 2 * Math.PI;} else {sweep += 2 * Math.PI;}}cx *= a;cy *= b;double tcx = cx;cx = cx * cosTheta - cy * sinTheta;cy = tcx * sinTheta + cy * cosTheta;arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);}/*** Converts an arc to cubic Bezier segments and records them in p.** @param p The target for the cubic Bezier segments* @param cx The x coordinate center of the ellipse* @param cy The y coordinate center of the ellipse* @param a The radius of the ellipse in the horizontal direction* @param b The radius of the ellipse in the vertical direction* @param e1x E(eta1) x coordinate of the starting point of the arc* @param e1y E(eta2) y coordinate of the starting point of the arc* @param theta The angle that the ellipse bounding rectangle makes with horizontal plane* @param start The start angle of the arc on the ellipse* @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse*/private static void arcToBezier(Path p,double cx,double cy,double a,double b,double e1x,double e1y,double theta,double start,double sweep) {// Taken from equations at: /documents/ellipse/node8.html// and /documents/ellipse/node22.html// Maximum of 45 degrees per cubic Bezier segmentint numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));double eta1 = start;double cosTheta = Math.cos(theta);double sinTheta = Math.sin(theta);double cosEta1 = Math.cos(eta1);double sinEta1 = Math.sin(eta1);double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);double anglePerSegment = sweep / numSegments;for (int i = 0; i < numSegments; i++) {double eta2 = eta1 + anglePerSegment;double sinEta2 = Math.sin(eta2);double cosEta2 = Math.cos(eta2);double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;double tanDiff2 = Math.tan((eta2 - eta1) / 2);double alpha =Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;double q1x = e1x + alpha * ep1x;double q1y = e1y + alpha * ep1y;double q2x = e2x - alpha * ep2x;double q2y = e2y - alpha * ep2y;p.cubicTo((float) q1x,(float) q1y,(float) q2x,(float) q2y,(float) e2x,(float) e2y);eta1 = eta2;e1x = e2x;e1y = e2y;ep1x = ep2x;ep1y = ep2y;}}}}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。