使用SVG打造不规则自定义控件

传统的ImageButtom图片等控件都是基于长方形等规则图形,若是想使用不规则图形,则可以使用SVG图片定义Path路径,从而打造不规则图形,代码如下:

public class ChinaMap extends View {
    //初始化画笔
    private Paint paint;

    private Context context;

    private ProviceItem select;
    //装中国地图的Rectf
    private RectF totalRect;

    List<ProviceItem> itemList;
    //地图缩放比例
    private float scale =1.0f;
    //省份颜色
    int[] colorArray = new int[]{0xFF239BD7, 0xFF30A9E5, 0xFF80CBF1, 0xFFFFFFFF};
    //表示SVG图片是否解析完成
    private boolean isEnd;

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

    public ChinaMap(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public ChinaMap(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

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

    private Thread loadThread = new Thread(new Runnable() {
        @Override
        public void run() {
            //定义一个输入流去解析xml
            InputStream input = context.getResources().openRawResource(R.raw.china);
            //定义一个封装所有省份的集合
            List<ProviceItem> list = new ArrayList<>();
            //获取到解析器的工厂类
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = null;
            try {
                documentBuilder = factory.newDocumentBuilder();
                //获取到SVG的XML对象
                Document parse = documentBuilder.parse(input);
                //获取所有节点根目录
                Element element = parse.getDocumentElement();
                //通过Element获取所有path节点的集合
                NodeList nodeList = element.getElementsByTagName("path");
                //定义四个点,记录中国地图两个点坐标
                float left = -1;
                float top = -1;
                float right = -1;
                float bottom = -1;
                //遍历所有的path节点
                for(int i=0;i<nodeList.getLength();i++){
                    //获取到每个节点
                    Element item = (Element) nodeList.item(i);
                    String pathData = item.getAttribute("android:pathData");
                    Path path = PathParser.createPathFromPathData(pathData);
                    ProviceItem proviceItem = new ProviceItem(path);
                    list.add(proviceItem);

                    //初始化省份Rect
                    RectF rect = new RectF();
                    path.computeBounds(rect,true);

                    //初始化中国地图两个点坐标
                    //获取到left最小的值
                    left = left == -1 ? rect.left:Math.min(left,rect.left);

                    top = top == -1 ? rect.top:Math.min(top,rect.top);

                    right = right == -1 ? rect.right:Math.max(right,rect.right);

                    bottom = bottom == -1 ? rect.bottom:Math.max(bottom,rect.bottom);
                }
                //封装中国地图
                totalRect = new RectF(left,top,right,bottom);
                itemList = list;
                //绘制中国地图
                weakReference.get().sendEmptyMessage(1);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });

    private Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            if(itemList == null || itemList.size()<=0){
                return;
            }
            //给省份设置颜色
           // if(msg.what == 1){
                for(int i=0;i<itemList.size();i++){
                    ProviceItem proviceItem = itemList.get(i);
                    proviceItem.setDrawColor(colorArray[i%4]);
                }
            //}
            //xml解析完毕
            isEnd = true;
            //设置缩放比例
            measure(getMeasuredWidth(),getMeasuredHeight());
            //调用绘制
            postInvalidate();
        }
    };
    private WeakReference<Handler> weakReference = new WeakReference(handler);

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取到当前控件的宽高
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        if(!isEnd){
            setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(height , MeasureSpec.EXACTLY));
            return;
        }

        if(totalRect != null){
            //获取地图的宽度
            double mapWidth = totalRect.width();
            scale = (float) (width/mapWidth);
        }
        setMeasuredDimension(MeasureSpec.makeMeasureSpec(width,MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if(!isEnd){
            return;
        }

        if(itemList != null && itemList.size() > 0){
            canvas.save();
            //设置缩放比例
            canvas.scale(scale,scale);
            for(int i=0;i<itemList.size();i++){
                itemList.get(i).drawItem(canvas,paint,itemList.get(i)==select);
            }
        }
        super.onDraw(canvas);
    }

    //处理触摸事件
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(itemList ==null || itemList.size() ==0) {
            return true;
        }

        //选出被触摸的省份
        if(itemList != null && itemList.size() !=0){
            for(ProviceItem item :itemList){
                if(item.isTouch(event.getX()/scale,event.getY()/scale)){
                    select = item;
                }
            }
            //若存在被触摸的省份,则重新绘制
            if(select != null){
                postInvalidate();
            }
        }
        return super.onTouchEvent(event);
    }
}

加载结果:
使用SVG打造不规则自定义控件
当点击图片中某个省份时,结果如下:
使用SVG打造不规则自定义控件

上一篇:Iconset:免费的 SVG 图标资源管理软件


下一篇:css实现垂直居中的方式