文章目录

  • svg 概念
  • svg 优势
  • path 支持的指令
  • 实现中国地图
    • 1 raw
    • 2 PathParser
    • 3 ProviceItem
    • 4 MapView
    • 5 两个问题
    • 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
https://www.zhangxinxu.com/sp/svg/
svg语法教程
http://www.w3school.com.cn/svg/
地图数据
https://www.amcharts.com/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

或者
http://aospxref.com/android-6.0.1_r81/xref/frameworks/base/core/java/android/util/PathParser.java

重写哪些方法?

  • onDraw()
  • onTouchEvent()

实现中国地图

Github:
https://github.com/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();//获取到每个省份的边界path.computeBounds(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();//获取到当前省份的矩形边界path.computeBounds(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) 2014 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** http://www.apache.org/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: http://spaceroots.org/documents/ellipse/node8.html// and http://www.spaceroots.org/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;}}}
}

Android SVG矢量图形打造中国地图相关推荐

  1. Android 使用svg构造交互式中国地图

    概念 什么是svg 即Scalable Vector Graphics 可伸缩矢量图形 SVG的W3C的解释: http://www.w3school.com.cn/svg/svg_intro.asp ...

  2. Android开发画出中国地图,Android 绘制中国地图

    最近的版本有这样一个需求: 有 3 个要素: 中国地图 高亮省区 中心显示数字 面对这样一个需求,该如何实现呢? 高德地图 因为项目是基于高德地图来做的,所以很自然而然的想到了高德.但是当查阅高德地图 ...

  3. Android 自定义View之中国地图热点区域分布

    本文出自:http://blog.csdn.net/dt235201314/article/details/78133932 一丶效果图 二丶需求功能点技术点 1.业务想要的大致模样 呈现地图及省份, ...

  4. android 自定义热点区,Android 自定义View之中国地图热点区域分布

    一丶效果图 二丶需求功能点技术点 1.业务想要的大致模样 呈现地图及省份,高热点地域颜色越红,前五以不同色值标注 2.程序员表示 移动端没有控件及框架,开发的话需要大量时间.前段有相关框架,不如前段做 ...

  5. SVG 详解——自定义可点击的中国地图

    SVG 详解--自定义可点击的中国地图 SVG 定义 SVG 是一种图像文件格式,类似于 JPG.PNG.只不过 JPG 和 PNG 这种文件需要图像引擎加载,而 SVG 则是由画布来加载的. 它的英 ...

  6. (十七)SVG 实例-可交互式中国地图

    版权声明:本文为博主原创文章,未经博主允许不得转载. 本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下. 一.效果 地图效果只有各个省份的,按上级要求,地图的展示必须是完整的,这边 ...

  7. 地图 svg中国地图、echarts百度迁徙图

    总结下最近使用过的地图控件: 第一种 svg中国地图 demo:http://www.oschina.net/code/snippet_54124_36979 这个很简单,照着demo里面直接调用就可 ...

  8. android vector 圆形,Android中矢量图形的那些事 - SVG or Vector

    之前对矢量图形有所耳闻,但因为Android对于矢量图形的原生支持较晚,所以一直没好好研究过(16年2月25就出来的东西,其实就是懒 =.=).最近工作上正好遇到一个产品需求,想用SVG来解决,借此机 ...

  9. html5 svg中国地图map悬停显示省市地区代码

    html5 svg中国地图map悬停显示省市地区代码 html5 svg中国地图map悬https://www.51qianduan.com/article/4401.html停显示省市地区代码

  10. 【赞】用CSS打造的中国地图

    代码简介: 采用CSS结合部分图片实现的漂亮实用的中国地图,相信这个地图能帮到你,运行一下,您就知道了. 代码内容: View Code <!DOCTYPE HTML PUBLIC " ...

最新文章

  1. Udacity机器人软件工程师课程笔记(五)-样本搜索和找回-基于漫游者号模拟器-自主驾驶
  2. 一线大厂Java开发所需掌握的技能要点汇总
  3. 以太网性能测试仪应该具备什么功能?
  4. 【BZOJ 1098】办公楼(补图连通块个数,Bfs)
  5. Cocos2d-x 2 0 4 小心隐藏的retain
  6. windows下命令
  7. sftp访问_实时数据处理探索:接收、处理、访问
  8. python爬虫——爬取b站APP视频信息(通过fiddler抓包工具)
  9. pythonui自动化断言,python UI自动化13- 断言方法
  10. java读取mp3文件_java读取mp3文件 | 学步园
  11. ftp服务器技术原理,FTP工作原理
  12. 重启计算机找不到打印机,如何解决重启后打印机找不到设备
  13. 微信小程序云数据库调用模板
  14. 残差平方和(RSS)、均方误差(MSE)、均方根误差(RMSE)、平均绝对误差(MAE)
  15. 什么软件可以测试家里的宽带,技术贴 | 如何简单又专业的测试自己家的网速是否达标...
  16. Typora:Typora快捷键
  17. Pinterest先辈Wists的创业故事
  18. Centos常用基础命令
  19. 打印九九乘法表的两种方法:
  20. OllyDbg动态调试与逆向破解TraceMe.exe

热门文章

  1. 调用企业微信接口注意事项
  2. LayUI⑷:模板语法
  3. h3c交换机重启_华三交换机重启命令(范文篇).doc
  4. PHP后端美化,基于Thinkphp5.5表白墙源码,已美化后端
  5. 【嵌入式】NBIoT(BC26)低功耗模式与控制
  6. javascript模块化编程规范
  7. 内容:提出共享储能背景下微网运营商与用户聚合商间的 Stackelberg 博弈模型,在 MATLAB 平台上进行算例仿真
  8. C++项目经验(6)——yaml-cpp的安装、报错解决及使用
  9. 计算机二级VEP考试内容,2017计算机二级VEP知识点:报表设计与应用
  10. Oracle索引的原理及使用