title: Android 4.3 ImageView with ScaleType: MATRIX
body: <p>My question is most similar to:</p>
  
  <p><a href="https://stackoverflow.com/questions/17883719/android-4-3-imageview-scaletype-matrix">Android 4.3 ImageView ScaleType.MATRIX</a></p>
  
  <p>but also echos:</p>
  
  <p><a href="https://stackoverflow.com/questions/9826853/android-zoom-image-using-matrix">android zoom image using matrix</a></p>
  
  <p>However, using the solutions to those answers has solved my problem.</p>
  
  <p>I am using a modified version of Mike Ortiz's TouchImageView to allow for both double tapping and the regular scale gesture to zoom my ImageView. For Android 4.2.2 and lower, commenting out the "setScaleType(ScaleType.MATRIX)" was just fine, the image was centered in the ImageView, and you could zoom in and out at will. Uncommenting, though, would cause the image to be sized incorrectly for the ImageView. All this was just fine, as I just left that line commented, and everything worked.</p>
  
  <p>As of Android 4.3, however, you can't zoom without setScaleType(ScaleType.MATRIX). So I've had to uncomment that line, but the old problem I never solved is back.</p>
  
  <p>So, first and foremost, why do my calculations fail to result in the correct image/ImageView ratio when all I'm doing is setting the ScaleType? Second, why would it zoom just fine on 4.2.2 without setting the scale type, but on 4.3, it won't? And finally, could someone give me some resources to learn more about what ScaleType.MATRIX really does? I've read through some of the sources, and done some googling, but don't totally grasp it.</p>
  
  <p>Thank you in advance, and code below.</p>
  
  <pre><code>public class TouchImageView extends ImageView {
  
  Matrix matrix = new Matrix();
  
  // We can be in one of these 3 states
  static final int NONE = 0;
  static final int DRAG = 1;
  static final int ZOOM = 2;
  int mode = NONE;
  
  // Remember some things for zooming
  PointF last = new PointF();
  PointF start = new PointF();
  float minScale = 1f;
  float maxScale = 10f;
  float[] floatArray;
  
  float unusedWidth, unusedHeight;
  
  float imageViewWidth, imageViewHeight;
  float initialScale = 1f;
  float right, bottom, origWidth, origHeight, imageWidth, imageHeight;
  
  boolean zoomedInLastTime = false;
  PointF zoomCenter;
  
  ScaleGestureDetector mScaleDetector;
  GestureDetector mDetector;
  
  Context context;
  
  public TouchImageView(Context context) {
      super(context);
      sharedConstructing(context);
  }
  
  public TouchImageView(Context context, AttributeSet attrs) {
      super(context, attrs);
      sharedConstructing(context);
  }
  
  private void sharedConstructing(Context context) {
      super.setClickable(true);
      this.context = context;
      setScaleType(ScaleType.MATRIX);
      //matrix = this.getImageMatrix();
      mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
  
      //this is an empty GestureDetector
      mDetector = new GestureDetector(this.context, new GestureDetector.OnGestureListener() {
          @Override
          public boolean onDown(MotionEvent motionEvent) {
              return false;
          }
  
          @Override
          public void onShowPress(MotionEvent motionEvent) {
  
          }
  
          @Override
          public boolean onSingleTapUp(MotionEvent motionEvent) {
              return false;
          }
  
          @Override
          public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent2, float v, float v2) {
              return false;
          }
  
          @Override
          public void onLongPress(MotionEvent motionEvent) {
  
          }
  
          @Override
          public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent2, float v, float v2) {
              return false;
          }
      }, null, true);
      mDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener(){
  
          @Override
          public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
              return false;
          }
  
          @Override
          public boolean onDoubleTap(MotionEvent motionEvent) {
              Log.d("TouchImageView", "double tap heard");
              PointF currentTapLocation = new PointF(motionEvent.getX(), motionEvent.getY());
              if (!zoomedInLastTime){
                  (new ScaleListener()).scaleIt(3f,currentTapLocation.x,currentTapLocation.y);
                  zoomCenter = currentTapLocation;
                  zoomedInLastTime = true;
              }else {
                  (new ScaleListener()).scaleIt(.33f, zoomCenter.x, zoomCenter.y);
                  zoomedInLastTime = false;
              }
              return true;
          }
  
          @Override
          public boolean onDoubleTapEvent(MotionEvent motionEvent) {
              return false;
          }
      });
      matrix.setTranslate(1f, 1f);
      floatArray = new float[9];
      setImageMatrix(matrix);
  
      setOnTouchListener(new DoubleTapPinchZoomListener());
  }
  
  @Override
  public void setImageBitmap(Bitmap bm) { 
      super.setImageBitmap(bm);
      if(bm != null) {
          if (Build.VERSION.SDK_INT &gt; 17){
              imageWidth = bm.getWidth();
              imageHeight = bm.getHeight();
          } else {
              imageWidth = 2*bm.getWidth();
              imageHeight = 2*bm.getHeight();
          }
      }
  }
  
  public void setMaxZoom(float x)
  {
      maxScale = x;
  }
  
  private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
      @Override
      public boolean onScaleBegin(ScaleGestureDetector detector) {
          mode = ZOOM;
          return true;
      }
  
      @Override
      public boolean onScale(ScaleGestureDetector detector) {
          float mScaleFactor = detector.getScaleFactor();
          scaleIt(mScaleFactor, detector.getFocusX(), detector.getFocusY());
          return true;
      }
  
      public void scaleIt(float mScaleFactor, float focusx, float focusy){
          float origScale = initialScale;
          initialScale *= mScaleFactor;
          if (initialScale &gt; maxScale) {
              initialScale = maxScale;
              mScaleFactor = maxScale / origScale;
          } else if (initialScale &lt; minScale) {
              initialScale = minScale;
              mScaleFactor = minScale / origScale;
          }
          right = imageViewWidth * initialScale - imageViewWidth - (2 * unusedWidth * initialScale);
          bottom = imageViewHeight * initialScale - imageViewHeight - (2 * unusedHeight * initialScale);
          if (origWidth * initialScale &lt;= imageViewWidth || origHeight * initialScale &lt;= imageViewHeight) {
              matrix.postScale(mScaleFactor, mScaleFactor, imageViewWidth / 2, imageViewHeight / 2);
              if (mScaleFactor &lt; 1) {
                  matrix.getValues(floatArray);
                  float x = floatArray[Matrix.MTRANS_X];
                  float y = floatArray[Matrix.MTRANS_Y];
                  if (mScaleFactor &lt; 1) {
                      if (Math.round(origWidth * initialScale) &lt; imageViewWidth) {
                          if (y &lt; -bottom)
                              matrix.postTranslate(0, -(y + bottom));
                          else if (y &gt; 0)
                              matrix.postTranslate(0, -y);
                      } else {
                          if (x &lt; -right) 
                              matrix.postTranslate(-(x + right), 0);
                          else if (x &gt; 0) 
                              matrix.postTranslate(-x, 0);
                      }
                  }
              }
          } else {
              matrix.postScale(mScaleFactor, mScaleFactor, focusx, focusy);
              matrix.getValues(floatArray);
              float x = floatArray[Matrix.MTRANS_X];
              float y = floatArray[Matrix.MTRANS_Y];
              if (mScaleFactor &lt; 1) {
                  if (x &lt; -right) 
                      matrix.postTranslate(-(x + right), 0);
                  else if (x &gt; 0) 
                      matrix.postTranslate(-x, 0);
                  if (y &lt; -bottom)
                      matrix.postTranslate(0, -(y + bottom));
                  else if (y &gt; 0)
                      matrix.postTranslate(0, -y);
              }
          }
      }
  }
  
  @Override
  protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec){
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  
      imageViewWidth = MeasureSpec.getSize(widthMeasureSpec);
      imageViewHeight = MeasureSpec.getSize(heightMeasureSpec);
  
      /*RectF drawableRect = new RectF(0, 0, imageWidth, imageHeight);
      RectF viewRect = new RectF(0, 0, imageViewWidth, imageViewHeight);
  
      //draw the image in the view
      matrix.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.CENTER);*/
  
      //Fit to screen.
      float scale;
      float scaleX =  (float) imageViewWidth / (float)(imageWidth);
      float scaleY = (float) imageViewHeight / (float)(imageHeight);
      scale = Math.min(scaleX, scaleY);
      matrix.setScale(scale, scale);
      setImageMatrix(matrix);
      initialScale = 1f;
  
      // Center the image
      unusedHeight = (float) imageViewHeight - (scale * (float) imageHeight) ;
      unusedWidth = (float) imageViewWidth - (scale * (float) imageWidth);
      unusedHeight /= (float)2;
      unusedWidth /= (float)2;
  
      matrix.postTranslate(unusedWidth, unusedHeight);
  
      origWidth = imageViewWidth - 2 * unusedWidth;
      origHeight = imageViewHeight - 2 * unusedHeight;
      right = imageViewWidth * initialScale - imageViewWidth - (2 * unusedWidth * initialScale);
      bottom = imageViewHeight * initialScale - imageViewHeight - (2 * unusedHeight * initialScale);
      setImageMatrix(matrix);
  }
  
  class DoubleTapPinchZoomListener implements OnTouchListener {
  
      @Override
      public boolean onTouch(View v, MotionEvent event) {
          mScaleDetector.onTouchEvent(event);
  
          mDetector.onTouchEvent(event);
  
          matrix.getValues(floatArray);
          float x = floatArray[Matrix.MTRANS_X];
          float y = floatArray[Matrix.MTRANS_Y];
          PointF curr = new PointF(event.getX(), event.getY());
  
          switch (event.getAction()) {
              case MotionEvent.ACTION_DOWN:
                  last.set(event.getX(), event.getY());
                  start.set(last);
                  mode = DRAG;
                  break;
              case MotionEvent.ACTION_MOVE:
                  if (mode == DRAG) {
                      float deltaX = curr.x - last.x;
                      float deltaY = curr.y - last.y;
                      float scaleWidth = Math.round(origWidth * initialScale);
                      float scaleHeight = Math.round(origHeight * initialScale);
                      if (scaleWidth &lt; imageViewWidth) {
                          deltaX = 0;
                          if (y + deltaY &gt; 0)
                              deltaY = -y;
                          else if (y + deltaY &lt; -bottom)
                              deltaY = -(y + bottom); 
                      } else if (scaleHeight &lt; imageViewHeight) {
                          deltaY = 0;
                          if (x + deltaX &gt; 0)
                              deltaX = -x;
                          else if (x + deltaX &lt; -right)
                              deltaX = -(x + right);
                      } else {
                          if (x + deltaX &gt; 0)
                              deltaX = -x;
                          else if (x + deltaX &lt; -right)
                              deltaX = -(x + right);
  
                          if (y + deltaY &gt; 0)
                              deltaY = -y;
                          else if (y + deltaY &lt; -bottom)
                              deltaY = -(y + bottom);
                      }
                      matrix.postTranslate(deltaX, deltaY);
                      last.set(curr.x, curr.y);
                  }
                  break;
  
              case MotionEvent.ACTION_POINTER_UP:
                  mode = NONE;
                  break;
          }
          setImageMatrix(matrix);
          invalidate();
          return true;
      }
  }
  }
  </code></pre>