/** * Measures the width and height of the view, preserving the aspect ratio of the image displayed if wrap_content is * used. The image will scale within this box, not resizing the view as it is zoomed. */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int parentWidth = MeasureSpec.getSize(widthMeasureSpec); int parentHeight = MeasureSpec.getSize(heightMeasureSpec); boolean resizeWidth = widthSpecMode != MeasureSpec.EXACTLY; boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; int width = parentWidth; int height = parentHeight; if (sWidth > 0 && sHeight > 0) { if (resizeWidth && resizeHeight) { width = sWidth(); height = sHeight(); } else if (resizeHeight) { height = (int)((((double)sHeight()/(double)sWidth()) * width)); } else if (resizeWidth) { width = (int)((((double)sWidth()/(double)sHeight()) * height)); } } width = Math.max(width, getSuggestedMinimumWidth()); height = Math.max(height, getSuggestedMinimumHeight()); setMeasuredDimension(width, height); }
/** * Adjusts current scale and translate values to keep scale within the allowed range and the image on screen. Minimum scale * is set so one dimension fills the view and the image is centered on the other dimension. * @param center Whether the image should be centered in the dimension it's too small to fill. While animating this can be false to avoid changes in direction as bounds are reached. */ private void fitToBounds(boolean center) { boolean init = false; if (vTranslate == null) { init = true; vTranslate = new PointF(0, 0); } if (satTemp == null) { satTemp = new ScaleAndTranslate(0, new PointF(0, 0)); } satTemp.scale = scale; satTemp.vTranslate.set(vTranslate); fitToBounds(center, satTemp); scale = satTemp.scale; vTranslate.set(satTemp.vTranslate); if (init && minimumScaleType != SCALE_TYPE_START) { vTranslate.set(vTranslateForSCenter(sWidth()/2, sHeight()/2, scale)); } }
/** * Fully zoom out and return the image to the middle of the screen. This might be useful if you have a view pager * and want images to be reset when the user has moved to another page. */ public final void resetScaleAndCenter() { this.anim = null; this.pendingScale = limitedScale(0); if (isReady()) { this.sPendingCenter = new PointF(sWidth()/2, sHeight()/2); } else { this.sPendingCenter = new PointF(0, 0); } invalidate(); }
int yTiles = 1; while (true) { int sTileWidth = sWidth()/xTiles; int sTileHeight = sHeight()/yTiles; int subTileWidth = sTileWidth/sampleSize; while (subTileWidth + xTiles + 1 > maxTileDimensions.x || (subTileWidth > getWidth() * 1.25 && sampleSize < fullImageSampleSize)) { xTiles += 1; sTileWidth = sWidth()/xTiles; subTileWidth = sTileWidth/sampleSize; x * sTileWidth, y * sTileHeight, x == xTiles - 1 ? sWidth() : (x + 1) * sTileWidth, y == yTiles - 1 ? sHeight() : (y + 1) * sTileHeight );
/** * Calculate how much further the image can be panned in each direction. The results are set on * the supplied {@link RectF} and expressed as screen pixels. For example, if the image cannot be * panned any further towards the left, the value of {@link RectF#left} will be set to 0. * @param vTarget target object for results. Re-use for efficiency. */ public final void getPanRemaining(RectF vTarget) { if (!isReady()) { return; } float scaleWidth = scale * sWidth(); float scaleHeight = scale * sHeight(); if (panLimit == PAN_LIMIT_CENTER) { vTarget.top = Math.max(0, -(vTranslate.y - (getHeight() / 2))); vTarget.left = Math.max(0, -(vTranslate.x - (getWidth() / 2))); vTarget.bottom = Math.max(0, vTranslate.y - ((getHeight() / 2) - scaleHeight)); vTarget.right = Math.max(0, vTranslate.x - ((getWidth() / 2) - scaleWidth)); } else if (panLimit == PAN_LIMIT_OUTSIDE) { vTarget.top = Math.max(0, -(vTranslate.y - getHeight())); vTarget.left = Math.max(0, -(vTranslate.x - getWidth())); vTarget.bottom = Math.max(0, vTranslate.y + scaleHeight); vTarget.right = Math.max(0, vTranslate.x + scaleWidth); } else { vTarget.top = Math.max(0, -vTranslate.y); vTarget.left = Math.max(0, -vTranslate.x); vTarget.bottom = Math.max(0, (scaleHeight + vTranslate.y) - getHeight()); vTarget.right = Math.max(0, (scaleWidth + vTranslate.x) - getWidth()); } }
/** * Returns the minimum allowed scale. */ private float minScale() { int vPadding = getPaddingBottom() + getPaddingTop(); int hPadding = getPaddingLeft() + getPaddingRight(); if (minimumScaleType == SCALE_TYPE_CENTER_CROP || minimumScaleType == SCALE_TYPE_START) { return Math.max((getWidth() - hPadding) / (float) sWidth(), (getHeight() - vPadding) / (float) sHeight()); } else if (minimumScaleType == SCALE_TYPE_CUSTOM && minScale > 0) { return minScale; } else { return Math.min((getWidth() - hPadding) / (float) sWidth(), (getHeight() - vPadding) / (float) sHeight()); } }
vTranslate.x = vCenterEndX - vLeftNow; vTranslate.y = vCenterEndY - vTopNow; if ((previousScale * sHeight() < getHeight() && scale * sHeight() >= getHeight()) || (previousScale * sWidth() < getWidth() && scale * sWidth() >= getWidth())) { fitToBounds(true); vCenterStart.set(vCenterEndX, vCenterEndY); } else { vTranslate.x = (getWidth()/2) - (scale * (sWidth()/2)); vTranslate.y = (getHeight()/2) - (scale * (sHeight()/2)); vTranslate.x = vCenterStart.x - vLeftNow; vTranslate.y = vCenterStart.y - vTopNow; if ((previousScale * sHeight() < getHeight() && scale * sHeight() >= getHeight()) || (previousScale * sWidth() < getWidth() && scale * sWidth() >= getWidth())) { fitToBounds(true); vCenterStart.set(sourceToViewCoord(quickScaleSCenter)); } else { vTranslate.x = (getWidth()/2) - (scale * (sWidth()/2)); vTranslate.y = (getHeight()/2) - (scale * (sHeight()/2));
/** * Enable or disable pan gesture detection. Disabling pan causes the image to be centered. Pan * can still be changed from code. * @param panEnabled true to enable panning, false to disable. */ public final void setPanEnabled(boolean panEnabled) { this.panEnabled = panEnabled; if (!panEnabled && vTranslate != null) { vTranslate.x = (getWidth()/2) - (scale * (sWidth()/2)); vTranslate.y = (getHeight()/2) - (scale * (sHeight()/2)); if (isReady()) { refreshRequiredTiles(true); invalidate(); } } }
float scaleWidth = scale * sWidth(); float scaleHeight = scale * sHeight();
if (fullImageSampleSize == 1 && sRegion == null && sWidth() < maxTileDimensions.x && sHeight() < maxTileDimensions.y) {
/** * Double tap zoom handler triggered from gesture detector or on touch, depending on whether * quick scale is enabled. */ private void doubleTapZoom(PointF sCenter, PointF vFocus) { if (!panEnabled) { if (sRequestedCenter != null) { // With a center specified from code, zoom around that point. sCenter.x = sRequestedCenter.x; sCenter.y = sRequestedCenter.y; } else { // With no requested center, scale around the image center. sCenter.x = sWidth()/2; sCenter.y = sHeight()/2; } } float doubleTapZoomScale = Math.min(maxScale, SubsamplingScaleImageView.this.doubleTapZoomScale); boolean zoomIn = (scale <= doubleTapZoomScale * 0.9) || scale == minScale; float targetScale = zoomIn ? doubleTapZoomScale : minScale(); if (doubleTapZoomStyle == ZOOM_FOCUS_CENTER_IMMEDIATE) { setScaleAndCenter(targetScale, sCenter); } else if (doubleTapZoomStyle == ZOOM_FOCUS_CENTER || !zoomIn || !panEnabled) { new AnimationBuilder(targetScale, sCenter).withInterruptible(false).withDuration(doubleTapZoomDuration).withOrigin(ORIGIN_DOUBLE_TAP_ZOOM).start(); } else if (doubleTapZoomStyle == ZOOM_FOCUS_FIXED) { new AnimationBuilder(targetScale, sCenter, vFocus).withInterruptible(false).withDuration(doubleTapZoomDuration).withOrigin(ORIGIN_DOUBLE_TAP_ZOOM).start(); } invalidate(); }
/** * Measures the width and height of the view, preserving the aspect ratio of the image displayed if wrap_content is * used. The image will scale within this box, not resizing the view as it is zoomed. */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int parentWidth = MeasureSpec.getSize(widthMeasureSpec); int parentHeight = MeasureSpec.getSize(heightMeasureSpec); boolean resizeWidth = widthSpecMode != MeasureSpec.EXACTLY; boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; int width = parentWidth; int height = parentHeight; if (sWidth > 0 && sHeight > 0) { if (resizeWidth && resizeHeight) { width = sWidth(); height = sHeight(); } else if (resizeHeight) { height = (int)((((double)sHeight()/(double)sWidth()) * width)); } else if (resizeWidth) { width = (int)((((double)sWidth()/(double)sHeight()) * height)); } } width = Math.max(width, getSuggestedMinimumWidth()); height = Math.max(height, getSuggestedMinimumHeight()); setMeasuredDimension(width, height); }
/** * Adjusts current scale and translate values to keep scale within the allowed range and the image on screen. Minimum scale * is set so one dimension fills the view and the image is centered on the other dimension. * @param center Whether the image should be centered in the dimension it's too small to fill. While animating this can be false to avoid changes in direction as bounds are reached. */ private void fitToBounds(boolean center) { boolean init = false; if (vTranslate == null) { init = true; vTranslate = new PointF(0, 0); } if (satTemp == null) { satTemp = new ScaleAndTranslate(0, new PointF(0, 0)); } satTemp.scale = scale; satTemp.vTranslate.set(vTranslate); fitToBounds(center, satTemp); scale = satTemp.scale; vTranslate.set(satTemp.vTranslate); if (init) { vTranslate.set(vTranslateForSCenter(sWidth()/2, sHeight()/2, scale)); } }
/** * Fully zoom out and return the image to the middle of the screen. This might be useful if you have a view pager * and want images to be reset when the user has moved to another page. */ public final void resetScaleAndCenter() { this.anim = null; this.pendingScale = limitedScale(0); if (isReady()) { this.sPendingCenter = new PointF(sWidth()/2, sHeight()/2); } else { this.sPendingCenter = new PointF(0, 0); } invalidate(); }
/** * Returns the minimum allowed scale. */ private float minScale() { int vPadding = getPaddingBottom() + getPaddingTop(); int hPadding = getPaddingLeft() + getPaddingRight(); if (minimumScaleType == SCALE_TYPE_CENTER_CROP) { return Math.max((getWidth() - hPadding) / (float) sWidth(), (getHeight() - vPadding) / (float) sHeight()); } else if (minimumScaleType == SCALE_TYPE_CUSTOM && minScale > 0) { return minScale; } else { return Math.min((getWidth() - hPadding) / (float) sWidth(), (getHeight() - vPadding) / (float) sHeight()); } }
/** * Enable or disable pan gesture detection. Disabling pan causes the image to be centered. */ public final void setPanEnabled(boolean panEnabled) { this.panEnabled = panEnabled; if (!panEnabled && vTranslate != null) { vTranslate.x = (getWidth()/2) - (scale * (sWidth()/2)); vTranslate.y = (getHeight()/2) - (scale * (sHeight()/2)); if (isReady()) { refreshRequiredTiles(true); invalidate(); } } }
if (fullImageSampleSize == 1 && sRegion == null && sWidth() < maxTileDimensions.x && sHeight() < maxTileDimensions.y) {
/** * Double tap zoom handler triggered from gesture detector or on touch, depending on whether * quick scale is enabled. */ private void doubleTapZoom(PointF sCenter, PointF vFocus) { if (!panEnabled) { if (sRequestedCenter != null) { // With a center specified from code, zoom around that point. sCenter.x = sRequestedCenter.x; sCenter.y = sRequestedCenter.y; } else { // With no requested center, scale around the image center. sCenter.x = sWidth()/2; sCenter.y = sHeight()/2; } } float doubleTapZoomScale = Math.min(maxScale, SubsamplingScaleImageView.this.doubleTapZoomScale); boolean zoomIn = scale <= doubleTapZoomScale * 0.9; float targetScale = zoomIn ? doubleTapZoomScale : minScale(); if (doubleTapZoomStyle == ZOOM_FOCUS_CENTER_IMMEDIATE) { setScaleAndCenter(targetScale, sCenter); } else if (doubleTapZoomStyle == ZOOM_FOCUS_CENTER || !zoomIn || !panEnabled) { new AnimationBuilder(targetScale, sCenter).withInterruptible(false).withDuration(doubleTapZoomDuration).withOrigin(ORIGIN_DOUBLE_TAP_ZOOM).start(); } else if (doubleTapZoomStyle == ZOOM_FOCUS_FIXED) { new AnimationBuilder(targetScale, sCenter, vFocus).withInterruptible(false).withDuration(doubleTapZoomDuration).withOrigin(ORIGIN_DOUBLE_TAP_ZOOM).start(); } invalidate(); }