天天看點

Android UI SVG使用

Android UI SVG使用

1.

SVG是什麼?

  • 可縮放矢量圖形(Scalable Vector Graphics)
  • SVG于2003 年1月14日成為 W3C 推薦标準

2.

SVG特性

  • SVG可被很多工具讀取和使用
  • SVG跟PNG,JPG,GIF圖像比起來,尺寸更小,可壓縮性更強
  • SVG是可伸縮的
  • SVG适配性好,任意放大縮小不失真

3.

SVG使用場景

  • Android SDK23 APP的圖示都是由SVG來制作的
  • 不規則控件,複雜的互動,子控件重疊判斷,圖示等都可以用SVG來做
  • 複雜路徑動畫等

4.

SVG使用

自己制作SVG:https://editor.method.ac/ (線上SVG編輯器)

SVG檔案下載下傳:https://www.amcharts.com/download/ (比如中國地圖等)

Android UI SVG使用

5.

SVG常用方法

Android UI SVG使用

接下來利用SVG畫一個中國地圖,并且實作各個省份有可點選事件

6.

涉及到知識點

  1. 自定義View (MapView.java)
public class MapView extends View {

    private int[] colorArray = new int[]{0xFF239BD7, 0xFF30A9E5, 0xFF80CBF1, 0xFFFFFFFF};
    private Context context;
    private List<ProvinceItem> itemList;
    private Paint paint;
    private ProvinceItem select;
    private RectF totalRect;
    private float scale = 1.0f;

    public MapView(Context context) {
        super(context);
    }

    public MapView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        this.context = context;
        paint = new Paint();
        paint.setAntiAlias(true);
        itemList = new ArrayList<>();
        loadThread.start();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 擷取到目前控件寬高值
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (totalRect != null) {
            double mapWidth = totalRect.width();
            // SVG縮放适配
            scale = (float) (width / mapWidth);
        }
        setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));

    }

    private Thread loadThread = new Thread() {
        @Override
        public void run() {
            final InputStream inputStream = context.getResources().openRawResource(R.raw.china);
            // 取得DocumentBuilderFactory執行個體
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();  
            //從factory擷取DocumentBuilder執行個體
            DocumentBuilder builder = null; 
            try {
                builder = factory.newDocumentBuilder();
                Document doc = builder.parse(inputStream);   
                // 解析輸入流 得到Document執行個體
                Element rootElement = doc.getDocumentElement();
                NodeList items = rootElement.getElementsByTagName("path");
                float left = -1;
                float right = -1;
                float top = -1;
                float bottom = -1;
                List<ProvinceItem> list = new ArrayList<>();
                // 解析所有省份SVG資料,然後将省份所對應字元串轉換為Path繪制資料
                for (int i = 0; i < items.getLength(); i++) {
					// Dom解析xml
                    Element element = (Element) items.item(i);
                    String pathData = element.getAttribute("android:pathData");
                    @SuppressLint("RestrictedApi")
                    Path path = PathParser.createPathFromPathData(pathData);
                    ProvinceItem proviceItem = new ProvinceItem(path);
                    proviceItem.setDrawColor(colorArray[i % 4]);
                    // 通過計算地圖所占最大矩形區域(左,上,右,下位置,來計算縮放scale)
                    RectF rect = new RectF();
                    path.computeBounds(rect, true);
                    left = left == -1 ? rect.left : Math.min(left, rect.left);
                    right = right == -1 ? rect.right : Math.max(right, rect.right);
                    top = top == -1 ? rect.top : Math.min(top, rect.top);
                    bottom = bottom == -1 ? rect.bottom : Math.max(bottom, rect.bottom);
                    list.add(proviceItem);
                }
                itemList = list;
                totalRect = new RectF(left, top, right, bottom);
				// 重新整理界面
                Handler handler = new Handler(Looper.getMainLooper());
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        requestLayout();
                        invalidate();
                    }
                });

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 适配點選事件區域X,Y
        handleTouch(event.getX() / scale, event.getY() / scale);
        return super.onTouchEvent(event);
    }

    private void handleTouch(float x, float y) {
        if (itemList == null) {
            return;
        }
        ProvinceItem selectItem = null;
        for (ProvinceItem proviceItem : itemList) {
            if (proviceItem.isTouch(x, y)) {
                selectItem = proviceItem;
            }
        }
        if (selectItem != null) {
            select = selectItem;
            postInvalidate();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (itemList != null) {
            canvas.save();
            canvas.scale(scale, scale);
            for (ProvinceItem proviceItem : itemList) {
                if (proviceItem != select) {
                    proviceItem.drawItem(canvas, paint, false);
                } else {
                    select.drawItem(canvas, paint, true);
                }
            }
        }
    }
}

           
  1. 封裝每個省份資訊對象
public class ProvinceItem {

    private Path path;

    // 繪制顔色
    private int drawColor;

    public ProvinceItem(Path path) {
        this.path = path;
    }

    public void setDrawColor(int drawColor) {
        this.drawColor = drawColor;
    }

    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);
            int strokeColor = 0xFFD0E8F4;
            paint.setColor(strokeColor);
            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);
        }
    }

	// 判斷手指點選區域所在省份
	//  Android Region 區域點選
    public boolean isTouch(float x, float y) {
        RectF rectF = new RectF();
        path.computeBounds(rectF, true);
        Region region = new 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);
    }

}
           
  1. 最後需要一個 中國地圖SVG,可從上面提供網站下載下傳

7.

參考

  1. SVG—最簡單的SVG動畫
  2. SVG矢量動畫機制
  3. geftimov/android-pathview
  4. android中svg介紹及使用[彙總]
  5. Android實作炫酷SVG動畫效果
  6. SVGA 是一種跨平台的開源動畫格式