Android 自定义地图控件 可手指拉伸放大缩小拖动,指定坐标加点加线

之前公司 项目有用到 gps定位 ,以及 工厂地图 布置 点 ,但是 无法使用百度地图之类的 第三方地图SDK ,只给一个工厂平面图,就要实现gps定位,一直 地图上布点。

gps 坐标转换到 图片上 的 算法 由  老大 搞定了,我这边 负责 UI 的 显示。

这是 demo 地址: 点击打开链接

这是 效果图:




首先 整个 控件 布局 是一个相对布局,右上角 放置了 放大,原始,缩小 按钮,实现对应功能

显示 地图图片 的 控件 也是一个 自定义控件 ,继承ImageView  里面 重写 了onDraw 方法 用来 画地图 的 线

/**
 * 自定义 地图 地图
 * Created by Administrator on 2016/6/30.
 */
public class MyBaseMapAndLines extends ImageView {

    // 线 坐标
    public ArrayList mapLineCoords;

    public MyBaseMapAndLines(Context context) {
        super(context);
        mapLineCoords = new ArrayList<>();
    }

    public MyBaseMapAndLines(Context context, AttributeSet attrs) {
        super(context, attrs);
        mapLineCoords = new ArrayList<>();
    }

    public ArrayList getMapLineCoords() {
        return mapLineCoords;
    }

    public int getLineSize() {
        return mapLineCoords.size();
    }

    public void clearLines() {
        mapLineCoords.clear();
    }

    public void addLines(ArrayList mapLineCoords) {
        this.mapLineCoords.addAll(mapLineCoords);
    }

    public MapLineCoord getLine(int index) {
        return mapLineCoords.get(index);
    }

    public void addLine(MapLineCoord mapLineCoord) {
        mapLineCoords.add(mapLineCoord);
        this.invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mapLineCoords == null || mapLineCoords.size() <= 0)
            return;
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStrokeWidth((float) 5.0);
        canvas.drawCircle(mapLineCoords.get(0).getViewX(), mapLineCoords.get(0).getViewY(), 10, paint);
        canvas.drawCircle(mapLineCoords.get(mapLineCoords.size() - 1).getViewX(),
                mapLineCoords.get(mapLineCoords.size() - 1).getViewY(), 10, paint);
        // 划线
        for (int i = 1; i < mapLineCoords.size(); i++) {
            canvas.drawLine(mapLineCoords.get(i - 1).getViewX(),
                    mapLineCoords.get(i - 1).getViewY(),
                    mapLineCoords.get(i).getViewX(), mapLineCoords.get(i).getViewY(), paint);
        }
    }

    /**
     * 地图 线 拐点 坐标
     */
    public class MapLineCoord {
        private float firstX;
        private float firstY;

        private float viewX;
        private float viewY;

        public MapLineCoord() {
        }

        public MapLineCoord(float firstX, float firstY, float viewX, float viewY) {
            this.firstX = firstX;
            this.firstY = firstY;
            this.viewX = viewX;
            this.viewY = viewY;
        }

        public float getFirstX() {
            return firstX;
        }

        public void setFirstX(float firstX) {
            this.firstX = firstX;
        }

        public float getFirstY() {
            return firstY;
        }

        public void setFirstY(float firstY) {
            this.firstY = firstY;
        }

        public float getViewX() {
            return viewX;
        }

        public void setViewX(float viewX) {
            this.viewX = viewX;
        }

        public float getViewY() {
            return viewY;
        }

        public void setViewY(float viewY) {
            this.viewY = viewY;
        }

        @Override
        public String toString() {
            return "MapLineCoord{" +
                    "firstX=" + firstX +
                    ", firstY=" + firstY +
                    ", viewX=" + viewX +
                    ", viewY=" + viewY +
                    '}';
        }
    }
}


地图上的 点 也是 一个自定义控件 继承LinearLayout  里面 放了一个 ImageView 显示地图点 的 图片,一个TextView 显示 地图点的 名称

因为 服务器 那边的 地图点的 坐标 是获取 图片左上角的坐标 作为 定义的 ,所以 我这里 对于地图点的 坐标 计算 也是 计算至ImageView的 左上角

