自定义Surfaceview实现景区室内布展图

/**
 * Author:guojiaojiao
 * Date:2024/1/19 11:11
 * Description:
 */
public class MyMap extends SurfaceView implements SurfaceHolder.Callback {
    private static final String TAG = MyMap.class.getSimpleName();

    private static final long DOUBLE_CLICK_TIME_SPACE = 300;
    private final List<MarkObject> markList = new ArrayList();
    Canvas canvas = null;
    private float mCurrentScaleMax;//最大缩放值
    private float mCurrentScale = 1.0f;//当前缩放值
    private float mCurrentScaleMin;//最小缩放值
    private float windowWidth, windowHeight;//屏幕的宽和高
    private Bitmap mBitmap;//当前的底图
    private Paint mPaint;
    private PointF mStartPoint;//起始坐标
    private volatile PointF mapCenter;// mapCenter表示地图中心在屏幕上的坐标
    private long lastClickTime;// 记录上一次点击屏幕的时间,以判断双击事件
    private Status mStatus = Status.NONE;
    private float oldRate = 1;
    private float oldDist = 1;
    private float offsetX, offsetY;
    private boolean isShu = true;
    private DrawThread mDrawThread;
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            if (mBitmap != null && !mBitmap.isRecycled()) {
                mBitmap.recycle();
            }
            for (MarkObject object : markList) {

                if (object.getmBitmap() != null) {

                    object.getmBitmap().recycle();

                }

            }
            mDrawThread.mHandler.getLooper().quit();
        }
    };

    public MyMap(Context context, AttributeSet attrs, int defStyle) {

        super(context, attrs, defStyle);

// TODO Auto-generated constructor stub

        init();

    }

    public MyMap(Context context, AttributeSet attrs) {

        super(context, attrs);

// TODO Auto-generated constructor stub

        init();

    }

    public MyMap(Context context) {

        super(context);

// TODO Auto-generated constructor stub

        init();

    }

    private void init() {

        SurfaceHolder holder = getHolder();

        holder.addCallback(this);

        // 获取屏幕的宽和高

        windowWidth = getResources().getDisplayMetrics().widthPixels;

        windowHeight = getResources().getDisplayMetrics().heightPixels;

        mPaint = new Paint();

        mStartPoint = new PointF();

        mapCenter = new PointF();

        mDrawThread = new DrawThread();// 开启异步线程绘图

        mDrawThread.start();

    }

    public void setBitmap(Bitmap bitmap) {

//        LogUtils.e("draw", Thread.currentThread());
        if (bitmap == mBitmap || bitmap == null) {
            LogUtils.e("draw", "bitmap====is null!!!!!!!!");
            return;
        }
        if (bitmap.isRecycled()) {
            LogUtils.e("draw", "setBimap  bitmap  isRecycler!!!!!!!!");
            return;
        }


        if (mDrawThread != null && mDrawThread.mHandler != null) {
            mDrawThread.mHandler.removeCallbacksAndMessages(null);
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    //                    把你的setBitMap放到这个线程里来
                    if (mBitmap != null && !mBitmap.isRecycled()) {
                        mBitmap.recycle();
                    }
                    if (bitmap.isRecycled()) {
                        return;
                    }
                    mBitmap = bitmap;

// 设置最小缩放为铺满屏幕,最大缩放为最小缩放的4倍

                    mCurrentScaleMin = Math.min(windowHeight / mBitmap.getHeight(),

                            windowWidth / mBitmap.getWidth());

                    mCurrentScale = mCurrentScaleMin;

                    mCurrentScaleMax = mCurrentScaleMin * 5;

//        mCurrentScaleMax=Math.max(windowHeight / mBitmap.getHeight(),
//
//                windowWidth / mBitmap.getWidth());
//        mapCenter.set(mBitmap.getWidth() * mCurrentScale / 2,
//
//                mBitmap.getHeight() * mCurrentScale / 2);
                    mapCenter.set(windowWidth / 2, windowHeight / 2);

                    //float bitmapRatio = mBitmap.getHeight() / mBitmap.getWidth();
                    float bitmapRatio = mBitmap.getHeight() / mBitmap.getWidth();

                    float winRatio = windowHeight / windowWidth;

// 判断屏幕铺满的情况,isShu为true表示屏幕横向被铺满,为false表示屏幕纵向被铺满

                    isShu = bitmapRatio <= winRatio;
                }
            };
            FutureTask<Void> task = new FutureTask<Void>(runnable, null);
            boolean success = mDrawThread.mHandler.post(task);
            if (success) {
                try {
                    task.get();
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            } else {
            }

        }

        draw();

    }

    /**
     * 为当前地图添加标记
     *
     * @param object
     */

    public void addMark(MarkObject object) {

        markList.add(object);
        draw();

    }

    public void clearMark() {
        markList.clear();
        draw();
    }

    /**
     * 地图放大
     */

    public void zoomIn() {

        mCurrentScale *= 1.5f;

        if (mCurrentScale > mCurrentScaleMax) {

            mCurrentScale = mCurrentScaleMax;

        }

        draw();

    }

    /**
     * 地图缩小
     */

    public void zoomOut() {

        mCurrentScale /= 1.5f;

        if (mCurrentScale < mCurrentScaleMin) {

            mCurrentScale = mCurrentScaleMin;

        }

        if (isShu) {

            if (mapCenter.x - mBitmap.getWidth() * mCurrentScale / 2 > 0) {

                mapCenter.x = mBitmap.getWidth() * mCurrentScale / 2;

            } else if (mapCenter.x + mBitmap.getWidth() * mCurrentScale / 2 < windowWidth) {

                mapCenter.x = windowWidth - mBitmap.getWidth() * mCurrentScale

                        / 2;

            }

            if (mapCenter.y - mBitmap.getHeight() * mCurrentScale / 2 > 0) {

                mapCenter.y = mBitmap.getHeight() * mCurrentScale / 2;

            }

        } else {

            if (mapCenter.y - mBitmap.getHeight() * mCurrentScale / 2 > 0) {

                mapCenter.y = mBitmap.getHeight() * mCurrentScale / 2;

            } else if (mapCenter.y + mBitmap.getHeight() * mCurrentScale / 2 < windowHeight) {

                mapCenter.y = windowHeight - mBitmap.getHeight()

                        * mCurrentScale / 2;

            }

            if (mapCenter.x - mBitmap.getWidth() * mCurrentScale / 2 > 0) {

                mapCenter.x = mBitmap.getWidth() * mCurrentScale / 2;

            }

        }

        draw();

    }

    public void onDestory() {

        FutureTask<Void> task = new FutureTask<Void>(runnable, null);
        if (mDrawThread != null && mDrawThread.mHandler != null) {
            boolean success = mDrawThread.mHandler.post(task);
            if (success) {
                try {
                    task.get(100, TimeUnit.MILLISECONDS);
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            }
        }

       /* if (mDrawThread != null) {

            mDrawThread.mHandler.sendEmptyMessage(1);

        }*/

    }
// 处理拖拽事件

    private void drag(MotionEvent event) {

        PointF currentPoint = new PointF();

        currentPoint.set(event.getX(), event.getY());

        offsetX = currentPoint.x - mStartPoint.x;

        offsetY = currentPoint.y - mStartPoint.y;

// 以下是进行判断,防止出现图片拖拽离开屏幕

        if (mBitmap != null && !mBitmap.isRecycled()) {
            if (offsetX > 0

                    && mapCenter.x + offsetX - mBitmap.getWidth() * mCurrentScale

                    / 2 > 0) {

                offsetX = 0;
            }

        }

        if (offsetX < 0

                && mapCenter.x + offsetX + mBitmap.getWidth() * mCurrentScale

                / 2 < windowWidth) {

            offsetX = 0;

        }

        if (offsetY > 0

                && mapCenter.y + offsetY - mBitmap.getHeight() * mCurrentScale

                / 2 > 0) {

            offsetY = 0;

        }

        if (offsetY < 0

                && mapCenter.y + offsetY + mBitmap.getHeight() * mCurrentScale

                / 2 < windowHeight) {

            offsetY = 0;

        }

        mapCenter.x += offsetX;

        mapCenter.y += offsetY;

        draw();

        mStartPoint = currentPoint;

    }

// 处理多点触控缩放事件

    private void zoomAction(MotionEvent event) {

        float newDist = spacing(event);

        if (newDist > 10.0f) {

            mCurrentScale = oldRate * (newDist / oldDist);

            if (mCurrentScale < mCurrentScaleMin) {

                mCurrentScale = mCurrentScaleMin;

            } else if (mCurrentScale > mCurrentScaleMax) {

                mCurrentScale = mCurrentScaleMax;

            }

            if (isShu) {

                if (mapCenter.x - mBitmap.getWidth() * mCurrentScale / 2 > 0) {

                   
                    mapCenter.x = mBitmap.getWidth() * mCurrentScale / 2;
//                    mapCenter.x=windowWidth/2;

                } else if (mapCenter.x + mBitmap.getWidth() * mCurrentScale / 2 < windowWidth) {
                    
                    mapCenter.x = windowWidth - mBitmap.getWidth()

                            * mCurrentScale / 2;
//                    mapCenter.x = windowWidth - mBitmap.getWidth()
//
//                            / 2;

                }

                if (mapCenter.y - mBitmap.getHeight() * mCurrentScale / 2 > 0) {
//                    mapCenter.y = mBitmap.getHeight() * mCurrentScale / 2;
                    mapCenter.y = mBitmap.getHeight() * mCurrentScale / 2 + (windowHeight - mBitmap.getHeight() * mCurrentScale) / 2;
//                    mapCenter.y=windowHeight/2-(mBitmap.getHeight()*mCurrentScale)+(mBitmap.getHeight()*mCurrentScale)/2;

                }

            } else {

                if (mapCenter.y - mBitmap.getHeight() * mCurrentScale / 2 > 0) {

//                    mapCenter.y = mBitmap.getHeight() * mCurrentScale / 2;
                    mapCenter.y = mBitmap.getHeight() * mCurrentScale / 2 + (windowHeight - mBitmap.getHeight() * mCurrentScale) / 2;

                } else if (mapCenter.y + mBitmap.getHeight() * mCurrentScale

                        / 2 < windowHeight) {

                    mapCenter.y = windowHeight - mBitmap.getHeight()

                            * mCurrentScale / 2;

                }

                if (mapCenter.x - mBitmap.getWidth() * mCurrentScale / 2 > 0) {

                    mapCenter.x = mBitmap.getWidth() * mCurrentScale / 2;
//                    mapCenter.x = windowWidth / 2;

                }

            }

        }

        draw();

    }

// 处理点击标记的事件

    private void clickAction(MotionEvent event) {

        int clickX = (int) event.getX();

        int clickY = (int) event.getY();

        for (MarkObject object : markList) {

            Bitmap location = object.getmBitmap();

            int objX = (int) (mapCenter.x - location.getWidth() / 2

                    - mBitmap.getWidth() * mCurrentScale / 2 + mBitmap

                    .getWidth() * object.getMapX() * mCurrentScale);

            int objY = (int) (mapCenter.y - location.getHeight()

                    - mBitmap.getHeight() * mCurrentScale / 2 + mBitmap

                    .getHeight() * object.getMapY() * mCurrentScale);

// 判断当前object是否包含触摸点,在这里为了得到更好的点击效果,我将标记的区域放大了

            if (objX - location.getWidth() < clickX

                    && objX + location.getWidth() > clickX

                    && objY + location.getHeight() > clickY

                    && objY - location.getHeight() < clickY) {

                if (object.getMarkListener() != null) {

                    object.getMarkListener()

                            .onMarkClick(clickX, clickY, object.getScenicSpotViewSpot());

                }

                break;

            }

        }

    }

// 计算两个触摸点的距离

    private float spacing(MotionEvent event) {

        float x = event.getX(0) - event.getX(1);

        float y = event.getY(0) - event.getY(1);

        return (float) Math.sqrt(x * x + y * y);

    }

    private void draw() {
        Log.e("gjj", "=========draw=====" + Integer.toHexString(getWindowVisibility()));
        if (mDrawThread != null && mDrawThread.mHandler != null
                && getWindowVisibility() == VISIBLE) {
            mDrawThread.mHandler.sendEmptyMessage(0);
        }
    }

    @Override

    public boolean onTouchEvent(MotionEvent event) {

// TODO Auto-generated method stub

        switch (event.getAction() & MotionEvent.ACTION_MASK) {

            case MotionEvent.ACTION_DOWN:

                if (event.getPointerCount() == 1) {

                    // 如果两次点击时间间隔小于一定值,则默认为双击事件

                    if (event.getEventTime() - lastClickTime < DOUBLE_CLICK_TIME_SPACE) {

                        zoomIn();

                    } else {

                        mStartPoint.set(event.getX(), event.getY());

                        mStatus = Status.DRAG;

                    }

                }

                lastClickTime = event.getEventTime();

                break;

            case MotionEvent.ACTION_POINTER_DOWN:

                float distance = spacing(event);

                if (distance > 10f) {

                    mStatus = Status.ZOOM;

                    oldDist = distance;

                }

                break;

            case MotionEvent.ACTION_MOVE:

                if (mStatus == Status.DRAG) {

                    try {
                        drag(event);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                } else if (mStatus == Status.ZOOM) {

                    zoomAction(event);

                }

                break;

            case MotionEvent.ACTION_UP:

                if (mStatus != Status.ZOOM) {

                    clickAction(event);

                }

            case MotionEvent.ACTION_POINTER_UP:

                oldRate = mCurrentScale;

                mStatus = Status.NONE;

                break;

            default:

                break;

        }

        return true;

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
//        LogUtils.e("draw","surfaceCreated()");
        Log.e("gjj", "surfaceCreated:");
        draw();

    }

    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        Log.e("gjj", "onWindowVisibilityChanged:" + Integer.toHexString(visibility));
        super.onWindowVisibilityChanged(visibility);
    }

    @Override

    public void surfaceChanged(SurfaceHolder holder, int format, int width,

                               int height) {

// TODO Auto-generated method stub

    }

    @Override

    public void surfaceDestroyed(SurfaceHolder holder) {

        Log.e("gjj", "surfaceDestroyed:" + canvas);
        mDrawThread.mHandler.removeCallbacksAndMessages(null);
        FutureTask<Void> task = new FutureTask<Void>(new Runnable() {
            @Override
            public void run() {
                Log.e("gjj", "run:结束");
            }
        }, null);
        if (mDrawThread.mHandler.post(task)) {
            try {
                task.get();
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }

    }

    private enum Status {

        NONE, ZOOM, DRAG

    }

    public class DrawThread extends Thread {

        public Handler mHandler;

        @Override

        public void run() {

// TODO Auto-generated method stub

            Looper.prepare();

            synchronized (this) {

                mHandler = new Handler(Looper.myLooper()) {// 当接收到绘图消息时,重新进行绘图

                    @Override
                    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
                        Log.e("gjj", "sendMessageAtTime:" + msg.what + "  run:" + msg.getCallback());
                        if (getWindowVisibility() != View.VISIBLE && msg.getCallback() == null) {
                            Log.e("gjj", Log.getStackTraceString(new NullPointerException("是谁还在发消息")));
                        }
                        return super.sendMessageAtTime(msg, uptimeMillis);
                    }

                    @Override
                    public void dispatchMessage(@NonNull Message msg) {
                        try {
                            super.dispatchMessage(msg);
                        } catch (Throwable throwable) {
                            Log.e("gjj", Log.getStackTraceString(throwable));
                        }
                    }

                    @Override

                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);

                        switch (msg.what) {

                            case 1:// 销毁绘图线程

                                Looper.myLooper().quit();
                                break;

                            default:// 绘图
                                Log.e("gjj", "draw开始:" + Integer.toHexString(getWindowVisibility()));
                                if (getWindowVisibility() != VISIBLE) {
                                    return;
                                }
                                Canvas canvas = null;
                                Bitmap bitmap = mBitmap;
                                try {
                                    canvas = getHolder().lockCanvas();
                                    if (canvas != null) {
                                        MyMap.this.canvas = canvas;
                                    }
                                    if (canvas != null && bitmap != null && !bitmap.isRecycled()) {
                                        canvas.drawColor(getContext().getColor(R.color.color_FFFEFD));
                                        Matrix matrix = new Matrix();
                                        matrix.setScale(mCurrentScale, mCurrentScale, bitmap.getWidth() / 2, bitmap.getHeight() / 2);
                                        matrix.postTranslate(
                                                mapCenter.x - bitmap.getWidth() / 2,
                                                mapCenter.y - bitmap.getHeight() / 2);
                                        canvas.drawBitmap(bitmap, matrix, mPaint);
                                        for (MarkObject object : markList) {
                                            Bitmap location = object.getmBitmap();
                                            matrix.setScale(1.0f, 1.0f);
// 使用Matrix使得Bitmap的宽和高发生变化,在这里使用的mapX和mapY都是相对值
                                            float x = mapCenter.x - location.getWidth() / 2 - bitmap.getWidth()

                                                    * mCurrentScale / 2

                                                    + bitmap.getWidth()

                                                    * object.getMapX()

                                                    * mCurrentScale;
                                            float y = mapCenter.y - location.getHeight()

                                                    - bitmap.getHeight()

                                                    * mCurrentScale / 2

                                                    + bitmap.getHeight()

                                                    * object.getMapY()

                                                    * mCurrentScale;
                                            matrix.postTranslate(x, y);
//                                        matrix.postTranslate(20,20);
                                            canvas.drawBitmap(location, matrix, mPaint);
                                        }
                                    }

                                } catch (Throwable throwable) {

                                } finally {
                                    Log.e("gjj", "draw结束" + canvas + " ?"
                                            + getHolder().getSurface().isValid() + " " + Integer.toHexString(getWindowVisibility()));
                                    if (canvas != null) {
                                        MyMap.this.canvas = null;
                                        if (getHolder().getSurface().isValid()) {
                                            getHolder().unlockCanvasAndPost(canvas);
                                        } else {
                                            Log.e("gjj", "没有释放canvas");
                                        }
                                    }
                                }


                                break;

                        }

                    }

                };

            }

            Looper.loop();

        }

    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值