View with horizontal and vertical pan/drag and pinch-zoom

前端 未结 9 2021
离开以前
离开以前 2020-11-28 22:56

Is it possible to have a view who supports horizontal and vertical pan/drag. On top of that, I want to be able to pinch to zoom and double tap to zoom. Does this view exists

9条回答
  •  轻奢々
    轻奢々 (楼主)
    2020-11-28 23:46

    I came with this solution (A mix of your codes and some of my ideas) :

    • double tap zoom and unzoom
    • zoom and unzoom afterdouble tap (with one finger)
    • pan around ok
    • pinch zoom ok and it zooms where you point
    • child views are touchable
    • layout with border ! (Cant get off the layout by unzooming or by panning around)

    its not animated but fullyworking. enjoy

    Usage :

    
    
        
    
            
    
        
    
    

    and here is the zoomableViewGroup Java file, just copy and use :

    public class ZoomableViewGroup extends ViewGroup {
    
        private boolean doubleTap = false;
    
    
        private float MIN_ZOOM = 1f;
        private float MAX_ZOOM = 2.5f;
        private float[] topLeftCorner = {0, 0};
        private float scaleFactor;
    
        // States.
        private static final byte NONE = 0;
        private static final byte DRAG = 1;
        private static final byte ZOOM = 2;
    
        private byte mode = NONE;
    
        // Matrices used to move and zoom image.
        private Matrix matrix = new Matrix();
        private Matrix matrixInverse = new Matrix();
        private Matrix savedMatrix = new Matrix();
    
        // Parameters for zooming.
        private PointF start = new PointF();
        private PointF mid = new PointF();
        private float oldDist = 1f;
        private float[] lastEvent = null;
        private long lastDownTime = 0l;
        private long downTime = 0l;
    
        private float[] mDispatchTouchEventWorkingArray = new float[2];
        private float[] mOnTouchEventWorkingArray = new float[2];
    
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            mDispatchTouchEventWorkingArray[0] = ev.getX();
            mDispatchTouchEventWorkingArray[1] = ev.getY();
            mDispatchTouchEventWorkingArray = screenPointsToScaledPoints(mDispatchTouchEventWorkingArray);
            ev.setLocation(mDispatchTouchEventWorkingArray[0], mDispatchTouchEventWorkingArray[1]);
            return super.dispatchTouchEvent(ev);
        }
    
        public ZoomableViewGroup(Context context) {
            super(context);
        }
    
        public ZoomableViewGroup(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public ZoomableViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
    
        /**
         * Determine the space between the first two fingers
         */
        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);
        }
    
        /**
         * Calculate the mid point of the first two fingers
         */
        private void midPoint(PointF point, MotionEvent event) {
            float x = event.getX(0) + event.getX(1);
            float y = event.getY(0) + event.getY(1);
            point.set(x / 2, y / 2);
        }
    
        private float[] scaledPointsToScreenPoints(float[] a) {
            matrix.mapPoints(a);
            return a;
        }
    
        private float[] screenPointsToScaledPoints(float[] a) {
            matrixInverse.mapPoints(a);
            return a;
        }
    
    
        @Override
        public void onLayout(boolean changed, int left, int top, int right, int bottom) {
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                if (child.getVisibility() != GONE) {
                    child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
                }
            }
        }
    
        @Override
        public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
           super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                if (child.getVisibility() != GONE) {
                    measureChild(child, widthMeasureSpec, heightMeasureSpec);
                }
            }
        }
    
        @Override
        public void dispatchDraw(Canvas canvas) {
            float[] values = new float[9];
            matrix.getValues(values);
            canvas.save();
            canvas.translate(values[Matrix.MTRANS_X], values[Matrix.MTRANS_Y]);
            canvas.scale(values[Matrix.MSCALE_X], values[Matrix.MSCALE_Y]);
            topLeftCorner[0] = values[Matrix.MTRANS_X];
            topLeftCorner[1] = values[Matrix.MTRANS_Y];
            scaleFactor = values[Matrix.MSCALE_X];
            super.dispatchDraw(canvas);
            canvas.restore();
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            // handle touch events here
            mOnTouchEventWorkingArray[0] = event.getX();
            mOnTouchEventWorkingArray[1] = event.getY();
    
            mOnTouchEventWorkingArray = scaledPointsToScreenPoints(mOnTouchEventWorkingArray);
    
            event.setLocation(mOnTouchEventWorkingArray[0], mOnTouchEventWorkingArray[1]);
    
            switch (event.getAction() & MotionEvent.ACTION_MASK) {
                case MotionEvent.ACTION_DOWN:
                    savedMatrix.set(matrix);
                    mode = DRAG;
                    lastEvent = null;
                    downTime = SystemClock.elapsedRealtime();
                    if (downTime - lastDownTime < 250l) {
                        doubleTap = true;
                        float density = getResources().getDisplayMetrics().density;
                        if (Math.max(Math.abs(start.x - event.getX()), Math.abs(start.y - event.getY())) < 40.f * density) {
                            savedMatrix.set(matrix);                                                         //repetition of savedMatrix.setmatrix
                            mid.set(event.getX(), event.getY());
                            mode = ZOOM;
                            lastEvent = new float[4];
                            lastEvent[0] = lastEvent[1] = event.getX();
                            lastEvent[2] = lastEvent[3] = event.getY();
                        }    
                        lastDownTime = 0l;
                    } else {
                        doubleTap = false;
                        lastDownTime = downTime;
                    }
                    start.set(event.getX(), event.getY());
    
                    break;
                case MotionEvent.ACTION_POINTER_DOWN:
                    oldDist = spacing(event);
                    if (oldDist > 10f) {
                        savedMatrix.set(matrix);
                        midPoint(mid, event);
                        mode = ZOOM;
                    }
                    lastEvent = new float[4];
                    lastEvent[0] = event.getX(0);
                    lastEvent[1] = event.getX(1);
                    lastEvent[2] = event.getY(0);
                    lastEvent[3] = event.getY(1);
                    break;
                case MotionEvent.ACTION_UP:
    
    
                    if (doubleTap && scaleFactor < 1.8f){
                        matrix.postScale(2.5f/scaleFactor, 2.5f/scaleFactor, mid.x, mid.y);
                    } else if(doubleTap && scaleFactor >= 1.8f){
                        matrix.postScale(1.0f/scaleFactor, 1.0f/scaleFactor, mid.x, mid.y);
                    }
    
                    Handler handler = new Handler();
                    handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            if(topLeftCorner[0] >= 0){
                                matrix.postTranslate(-topLeftCorner[0],0);
                            } else if (topLeftCorner[0] < -getWidth()*(scaleFactor-1)){
                                matrix.postTranslate((-topLeftCorner[0]) - getWidth()*(scaleFactor-1) ,0);
                            }
                            if(topLeftCorner[1] >= 0){
                                matrix.postTranslate(0,-topLeftCorner[1]);
                            } else if (topLeftCorner[1] < -getHeight()*(scaleFactor-1)){
                                matrix.postTranslate(0,(-topLeftCorner[1]) - getHeight()*(scaleFactor-1));
                            }
                            matrix.invert(matrixInverse);
                            invalidate();
                        }
                    }, 1);
    
                    break;
    
                case MotionEvent.ACTION_POINTER_UP:
                    mode = NONE;
                    lastEvent = null;
                    break;
                case MotionEvent.ACTION_MOVE:
    
                    final float density = getResources().getDisplayMetrics().density;
                    if (mode == DRAG) {
                        matrix.set(savedMatrix);
                        float dx = event.getX() - start.x;
                        float dy = event.getY() - start.y;
                        matrix.postTranslate(dx, dy);
                        matrix.invert(matrixInverse);
                        if (Math.max(Math.abs(start.x - event.getX()), Math.abs(start.y - event.getY())) > 20.f * density) {
                            lastDownTime = 0l;
                        }
                    } else if (mode == ZOOM) {
                        if (event.getPointerCount() > 1) {
                            float newDist = spacing(event);
                            if (newDist > 10f * density) {
                                matrix.set(savedMatrix);
                                float scale = (newDist / oldDist);
                                float[] values = new float[9];
                                matrix.getValues(values);
                                if (scale * values[Matrix.MSCALE_X] >= MAX_ZOOM) {
                                    scale = MAX_ZOOM / values[Matrix.MSCALE_X];
                                }
                                if (scale * values[Matrix.MSCALE_X] <= MIN_ZOOM) {
                                    scale = MIN_ZOOM / values[Matrix.MSCALE_X];
                                }
                                matrix.postScale(scale, scale, mid.x, mid.y);
                                matrix.invert(matrixInverse);
                            }
                        } else {
                            if ( SystemClock.elapsedRealtime() - downTime > 250l) {
                                doubleTap = false;
                            }
                            matrix.set(savedMatrix);
                            float scale = event.getY() / start.y;
                            float[] values = new float[9];
                            matrix.getValues(values);
                            if (scale * values[Matrix.MSCALE_X] >= MAX_ZOOM) {
                                scale = MAX_ZOOM / values[Matrix.MSCALE_X];
                            }
                            if (scale * values[Matrix.MSCALE_X] <= MIN_ZOOM) {
                                scale = MIN_ZOOM / values[Matrix.MSCALE_X];
                            }
                            matrix.postScale(scale, scale, mid.x, mid.y);
                            matrix.invert(matrixInverse);
                        }
                    }
                    break;
            }
    
    
            invalidate();
            return true;
        }
    
    }
    

提交回复
热议问题