要这样计算,就必须 先对这个 地图点的 ViewGroup 所有 进行 测量 宽高 

 /**
     * 测量 地图点 的 边界
     */
    private void measureBorder() {
        int height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        int width = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);

        this.pointIcon.measure(width, height);
        this.pointTitle.measure(width, height);
        this.measure(width, height);
	
	// 是否 显示 地图点 的 标题
        if (!isTitleShow) {
            pointTitle.setVisibility(INVISIBLE);
        } else {
            pointTitle.setVisibility(VISIBLE);
        }


        this.borderLeft = (this.getMeasuredWidth() - this.pointIcon.getMeasuredWidth()) / 2;
        this.borderTop = (this.getMeasuredHeight() - this.pointIcon.getMeasuredHeight() - this.pointTitle.getMeasuredHeight());

        Log.i("testss", this.borderLeft + "fffffff=========" + this.borderTop);
    }


然后 在 设置 显示位置 的 时候 ,调用 这两个方法

 public void setFirstXShow(float x) {
        x -= borderLeft;
        setX(x);
    }

    public void setFirstYShow(float y) {
        y -= borderTop;
        setY(y);
    }

整个 地图 控件  放大缩小,拖动 的 效果 ,是对其 设置 了 setOnTouchListener  ,在 其中 对 按下,抬起,双指触摸,移动事件 进行 记录 ,然后 移动或修改 地图图片,点,线 的 位置 大小 ,其中地图的长按时间 是通过 判断 按下到 抬起时 时间大于2s 并且 位置变化小于 5 个像素 ,如果 自定义监听器 不为空,则 调用其 回调方法

 /**
     * 地图 移动 放大 监听
     */
    private class MyTouchListener implements OnTouchListener {
        /**
         * 记录是拖拉照片模式还是放大缩小照片模式
         */
        private int mode = 0;// 初始状态
        /**
         * 拖拉照片模式
         */
        private static final int MODE_DRAG = 1;
        /**
         * 放大缩小照片模式
         */
        private static final int MODE_ZOOM = 2;

        /**
         * 用于记录开始时候的坐标位置
         */
        private PointF startPoint = new PointF();
        /**
         * 用于记录拖拉图片移动的坐标位置
         */
        private Matrix matrix = new Matrix();
        /**
         * 用于记录图片要进行拖拉时候的坐标位置
         */
        private Matrix currentMatrix = new Matrix();

        /**
         * 两个手指的开始距离
         */
        private float startDis;
        /**
         * 两个手指的中间点
         */
        private PointF midPoint;


        @Override
        public boolean onTouch(View v, MotionEvent event) {
            /** 通过与运算保留最后八位 MotionEvent.ACTION_MASK = 255 */
            switch (event.getAction() & MotionEvent.ACTION_MASK) {
                // 手指压下屏幕
                case MotionEvent.ACTION_DOWN:
                    mode = MODE_DRAG;
                    // 记录ImageView当前的移动位置
                    currentMatrix.set(myBaseMapAndLines.getImageMatrix());
                    startPoint.set(event.getX(), event.getY());

                    // 当 手指 按下时 初始化 长按标志
                    longPressTag = true;
                    downTime = System.currentTimeMillis();

//                Log.i("test", "=onTouch=x==" + event.getX() + "===y==" + event.getY());
                    setFirstDownX(event.getX());
                    setFirstDownY(event.getY());
                    break;
                // 手指在屏幕上移动,改事件会被不断触发
                case MotionEvent.ACTION_MOVE:
                    // 拖拉图片
                    if (mode == MODE_DRAG) {
                        float dx = event.getX() - startPoint.x; // 得到x轴的移动距离
                        float dy = event.getY() - startPoint.y; // 得到x轴的移动距离
                        // 在没有移动之前的位置上进行移动
                        matrix.set(currentMatrix);
                        matrix.postTranslate(dx, dy);
                        // 如果 手指移动 距离不超过 5 个像素点 的 视为 没有移动
                        float offset = (float) Math.sqrt(dx * dx + dy * dy);
                        longPressTag = offset < 5 ? true : false;

                    }

                    // 放大缩小图片
                    else if (mode == MODE_ZOOM) {
                        float endDis = distance(event);// 结束距离
                        if (endDis > 10f) { // 两个手指并拢在一起的时候像素大于10
                            float scale = endDis / startDis;// 得到缩放倍数
                            matrix.set(currentMatrix);
                            matrix.postScale(scale, scale, midPoint.x, midPoint.y);
                        }
                    }

                    break;
                // 手指离开屏幕
                case MotionEvent.ACTION_UP:
                    //如果 按下 抬起 时间 大于 2s 则是 长按 事件
                    longPressTag = System.currentTimeMillis() - downTime > 2000 ? true : false;
                    // 当触点离开屏幕,但是屏幕上还有触点(手指)
                case MotionEvent.ACTION_POINTER_UP:
                    mode = 0;
                    break;
                // 当屏幕上已经有触点(手指),再有一个触点压下屏幕
                case MotionEvent.ACTION_POINTER_DOWN:
                    longPressTag = false;

                    mode = MODE_ZOOM;
                    /** 计算两个手指间的距离 */
                    startDis = distance(event);
                    /** 计算两个手指间的中间点 */
                    if (startDis > 10f) { // 两个手指并拢在一起的时候像素大于10
                        midPoint = mid(event);
                        //记录当前ImageView的缩放倍数
                        currentMatrix.set(myBaseMapAndLines.getImageMatrix());
                    }
                    break;
            }

            /**
             * 如果 此次 触摸事件  是  移动,放大事件
             * 则 改变 地图 和 坐标点的位置
             */
            switch (event.getAction() & MotionEvent.ACTION_MASK) {
                case MotionEvent.ACTION_MOVE:
                    // 移动 地图
                    myBaseMapAndLines.setImageMatrix(matrix);

                    float[] matrixValues = new float[9];
                    matrix.getValues(matrixValues);
                    // 地图原点移动
                    originalPointView.setX((float) (0 * matrixValues[0] + matrixValues[2]));
                    originalPointView.setY((float) (0 * matrixValues[4] + matrixValues[5]));


                    Log.i("testss", originalPointView.getX() + "===originalPointView====" + originalPointView.getY());

                    firstScale = matrixValues[0];

                    // 移动 点
                    for (int i = 0; i < mapPoints.size(); i++) {
                        double scaleX = mapPoints.get(i).getFirstX() * matrixValues[0];
                        double scaleY = mapPoints.get(i).getFirstY() * matrixValues[4];
                        mapPoints.get(i).setFirstXShow((float) (scaleX + matrixValues[2]));
                        mapPoints.get(i).setFirstYShow((float) (scaleY + matrixValues[5]));
                    }

                    // 移动 线
                    for (int i = 0; i < myBaseMapAndLines.getLineSize(); i++) {
                        float v1 = myBaseMapAndLines.getLine(i).getFirstX() * matrixValues[0] + matrixValues[2];
                        float v2 = myBaseMapAndLines.getLine(i).getFirstY() * matrixValues[4] + matrixValues[5];
                        myBaseMapAndLines.getLine(i).setViewX(v1);
                        myBaseMapAndLines.getLine(i).setViewY(v2);
                    }
                    myBaseMapAndLines.invalidate();


                    /**
                     *如果 外层为ScrollView 此句代码是解决
                     * 地图的移动 和 ScrollView 的滚动冲突的
                     * 当触摸事件在地图范围内时,ScrollView 滚动事件无法响应
                     * 当触摸事件在 地图范围外时,ScrollView可以滚动
                     */
                    getParent().requestDisallowInterceptTouchEvent(true);
                    break;
            }

            // 如果 设置 了 长按 监听 则 传递 事件
            // 否则 自己 消费 该 事件
            if (getMapOnLongClickListener() != null) {
                return false;
            }
            return true;
        }


        /**
         * 计算两个手指间的距离
         */
        private float distance(MotionEvent event) {
            float dx = event.getX(1) - event.getX(0);
            float dy = event.getY(1) - event.getY(0);
            /** 使用勾股定理返回两点之间的距离 */
            return (float) Math.sqrt(dx * dx + dy * dy);
        }

        /**
         * 计算两个手指间的中间点
         */
        private PointF mid(MotionEvent event) {
            float midX = (event.getX(1) + event.getX(0)) / 2;
            float midY = (event.getY(1) + event.getY(0)) / 2;
            return new PointF(midX, midY);
        }
    }








你可能感兴趣的:(Android自定义控件)