Preface: At present, it is difficult to find a picture control on the Internet that can support the rotation and scaling of large pictures. Found only supports panning. There is no way but to write one yourself.
Implementation principle:
1. Incoming image path
2. Calculate the actual width and height of the picture, compare the width and height of the control, calculate the initial CenterMatrix, and let the picture center and fill the view adaptively
3. Set pan, zoom, and rotate gesture monitoring, and use gestureMatrix to record the transformation
4. When onDraw, calculate the mBitmapRect of the image display area based on CenterMatrix + gestureMatrix
5.bitmap = mDecoder.decodeRegion(mBitmapRect, mDecodeOptions); Only read the image area that needs to be displayed
6. Determine whether the size of the bitmap exceeds the specified size, OOM will occur if it exceeds the size, so when the size is exceeded, you need to do image compression
7. After compression, the processing of Matrix needs to be increased to become ScaleMatrix + CenterMatrix + gestureMatrix
8. The final result is that when the full image is reduced, the Bitmap needs to be compressed to save memory, and when the image is enlarged, only part of the image is loaded to save memory
package com.ange.bigimageview; import static com.ange.bigimageview.MathUtils.rectFTake; import android. content. Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import androidx.annotation.Nullable; import com.almeros.android.multitouch.RotateGestureDetector; import java.io.IOException; import java.io.InputStream; public class BigImageView extends View { private final static String TAG="BigImageView"; private float mBigImgWidth; private float mBigImgHeight; private BitmapRegionDecoder mDecoder; private static final BitmapFactory.Options mDecodeOptions = new BitmapFactory.Options(); private final Rect mBitmapRect = new Rect();//The position of the picture displayed in the View private Matrix centerMatrix ;//The picture is centered at the beginning private final Matrix currentMatrix=new Matrix();//Summary of all matrix transformations currently accumulated private final Matrix gestureMatrix=new Matrix();//Matrix transformation generated by gesture private final Matrix imgScale=new Matrix();//image occupation /** * Maximum image size */ private static final int BITMAP_MAX_SIZE =3000; static { mDecodeOptions.inPreferredConfig = Bitmap.Config.RGB_565; } private GestureDetector mMoveDetector; private RotateGestureDetector mRotateGestureDetector; private ScaleGestureDetector mScaleGestureDetector; public BigImageView(Context context) { super(context); init(context); } public BigImageView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context); } public BigImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } public BigImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context); } private void init(Context context){ mMoveDetector = new GestureDetector(context, new GestureDetector. SimpleOnGestureListener(){ @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { gestureMatrix.postTranslate(-distanceX,-distanceY); invalidate(); return true; } }); mRotateGestureDetector = new RotateGestureDetector(context, new RotateGestureDetector.SimpleOnRotateGestureListener(){ @Override public boolean onRotate(RotateGestureDetector detector) { gestureMatrix.postRotate(-detector.getRotationDegreesDelta(),getWidth()/2,getHeight()/2); Log.i(TAG,"onRotate"); invalidate(); return true; } }); mScaleGestureDetector=new ScaleGestureDetector(context,new ScaleGestureDetector.SimpleOnScaleGestureListener(){ @Override public boolean onScale(ScaleGestureDetector detector) { float scaleFactor = detector. getScaleFactor(); gestureMatrix.postScale(scaleFactor,scaleFactor,detector.getFocusX(),detector.getFocusY()); return true; } }); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if(centerMatrix==null){ centerMatrix = getCenterMatrix(); } if(mDecoder!=null){ Matrix matrix = getCurrentImageMatrix(); RectF imgShowRect = rectFTake(); imgShowRect.top=0; imgShowRect.bottom=getHeight(); imgShowRect.left=0; imgShowRect.right=getWidth(); Log.i(TAG,String.format("view,width=%1$s,height=%2$s",getWidth(),getHeight())); Log.i(TAG,imgShowRect.toString()); MathUtils.calculateImageRegion(imgShowRect, getWidth(), getHeight(), mBigImgWidth, mBigImgHeight, matrix, mBitmapRect); Log.i(TAG,mBitmapRect.toString()); if(isOutRect(mBitmapRect,mBigImgWidth,mBigImgHeight)){ Log.i(TAG,"Out of display range"); return; } // When the full image is displayed, it is judged that the size is too large and compressed int rectW=mBitmapRect.right-mBitmapRect.left; int rectH=mBitmapRect.bottom-mBitmapRect.top; int inSampleSize = 1; while (rectW*rectH/inSampleSize>BITMAP_MAX_SIZE*BITMAP_MAX_SIZE){ inSampleSize*=2; } Log.i(TAG,"inSampleSize=" + inSampleSize); mDecodeOptions.inSampleSize=inSampleSize; Bitmap bitmap = mDecoder.decodeRegion(mBitmapRect, mDecodeOptions); Log.i(TAG,"bitmap.size=" + bitmap.getByteCount()/1024/1024); if(inSampleSize>1){ imgScale. reset(); imgScale. postScale(inSampleSize, inSampleSize); imgScale. postConcat(matrix); matrix=imgScale; MathUtils.calculateImageRegion(imgShowRect, getWidth(), getHeight(), mBigImgWidth, mBigImgHeight, matrix, mBitmapRect); Log.i(TAG,mBitmapRect.toString()); if(isOutRect(mBitmapRect,mBigImgWidth,mBigImgHeight)){ Log.i(TAG,"Out of display range"); return; } } canvas.concat(matrix); canvas. save(); canvas.drawBitmap(bitmap, mBitmapRect.left, mBitmapRect.top, null); MathUtils.rectFGiven(imgShowRect); } } @Override public boolean onTouchEvent(MotionEvent event) { mRotateGestureDetector.onTouchEvent(event); mMoveDetector.onTouchEvent(event); mScaleGestureDetector.onTouchEvent(event); return true; } private Matrix getCurrentImageMatrix() { currentMatrix. reset(); currentMatrix. postConcat(centerMatrix); currentMatrix. postConcat(gestureMatrix); return currentMatrix; } public void setBigImage(InputStream inputStream) throws IOException { BitmapFactory. Options options = new BitmapFactory. Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeStream(inputStream, null, options); mBigImgWidth = options. outWidth; mBigImgHeight = options. outHeight; mBitmapRect. left = (int) 0; mBitmapRect.top=(int) 0; mBitmapRect.right= (int) mBigImgWidth; mBitmapRect.bottom=(int) mBigImgHeight; Log.d(TAG, String.format("mBigImgWidth=%1$s,mBigImgHeight=%2$s", mBigImgWidth, mBigImgHeight)); mDecoder = BitmapRegionDecoder.newInstance(inputStream, false); requestLayout(); invalidate(); } /** * Judging whether it is out of bounds * @return */ private boolean isOutRect(Rect imgRect, float imgWidth, float imgHeight){ if(imgRect.top<0||imgRect.bottom<0 ||imgRect.top>imgHeight ||imgRect.bottom>imgHeight ||imgRect.left<0 ||imgRect.right<0 ||imgRect.right>imgWidth ||imgRect.left>imgWidth){ return true; } return false; } /** * Center the picture * @return */ private Matrix getCenterMatrix(){ Matrix matrix = new Matrix(); float dx; float dy; float scale; scale = Math.min(getWidth() / mBigImgWidth, getHeight() / mBigImgHeight); matrix.setScale(scale, scale); dx = (getWidth() - mBigImgWidth*scale ) * 0.5f; dy = (getHeight() - mBigImgHeight*scale) * 0.5f; matrix. postTranslate(dx, dy); return matrix; } }
Complete project code: https://gitee.com/niangegelaile/bigImage