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

前端 未结 9 2025
离开以前
离开以前 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:27

    For those who are interested in a zooming/panning LinearLayout, I modified the version posted by Alex to lay things out vertically and cap the panning to the visible views. I use this for bitmaps from the PDFRenderer. I've tested this but if you notice any bugs please post because I'd like to know about them, too!

    Note: I opted not to implement double tapping, since QuickScale works.

    public class ZoomableLinearLayout extends ViewGroup {
    
       private static final int INVALID_POINTER_ID = 1;
       private int mActivePointerId = INVALID_POINTER_ID;
    
       private float mScaleFactor = 1;
       private ScaleGestureDetector mScaleDetector;
       private Matrix mScaleMatrix = new Matrix();
       private Matrix mScaleMatrixInverse = new Matrix();
    
       private float mPosX;
       private float mPosY;
       private Matrix mTranslateMatrix = new Matrix();
       private Matrix mTranslateMatrixInverse = new Matrix();
    
       private float mLastTouchX;
       private float mLastTouchY;
    
       private float mFocusY;
       private float mFocusX;
    
       private int mCanvasWidth;
       private int mCanvasHeight;
    
       private float[] mInvalidateWorkingArray = new float[6];
       private float[] mDispatchTouchEventWorkingArray = new float[2];
       private float[] mOnTouchEventWorkingArray = new float[2];
    
       private boolean mIsScaling;
    
       public ZoomableLinearLayout(Context context) {
          super(context);
          mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
          mTranslateMatrix.setTranslate(0, 0);
          mScaleMatrix.setScale(1, 1);
       }
    
       public ZoomableLinearLayout(Context context, AttributeSet attributeSet) {
          super(context, attributeSet);
          mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
          mTranslateMatrix.setTranslate(0, 0);
          mScaleMatrix.setScale(1, 1);
       }
    
       @Override
       protected void onLayout(boolean changed, int l, int t, int r, int b) {
          int childCount = getChildCount();
          for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
             if (child.getVisibility() != GONE) {
                child.layout(l, t, l+child.getMeasuredWidth(), t += child.getMeasuredHeight());
             }
          }
       }
    
       @Override
       protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
          int height = 0;
          int width = 0;
          int childCount = getChildCount();
          for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
             if (child.getVisibility() != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
                height += child.getMeasuredHeight();
                width = Math.max(width, child.getMeasuredWidth());
             }
          }
          mCanvasWidth = width;
          mCanvasHeight = height;
       }
    
       @Override
       protected void dispatchDraw(Canvas canvas) {
          canvas.save();
          canvas.translate(mPosX, mPosY);
          canvas.scale(mScaleFactor, mScaleFactor, mFocusX, mFocusY);
          super.dispatchDraw(canvas);
          canvas.restore();
       }
    
       @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);
       }
    
       /**
        * Although the docs say that you shouldn't override this, I decided to do
        * so because it offers me an easy way to change the invalidated area to my
        * likening.
        */
       @Override
       public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    
          mInvalidateWorkingArray[0] = dirty.left;
          mInvalidateWorkingArray[1] = dirty.top;
          mInvalidateWorkingArray[2] = dirty.right;
          mInvalidateWorkingArray[3] = dirty.bottom;
    
          mInvalidateWorkingArray = scaledPointsToScreenPoints(mInvalidateWorkingArray);
          dirty.set(Math.round(mInvalidateWorkingArray[0]), Math.round(mInvalidateWorkingArray[1]),
                Math.round(mInvalidateWorkingArray[2]), Math.round(mInvalidateWorkingArray[3]));
    
          location[0] *= mScaleFactor;
          location[1] *= mScaleFactor;
          return super.invalidateChildInParent(location, dirty);
       }
    
       private float[] scaledPointsToScreenPoints(float[] a) {
          mScaleMatrix.mapPoints(a);
          mTranslateMatrix.mapPoints(a);
          return a;
       }
    
       private float[] screenPointsToScaledPoints(float[] a){
          mTranslateMatrixInverse.mapPoints(a);
          mScaleMatrixInverse.mapPoints(a);
          return a;
       }
    
       @Override
       public boolean onTouchEvent(MotionEvent ev) {
          mOnTouchEventWorkingArray[0] = ev.getX();
          mOnTouchEventWorkingArray[1] = ev.getY();
    
          mOnTouchEventWorkingArray = scaledPointsToScreenPoints(mOnTouchEventWorkingArray);
    
          ev.setLocation(mOnTouchEventWorkingArray[0], mOnTouchEventWorkingArray[1]);
          mScaleDetector.onTouchEvent(ev);
    
          final int action = ev.getAction();
          switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN: {
                final float x = ev.getX();
                final float y = ev.getY();
    
                mLastTouchX = x;
                mLastTouchY = y;
    
                // Save the ID of this pointer
                mActivePointerId = ev.getPointerId(0);
                break;
             }
    
             case MotionEvent.ACTION_MOVE: {
                // Find the index of the active pointer and fetch its position
                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
                final float x = ev.getX(pointerIndex);
                final float y = ev.getY(pointerIndex);
    
                if (mIsScaling && ev.getPointerCount() == 1) {
                   // Don't move during a QuickScale.
                   mLastTouchX = x;
                   mLastTouchY = y;
    
                   break;
                }
    
                float dx = x - mLastTouchX;
                float dy = y - mLastTouchY;
    
                float[] topLeft = {0f, 0f};
                float[] bottomRight = {getWidth(), getHeight()};
                /*
                 * Corners of the view in screen coordinates, so dx/dy should not be allowed to
                 * push these beyond the canvas bounds.
                 */
                float[] scaledTopLeft = screenPointsToScaledPoints(topLeft);
                float[] scaledBottomRight = screenPointsToScaledPoints(bottomRight);
    
                dx = Math.min(Math.max(dx, scaledBottomRight[0] - mCanvasWidth), scaledTopLeft[0]);
                dy = Math.min(Math.max(dy, scaledBottomRight[1] - mCanvasHeight), scaledTopLeft[1]);
    
                mPosX += dx;
                mPosY += dy;
    
                mTranslateMatrix.preTranslate(dx, dy);
                mTranslateMatrix.invert(mTranslateMatrixInverse);
    
                mLastTouchX = x;
                mLastTouchY = y;
    
                invalidate();
                break;
             }
    
             case MotionEvent.ACTION_UP: {
                mActivePointerId = INVALID_POINTER_ID;
                break;
             }
    
             case MotionEvent.ACTION_CANCEL: {
                mActivePointerId = INVALID_POINTER_ID;
                break;
             }
    
             case MotionEvent.ACTION_POINTER_UP: {
                // Extract the index of the pointer that left the touch sensor
                final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                final int pointerId = ev.getPointerId(pointerIndex);
                if (pointerId == mActivePointerId) {
                   // This was our active pointer going up. Choose a new
                   // active pointer and adjust accordingly.
                   final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                   mLastTouchX = ev.getX(newPointerIndex);
                   mLastTouchY = ev.getY(newPointerIndex);
                   mActivePointerId = ev.getPointerId(newPointerIndex);
                }
                break;
             }
          }
          return true;
       }
    
       private float getMaxScale() {
          return 2f;
       }
    
       private float getMinScale() {
          return 1f;
       }
    
       private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
          @Override
          public boolean onScaleBegin(ScaleGestureDetector detector) {
             mIsScaling = true;
    
             mFocusX = detector.getFocusX();
             mFocusY = detector.getFocusY();
    
             float[] foci = {mFocusX, mFocusY};
             float[] scaledFoci = screenPointsToScaledPoints(foci);
    
             mFocusX = scaledFoci[0];
             mFocusY = scaledFoci[1];
    
             return true;
          }
    
          @Override
          public void onScaleEnd(ScaleGestureDetector detector) {
             mIsScaling = false;
          }
    
          @Override
          public boolean onScale(ScaleGestureDetector detector) {
             mScaleFactor *= detector.getScaleFactor();
             mScaleFactor = Math.max(getMinScale(), Math.min(mScaleFactor, getMaxScale()));
             mScaleMatrix.setScale(mScaleFactor, mScaleFactor, mFocusX, mFocusY);
             mScaleMatrix.invert(mScaleMatrixInverse);
             invalidate();
    
             return true;
          }
       }
    
    }
    

提交回复
热议问题