Any of the above solution did not work for me. It worked for me when I used the below ImageView:
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
@SuppressLint("AppCompatCustomView")
public class TouchImageView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener,View.OnTouchListener {
private final static int SINGLE_TOUCH = 1; //Single point
private final static int DOUBLE_TOUCH = 2; //Double finger
//Multi-finger touch mode, single finger, double finger
private int mode;
//Distance between two finger touch points
private float oldDist;
private float newDist;
/**
* Maximum zoom level
*/
private static final float MAX_SCALE = 5f;
/**
* Double-click the zoom level
*/
private float mDoubleClickScale = 2;
/**
* Scales when initialization, if the picture is wide or higher than the screen, this value will be less than 0
*/
private float initScale = 1.0f;
private boolean once = true;
private RectF rectF;
/**
* Double-click detection
*/
private GestureDetector mGestureDetector;
private int x = 0;
private int y = 0;
private Point mPoint = new Point();
private final Matrix matrix = new Matrix();
private Matrix oldMatrix = new Matrix();
private ValueAnimator animator;
public TouchImageView(Context context) {
this(context, null);
}
public TouchImageView(Context context, AttributeSet attrs) {
super(context, attrs);
super.setScaleType(ScaleType.MATRIX);
setOnTouchListener(this);
/**
* Double click to implement the picture to zoom
*/
mGestureDetector = new GestureDetector(context,
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
changeViewSize(e);
return true;
}
});
}
@Override
public boolean onTouch(View view, MotionEvent event) {
rectF = getMatrixRectF(); //Get image boundaries
if (mGestureDetector.onTouchEvent(event))
return true;
switch (event.getAction() & event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
//If the boundary of the picture is overstelled, then intercept the event, not letting ViewPager processing
if (rectF.width() > getWidth() || rectF.height() > getHeight()) {
getParent().requestDisallowInterceptTouchEvent(true);
}
mode = SINGLE_TOUCH;
x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
if (mode >= DOUBLE_TOUCH) //Double finger zoom
{
getParent().requestDisallowInterceptTouchEvent(true);
newDist = calculateDist(event); //Calculate distance
Point point = getMiPoint(event); //Get the midpoint coordinates between the two-hand fingers
if (newDist > oldDist + 1) //Pixabay (add one to prevent jitter)
{
changeViewSize(oldDist, newDist, point); //Enlarge reduction according to distance
oldDist = newDist;
}
if (oldDist > newDist + 1) //Narrow
{
changeViewSize(oldDist, newDist, point);
oldDist = newDist;
}
}
if (mode == SINGLE_TOUCH) //Drag and drop
{
float dx = event.getRawX() - x;
float dy = event.getRawY() - y;
//If the boundary of the picture in the movement exceeds the screen, then intercept the event, do not let ViewPager processing
if (rectF.width() > getWidth() || rectF.height() > getHeight()) {
getParent().requestDisallowInterceptTouchEvent(true);
}
//If you move the picture to the right, don't intercept the event, let the ViewPager process
if (rectF.left >= 0 && dx > 0)
getParent().requestDisallowInterceptTouchEvent(false);
//If you move to the left, don't intercept the event, let ViewPager processing
if (rectF.right <= getWidth() && dx < 0)
getParent().requestDisallowInterceptTouchEvent(false);
if (getDrawable() != null) {
//If the image width or height does not exceed the screen, then it is forbidden to slide around or down.
if (rectF.width() <= getWidth())
dx = 0;
if (rectF.height() < getHeight())
dy = 0;
//If the picture moves down to the end, don't let it continue to move
if (rectF.top >= 0 && dy > 0)
dy = 0;
//If the picture moves up to the end, don't let it continue to move
if (rectF.bottom <= getHeight() && dy < 0)
dy = 0;
//When the movement distance is greater than 1, it moves because Action_Move is relatively sensitive.
// The finger can only detect the jitter of the finger and let the picture move.
if (Math.abs(dx) > 1 || Math.abs(dy) > 1)
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);
}
}
x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode += 1;
oldDist = calculateDist(event);
Log.e("q", "" + "a");
Log.e(":::", "" + event.getPointerCount() + " " + event.getActionIndex() + " " + event.findPointerIndex(0));
break;
case MotionEvent.ACTION_POINTER_UP:
mode -= 1;
break;
case MotionEvent.ACTION_UP:
backToPosition();
mode = 0;
break;
//In Action_Move, the event is intercepted, sometimes Action_up can't trigger, so add Action_Cancel
case MotionEvent.ACTION_CANCEL:
backToPosition();
mode = 0;
break;
default:
break;
}
return true;
}
/**
* Calculate the distance between two finger touch points
*/
private float calculateDist(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);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
}
/**
* If it is separated from the screen boundary, then the image boundary is combined with the screen boundary.
* If the finger moves quickly, the picture will have a blank distance after the picture is stopped, and then the judgment can no longer move,
* However, it has appeared before the next judgment can continue to move.
* So you need to reset
*/
private void backToPosition() {
if (rectF.left >= 0) { //Image Left boundary and screen from the screen
matrix.postTranslate(-rectF.left, 0);
setImageMatrix(matrix);
}
if (rectF.right <= getWidth()) { //Image Right Boundary and Screen Distance
matrix.postTranslate(getWidth() - rectF.right, 0);
setImageMatrix(matrix);
}
if (rectF.top >= 0) { //Image on the image and the screen from the screen
matrix.postTranslate(0, -rectF.top);
setImageMatrix(matrix);
}
if (rectF.bottom <= getHeight()) { //Image of the image and the screen
matrix.postTranslate(0, getHeight() - rectF.bottom);
setImageMatrix(matrix);
}
}
/**
* Get the zoom in the zoom of double finger zoom
*
* @return
*/
private Point getMiPoint(MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
mPoint.set((int) x / 2, (int) y / 2);
return mPoint;
}
/**
* Double finger zoom picture
*/
private void changeViewSize(float oldDist, float newDist, Point mPoint) {
float scale = newDist / oldDist; //scaling ratio
matrix.postScale(scale, scale, mPoint.x, mPoint.y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix);
//Prevent reduction is less than the initial picture size, need to reset
reSetMatrix();
//If the zoom has been larger than the target multiple, stop, because it is possible to be exceeded, then directly zoom to the target size
if (getMatrixValueX() >= MAX_SCALE)
{
matrix.postScale(MAX_SCALE/getMatrixValueX(), MAX_SCALE/getMatrixValueX(), x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix);
return;
}
}
/**
* Double click to zoom pictures
*/
private void changeViewSize(MotionEvent e) {
//Get double-click coordinates
final float x = e.getX();
final float y = e.getY();
//If you are still zooming, you will return directly.
if (animator != null && animator.isRunning())
return;
//Judgment is a state in which it is amplified or reduced
if (!isZoomChanged()) {
animator = ValueAnimator.ofFloat(1.0f, 2.0f);
} else {
animator = ValueAnimator.ofFloat(1.0f, 0.0f);
}
animator.setTarget(this);
animator.setDuration(500);
animator.setInterpolator(new DecelerateInterpolator());
animator.start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
Float value = (Float) animator.getAnimatedValue();
matrix.postScale(value, value, x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix);
/**
* Control reduction range
* If it is already less than the initial size, then restore to the initial size, then stop
*/
if (checkRestScale()) {
matrix.set(oldMatrix);
setImageMatrix(matrix);
return;
}
/**
* Control the range of amplification
* If the magnification of the target is already larger than the target, it is directly in the target magnification.
* Then stop
*/
if (getMatrixValueX() >= mDoubleClickScale)
{
matrix.postScale(mDoubleClickScale/getMatrixValueX(), mDoubleClickScale/getMatrixValueX(), x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix);
return;
}
}
});
}
/**
* Judging whether the zoom level is changed
*
* @return TRUE expressed the non-initial value, FALSE indicates the initial value.
*/
private boolean isZoomChanged() {
float[] values = new float[9];
getImageMatrix().getValues(values);
//Get the current X-axis scale level
float scale = values[Matrix.MSCALE_X];
//Get the X-axis scaling level of the template, both
oldMatrix.getValues(values);
return scale != values[Matrix.MSCALE_X];
}
/**
* Reset Matrix
*/
private void reSetMatrix() {
if (checkRestScale()) {
matrix.set(oldMatrix);
setImageMatrix(matrix);
return;
}
}
/**
* Setup double-click a large multiple
*/
private void setDoubleClickScale(RectF rectF)
{
if(rectF.height()<getHeight()-100)
{
mDoubleClickScale=getHeight()/rectF.height();
}
else
mDoubleClickScale=2f;
}
/**
* Judging whether it needs to be reset
*
* @return When the current zoom level is less than the template zoom level, reset
*/
private boolean checkRestScale() {
// TODO Auto-generated method stub
float[] values = new float[9];
getImageMatrix().getValues(values);
//Get the current X-axis scale level
float scale = values[Matrix.MSCALE_X];
//Get the X-axis scaling level of the template, both
oldMatrix.getValues(values);
return scale < values[Matrix.MSCALE_X];
}
private float getMatrixValueX()
{
// TODO Auto-generated method stub
float[] values = new float[9];
getImageMatrix().getValues(values);
//Get the current X-axis scale level
float scale = values[Matrix.MSCALE_X];
//Get the X-axis scaling level of the template, both
oldMatrix.getValues(values);
return scale / values[Matrix.MSCALE_X];
}
/**
* When zooming, perform image display scope control
*/
private void checkBorderAndCenterWhenScale()
{
RectF rect = getMatrixRectF();
float deltaX = 0;
float deltaY = 0;
int width = getWidth();
int height = getHeight();
// Control range if wide or higher than the screen
if (rect.width() >= width)
{
if (rect.left > 0)
{
deltaX = -rect.left;
}
if (rect.right < width)
{
deltaX = width - rect.right;
}
}
if (rect.height() >= height)
{
if (rect.top > 0)
{
deltaY = -rect.top;
}
if (rect.bottom < height)
{
deltaY = height - rect.bottom;
}
}
// If the width or higher is less than the screen, let it hit
if (rect.width() < width)
{
deltaX = width * 0.5f - rect.right + 0.5f * rect.width();
}
if (rect.height() < height)
{
deltaY = height * 0.5f - rect.bottom + 0.5f * rect.height();
}
Log.e("TAG", "deltaX = " + deltaX + " , deltaY = " + deltaY);
matrix.postTranslate(deltaX, deltaY);
setImageMatrix(matrix);
}
/**
* Get the range of images according to the Matrix of the current picture
*
* @return
*/
private RectF getMatrixRectF()
{
RectF rect = new RectF();
Drawable d = getDrawable();
if (null != d)
{
rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
matrix.mapRect(rect); //If this is not this, the output of the following Log will be the same as the previous sentence.
}
Log.e("aaaa",""+rect.bottom+" "+rect.left+" "+rect.right+" "+rect.top);
return rect;
}
@Override
public void onGlobalLayout() {
if (once)
{
Drawable d = getDrawable();
if (d == null)
return;
Log.e("TAG", d.getIntrinsicWidth() + " , " + d.getIntrinsicHeight());
int width = getWidth();
int height = getHeight();
// Get the width and high of the picture
int dw = d.getIntrinsicWidth();
int dh = d.getIntrinsicHeight();
float scale = 1.0f;
// If the image is wide or higher than the screen, zoom to the width or high of the screen.
if (dw > width && dh <= height)
{
scale = width * 1.0f / dw;
}
if (dh > height && dw <= width)
{
scale = height * 1.0f / dh;
}
// If the width and high are greater than the screen, it will make it adapt to the screen size according to the proportion.
if (dw > width && dh > height)
{
scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
}
initScale = scale;
Log.e("TAG", "initScale = " + initScale);
matrix.postTranslate((width - dw) / 2, (height - dh) / 2);
matrix.postScale(scale, scale, getWidth() / 2,
getHeight() / 2);
// Image Move to the screen center
setImageMatrix(matrix);
oldMatrix.set(getImageMatrix());
once = false;
RectF rectF=getMatrixRectF();
setDoubleClickScale(rectF);
}
}
}
Also, I used the below ExtendedViewPager:
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.viewpager.widget.ViewPager;
public class ExtendedViewPager extends ViewPager {
public ExtendedViewPager(Context context) {
super(context);
}
public ExtendedViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
try {
return super.onTouchEvent(ev);
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
try {
return super.onInterceptTouchEvent(ev);
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
}
return false;
}
}