Android限制屏幕缩放至DriwingView

时间:2018-07-21 15:24:43

标签: java android

问题的实质如下:当图像放大时,可以左右左右上下移动,直至完全消失。问题是,如何在此处设置边界,以便缩放图像,但几乎没有或根本没有压痕地粘在屏幕的角落?或至少告诉我1)哪个代码段负责此工作? 2)告诉我代码的哪一部分负责放大图像(这里的门框也是,放大时,如果是以前的两个手指,则图像会移动到从第二个屏幕释放的手指,这也很愚蠢,并且在展开时会歪曲图片,因此总是会出现抽搐))。我将不胜感激,感谢您提供任何帮助,或者至少是指导性的阅读内容。我没有找到类似的情况。我不太了解,但是没有可能雇用一名程序员。谢谢!抱歉,我正在通过口译员(

public class DrawingView extends View {
    private static final String TAG = DrawingView.class.getSimpleName();
    private static final float MOVE_TOLERANCE = 2.0f;
    private static final float TOUCH_TOLERANCE = 4.0f;
    private static final int MODE_SINGLETOUCH = 1;
    private static final int MODE_MULTITOUCH = 2;
    private static final int MODE_NONE = 0;
    public static final float MAX_SCALING = 3.0f;
    public static final float MIN_SCALING = 1.0f;
    public interface DrawingViewHandler {
        RectF getCropingRect();
        void setCropingRectVisibility(boolean visibility);
    }
    public interface ScaleValueResolver {
        float getValue();
    }
    public interface ScalingObserver {
        boolean processScaleChanged(ScalingEvent event);
        void processMoveEvent(MoveEvent event);
        void onClearScale();
    }

    public static class MoveEvent {
        private float dx;
        private float dy;

        public MoveEvent(float dx, float dy) {
            this.dx = dx;
            this.dy = dy;
        }

        public float getDx() {
            return dx;
        }

        public float getDy() {
            return dy;
        }
    }

    public static class ScalingEvent {

        private float _x;
        private float _y;
        private float _factor;

        public ScalingEvent(float x, float y, float factor) {
            this._x = x;
            this._y = y;
            this._factor = factor;
        }

        public float getX() {
            return _x;
        }

        public ScalingEvent setX(float _x) {
            this._x = _x;
            return this;
        }

        public float getY() {
            return _y;
        }

        public ScalingEvent setY(float _y) {
            this._y = _y;
            return this;
        }

        public float getFactor() {
            return _factor;
        }

        public ScalingEvent setFactor(float _factor) {
            this._factor = _factor;
            return this;
        }
    }

    private class DrawingViewScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {

            float scaleFactor = getScaleFactor() * detector.getScaleFactor();
            if (_lastScaleX < 0) {
                _lastScaleX = detector.getFocusX();
            }
            if (_lastScaleY < 0) {
                _lastScaleY = detector.getFocusY();
            }
            boolean observersScaled = false;
            if(Math.abs(1 - detector.getScaleFactor()) > 0.01){
                if (detector.getScaleFactor() < 1 && (scaleFactor - getMinScaleFactor()) < ImageUtils.SCALE_LIMIT) {
                    resetScaling();
                    observersScaled = true;
                } else {
                    if(scaleFactor > MAX_SCALING){
                        scaleFactor = MAX_SCALING;
                    }

                    if(_scalingObserver != null){
                        ScalingEvent scaleEvent = new ScalingEvent(
                                _lastScaleX,
                                _lastScaleY,
                                detector.getScaleFactor());
                        if(BuildConfig.DEBUG) {
                            Log.d(TAG, String.format("posting scale = %f at (%f, %f); current drawingVIew scale = %f",
                                    detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY(), getScaleFactor()));
                        }
                        observersScaled = _scalingObserver.processScaleChanged(scaleEvent);
                    }

                    if (observersScaled) {
                        if(BuildConfig.DEBUG) {
                            Log.d(TAG, "observers scaled, updating drawing view scale to " + scaleFactor);
                        }
                        setScaleFactor(scaleFactor);
                        invalidate();
                    }
                }
            }

            if(BuildConfig.DEBUG) {
                Log.d(TAG, String.format("scaleFactor = %f (delta = %f); lastScaleX = %f, lastScaleY = %f; isScaled = %s",
                        scaleFactor, detector.getScaleFactor(), _lastScaleX, _lastScaleY, String.valueOf(observersScaled)));
            }
            return observersScaled;
        }
    }

    private static class RecalculateCropingAreaTaskArgs {

        private int bitmapHeight;
        private int bitmapWidth;
        private RectF cropingRect;

        public int getBitmapHeight() {
            return bitmapHeight;
        }

        public RecalculateCropingAreaTaskArgs setBitmapHeight(int bitmapHeight) {
            this.bitmapHeight = bitmapHeight;
            return this;
        }

        public int getBitmapWidth() {
            return bitmapWidth;
        }

        public RecalculateCropingAreaTaskArgs setBitmapWidth(int bitmapWidth) {
            this.bitmapWidth = bitmapWidth;
            return this;
        }

        public RectF getCropingRect() {
            return cropingRect;
        }

        public RecalculateCropingAreaTaskArgs setCropingRect(RectF cropingRect) {
            this.cropingRect = cropingRect;
            return this;
        }
    }

    private class RecalculateCropingAreaTask extends AsyncTask<RecalculateCropingAreaTaskArgs, Void, float[]> {

        private static final int CALC_STEP = 2;

        @Override
        protected float[] doInBackground(RecalculateCropingAreaTaskArgs... args) {
            float[] coords = new float[4];
            long start = System.currentTimeMillis();
            doCalculation(args[0], coords);
            long finished = System.currentTimeMillis();
            Log.d(TAG, "recalculateLineCropingArea() finished in " + (finished - start) + " ms");
            return coords;
        }

        private void doCalculation(RecalculateCropingAreaTaskArgs arg, float[] result) {
            int height = arg.getBitmapHeight();
            int width = arg.getBitmapWidth();
            int pixelsCount = 0;
            RectF croppingRect = arg.getCropingRect();
            for (int x = 0; x < width && !isCancelled(); x += CALC_STEP) {
                for (int y = 0; y < height && !isCancelled(); y += CALC_STEP) {
                    boolean isInCrop = croppingRect != null && croppingRect.contains(x, y);
                    if (!isInCrop) {
                        continue;
                    }
                    // todo: избавиться от обращения к bitmap
                    try {
                        if (_bitmap.getPixel(x, y) == 0) {
                            pixelsCount = 0;
                            continue;
                        }
                        pixelsCount += 1;
                        if (pixelsCount > 10) {
                            updateLineCropingArea(x, y, result);
                        }
                    } catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }

        private void updateLineCropingArea(float x, float y, float[] result) {
            if (x < result[0] || result[0] == 0) {
                result[0] = x;
            } else if (x > result[2]) {
                result[2] = x;
            }
            if (y < result[1] || result[1] == 0) {
                result[1] = y;
            } else if (y > result[3]) {
                result[3] = y;
            }
        }

        @Override
        protected void onPostExecute(float[] result) {
            _pendingRecalculateCropAreaTask = null;
            if(!isCancelled()){
                _minX = result[0];
                _minY = result[1];
                _maxX = result[2];
                _maxY = result[3];
            }
        }
    }

    private RecalculateCropingAreaTask _pendingRecalculateCropAreaTask = null;
    private Matrix _currentCanvasMatrix = null;
    private float[] _currentCanvasMatrixValues = null;
    private Paint _paint;
    private Bitmap _bitmap;
    private Canvas _canvas;
    private Path _path;
    private Paint _bitmapPaint;
    private Paint _eraserPaint;
    private DrawingViewHandler _drawingViewHandler;
    private ScaleValueResolver _minScaleResolver;
    private ScaleValueResolver _maxScaleResolver;
    private final PathMeasure _pathMeasure;
    private ScaleGestureDetector _scaleGestureDetector;
    private ScalingObserver _scalingObserver = null;

    private float _maxX = 0;
    private float _maxY = 0;
    private float _minX = 0;
    private float _minY = 0;

    private float _mX;
    private float _mY;
    private float _lastPathDistance = 0.0f;

    private float _scaleFactor = 0;
    private float _lastScaleX = -1;
    private float _lastScaleY = -1;

    private CoordsPoint _lastMovePoint = null;
    private CoordsPoint _translate = new CoordsPoint();

    private int _touchMode = MODE_NONE;

    public DrawingView(Context context) {
        super(context);

        _path = new Path();
        _pathMeasure = new PathMeasure();
        _bitmapPaint = new Paint();
        _bitmapPaint.setColor(Color.RED);
        _eraserPaint = new Paint();
        _eraserPaint.setColor(Color.TRANSPARENT);
        _eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        _eraserPaint.setAntiAlias(true);

        _scaleGestureDetector = new ScaleGestureDetector(context, new DrawingViewScaleListener());
    }

    public void setHandler(DrawingViewHandler handler) {
        _drawingViewHandler = handler;
    }

    public void setMinScaleResolver(ScaleValueResolver resolver) {
        _minScaleResolver = resolver;
    }

    public void setMaxScaleResolver(ScaleValueResolver resolver) {
        _maxScaleResolver = resolver;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if(w != oldw && h != oldh){
            cancelPendingRecalculation();

            if (_bitmap != null) {
                _bitmap.recycle();
            }

            _bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            _canvas = new Canvas(_bitmap);

            _maxX = 0;
            _maxY = 0;
            _minX = 0;
            _minY = 0;

            _translate = new CoordsPoint();
        }
    }

    private void checkPendingRecalculationFinished() {
        if (_pendingRecalculateCropAreaTask != null) {
            try {
                _pendingRecalculateCropAreaTask.get();
            } catch (Exception e) {
                Log.w(TAG, "_pendingRecalculateCropAreaTask.get() failed!", e);
            }
        }
    }

    private void cancelPendingRecalculation() {
        if (_pendingRecalculateCropAreaTask != null) {
            _pendingRecalculateCropAreaTask.cancel(true);
        }
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);

        float scaleFactor = getScaleFactor();
        if (_lastScaleX >= 0) {
            canvas.scale(scaleFactor, scaleFactor, _lastScaleX, _lastScaleY);
        }
        canvas.translate(_translate.getX(), _translate.getY());
        canvas.drawBitmap(_bitmap, 0, 0, _bitmapPaint);
        canvas.drawPath(_path, getPaint());

        _currentCanvasMatrix = canvas.getMatrix();
    }

    private Paint getPaint() {
        int cropLineColor = AppPreferences.getInstance().getCropLineColor();
        if (_paint == null || _paint.getColor() != cropLineColor) {
            _paint = new Paint();
            _paint.setAntiAlias(true);
            _paint.setDither(true);
            _paint.setColor(cropLineColor);
            _paint.setStyle(Paint.Style.STROKE);
            _paint.setStrokeJoin(Paint.Join.ROUND);
            _paint.setStrokeCap(Paint.Cap.ROUND);
            _paint.setStrokeWidth(AppPreferences.getInstance().getLineThickness() / getScaleFactor());
        }
        return _paint;
    }

    private void handleTouchStart(float x, float y) {
        _mX = x;
        _mY = y;
        _lastPathDistance = 0.0f;
        _path.moveTo(x, y);

        if (isEraserMode()) {
            eraseAtPoint((int) x, (int) y);
        }

        if(BuildConfig.DEBUG){
            Log.d(TAG, "touch start @ x = " + _mX + "; y = " + _mY);
        }
    }

    private void processMoveCanvasToPoint(float x, float y) {
        if(_lastMovePoint == null){
            _lastMovePoint = new CoordsPoint(x, y);
            if(BuildConfig.DEBUG) {
                Log.d(TAG, "move start @ x = " + _lastMovePoint.getX() + "; y = " + _lastMovePoint.getY());
            }
        } else {
            final float sensitivity = 0.4f;
            float dx = (x - _lastMovePoint.getX()) * sensitivity;
            float dy = (y - _lastMovePoint.getY()) * sensitivity;
            if (Math.abs(dx) >= MOVE_TOLERANCE || Math.abs(dy) >= MOVE_TOLERANCE){
                _translate.update(dx, dy);
                _lastMovePoint.setX(x);
                _lastMovePoint.setY(y);

                if(_scalingObserver != null){
                    _scalingObserver.processMoveEvent(new MoveEvent(dx * getScaleFactor(), dy * getScaleFactor()));
                }

                if(BuildConfig.DEBUG){
                    Log.d(TAG, "move canvas at dx = " + dx + "; dy = " + dy);
                }
            }
        }
    }

    private void handleTouchMove(float x, float y) {
        float dx = Math.abs(x - _mX);
        float dy = Math.abs(y - _mY);
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            _path.quadTo(_mX, _mY, (x + _mX) / 2, (y + _mY) / 2);
            _mX = x;
            _mY = y;
            if (isEraserMode()) {
                _pathMeasure.setPath(_path, false);
                float length = _pathMeasure.getLength();
                float[] coords = {0.0f, 0.0f};
                for (float distance = _lastPathDistance; distance < length; distance += 1) {
                    if (_pathMeasure.getPosTan(distance, coords, null)) {
                        eraseAtPoint((int) coords[0], (int) coords[1]);
                        _lastPathDistance = distance;
                    }
                }
            }
        }
    }

    private void eraseAtPoint(int x, int y) {
        _canvas.drawCircle(
                x,
                y,
                AppPreferences.getInstance().getLineThickness() / (getScaleFactor() * 2),
                _eraserPaint
        );
    }

    private void handleTouchUp() {
        _path.lineTo(_mX, _mY);
        _canvas.drawPath(_path, getPaint());
        _path.reset();
        recalculateLineCropingArea();

        if(BuildConfig.DEBUG){
            Log.d(TAG, "touch up!");
        }
    }

    private boolean isEraserMode() {
        return getPaint().getColor() == Color.TRANSPARENT;
    }

    private boolean isSingleTouchMode() {
        return _touchMode == MODE_SINGLETOUCH;
    }

    private boolean isMultiTouchMode() {
        return _touchMode == MODE_MULTITOUCH;
    }

    private float getMinScaleFactor() {
        if (_minScaleResolver != null) {
            return _minScaleResolver.getValue();
        }
        return MIN_SCALING;
    }

    private float getMaxScaleFactor() {
        if (_maxScaleResolver != null) {
            return _maxScaleResolver.getValue();
        }
        return MAX_SCALING;
    }

    private float getScaleFactor() {
        if (_scaleFactor == 0) {
            setScaleFactor(MIN_SCALING);
            if (_minScaleResolver != null) {
                setScaleFactor(_minScaleResolver.getValue());
            }
        }
        return _scaleFactor;
    }

    private void clearScaleFactor(){
        setScaleFactor(getMinScaleFactor());
        _lastScaleX = -1;
        _lastScaleY = -1;
    }

    private void setScaleFactor(float val) {
        _scaleFactor = val;
        _paint = null;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // touch coords
        float x = event.getX();
        float y = event.getY();

        boolean isScaled = false;
        // translate coordinates according to scaling
        if (_currentCanvasMatrix != null) {
            if (_currentCanvasMatrixValues == null) {
                _currentCanvasMatrixValues = new float[9];
            }

            _currentCanvasMatrix.getValues(_currentCanvasMatrixValues);

            if(!isMultiTouchMode()){
                x = (_currentCanvasMatrixValues[Matrix.MTRANS_X] * -1 + x) / _currentCanvasMatrixValues[Matrix.MSCALE_X];
                y = (_currentCanvasMatrixValues[Matrix.MTRANS_Y] * -1 + y) / _currentCanvasMatrixValues[Matrix.MSCALE_Y];
            }

            isScaled = _currentCanvasMatrixValues[Matrix.MSCALE_X] > (getMinScaleFactor() + 0.05);
            if(_drawingViewHandler != null){
                _drawingViewHandler.setCropingRectVisibility(!isScaled);
            }
        }

        // check if touch was in croping area
        boolean isInCrop = false;
        if (_drawingViewHandler != null) {
            RectF croppingRect = _drawingViewHandler.getCropingRect();
            isInCrop = croppingRect.contains(x, y);
        }

        if(isInCrop){
            _scaleGestureDetector.onTouchEvent(event);
        }

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                _touchMode = MODE_SINGLETOUCH;
                handleTouchStart(x, y);
                invalidate();
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                _touchMode = MODE_MULTITOUCH;
                _path.reset();
                _lastMovePoint = null;
                break;
            case MotionEvent.ACTION_MOVE:
                if (isInCrop) {
                    if(!isSingleTouchMode()){
                        if(getScaleFactor() > getMinScaleFactor())
                            processMoveCanvasToPoint(x, y);
                    } else {
                        handleTouchMove(x, y);
                    }
                    invalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
                if (isSingleTouchMode()) {
                    handleTouchUp();
                }
                invalidate();
                _touchMode = MODE_NONE;
                break;
            default:
                break;
        }

        // Log.d(TAG,  "[" + _touchMode + "]" + " touch coords = [" + event.getX() + ";" + event.getY() + "]; isInCrop = " + isInCrop);

        return isScaled || isInCrop;
    }

    private void recalculateLineCropingArea() {

        RecalculateCropingAreaTaskArgs arg = new RecalculateCropingAreaTaskArgs()
                .setBitmapHeight(_bitmap.getHeight())
                .setBitmapWidth(_bitmap.getWidth());

        if (_drawingViewHandler != null) {
            arg.setCropingRect(_drawingViewHandler.getCropingRect());
        }
        _pendingRecalculateCropAreaTask = new RecalculateCropingAreaTask();
        _pendingRecalculateCropAreaTask.execute(arg);
    }

    private void updateLineCropingArea(float x, float y) {
        if (x < _minX || _minX == 0) {
            _minX = x;
        } else if (x > _maxX) {
            _maxX = x;
        }
        if (y < _minY || _minY == 0) {
            _minY = y;
        } else if (y > _maxY) {
            _maxY = y;
        }
    }

    public Bitmap getBitmap() {
        return _bitmap;
    }

    public RectF getLineRect() {
        return new RectF(_minX, _minY, _maxX, _maxY);
    }

    public void waitForPendingCalculations(){
        checkPendingRecalculationFinished();
    }

    public void clearPath() {
        if (_bitmap == null) {
            return;
        }

        cancelPendingRecalculation();

        int width = _bitmap.getWidth();
        int height = _bitmap.getHeight();

        _bitmap.recycle();
        _bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        _canvas = new Canvas(_bitmap);

        _maxX = 0;
        _maxY = 0;
        _minX = 0;
        _minY = 0;

        _translate = new CoordsPoint();

        invalidate();
    }

    public void setScalingObserver(ScalingObserver observer) {
        _scalingObserver = observer;
    }

    public void resetScaling() {
        resetDrawingScaling();

        if(_scalingObserver != null){
            _scalingObserver.onClearScale();
        }

        _translate = new CoordsPoint();

        invalidate();
    }

    public void resetDrawingScaling(){
        clearScaleFactor();
        invalidate();
    }

    public void resetDrawingScalingo(){
        _paint = null;
    }
}

0 个答案:

没有答案