ImageView Drag Limitation In Android

10,018

Solution 1

Add the following parameters to the Touch class:

private float dx; // postTranslate X distance
private float dy; // postTranslate Y distance
private float[] matrixValues = new float[9];
float matrixX = 0; // X coordinate of matrix inside the ImageView
float matrixY = 0; // Y coordinate of matrix inside the ImageView
float width = 0; // width of drawable
float height = 0; // height of drawable

Modify your code after case MotionEvent.ACTION_MOVE:

if (mode == DRAG) {
        matrix.set(savedMatrix);

        matrix.getValues(matrixValues);
        matrixX = matrixValues[2];
        matrixY = matrixValues[5];
        width = matrixValues[0] * (((ImageView) view).getDrawable()
                                .getIntrinsicWidth());
        height = matrixValues[4] * (((ImageView) view).getDrawable()
                                .getIntrinsicHeight());

        dx = event.getX() - start.x;
        dy = event.getY() - start.y;

        //if image will go outside left bound
        if (matrixX + dx < 0){
            dx = -matrixX;
        }
        //if image will go outside right bound
        if(matrixX + dx + width > view.getWidth()){
            dx = view.getWidth() - matrixX - width;
        }
        //if image will go oustside top bound
        if (matrixY + dy < 0){
            dy = -matrixY;
        }
        //if image will go outside bottom bound
        if(matrixY + dy + height > view.getHeight()){
            dy = view.getHeight() - matrixY - height;
        }
        matrix.postTranslate(dx, dy);   
    }

Solution 2

Android documentation says:

It is possible to retrieve the location of a view by invoking the methods getLeft() and getTop(). The former returns the left, or X, coordinate of the rectangle representing the view. The latter returns the top, or Y, coordinate of the rectangle representing the view. These methods both return the location of the view relative to its parent.
In addition, several convenience methods are offered to avoid unnecessary computations, namely getRight() and getBottom(). These methods return the coordinates of the right and bottom edges of the rectangle representing the view. For instance, calling getRight() is similar to the following computation: getLeft() + getWidth().

So you can get your image coordinates before moving it,and you can compute measure of movement in your OnTouchListener class.Now if (X coordinate of your image plus horizontal movement that you computed,be greater than device width or Y coordinate of your image plus vertical movement that you computed,be greater than device height) your image not move.

Solution 3

I used the answer from Benito Bertoli and modified the ACTION_MOVE code changing minor and major sign to make image zoom and drag for image zoomed

here´s the code

private static final float MIN_ZOOM = 1f; 
private static final float MAX_ZOOM = 2.5f; 

public boolean onTouch(View v, MotionEvent event) {
    ImageView view = (ImageView) v;
    view.setScaleType(ImageView.ScaleType.MATRIX);

    // Handle touch events here...
    switch (event.getAction() & MotionEvent.ACTION_MASK) {

        case MotionEvent.ACTION_DOWN: //first finger down only
            savedMatrix.set(matrix);
            start.set(event.getX(), event.getY());
            Log.d(TAG, "mode=DRAG" );
            mode = DRAG;
            break;
        case MotionEvent.ACTION_UP: //first finger lifted
        case MotionEvent.ACTION_POINTER_UP: //second finger lifted
            mode = NONE;
            Log.d(TAG, "mode=NONE" );
            break;
        case MotionEvent.ACTION_POINTER_DOWN: //second finger down
            oldDist = spacing(event);
            Log.d(TAG, "oldDist=" + oldDist);
            if (oldDist > 10f) {
                savedMatrix.set(matrix);
                midPoint(mid, event);
                mode = ZOOM;
                Log.d(TAG, "mode=ZOOM" );
            }
            break;
        case MotionEvent.ACTION_MOVE:
            if (mode == DRAG) {
                //matrix.set(savedMatrix);
                //matrix.postTranslate(event.getX() - start.x, event.getY() - start.y);
                matrix.set(savedMatrix);

                matrix.getValues(matrixValues);
                matrixX = matrixValues[2];
                matrixY = matrixValues[5];
                width = matrixValues[0] * (view.getDrawable().getIntrinsicWidth());
                height = matrixValues[4] * (view.getDrawable().getIntrinsicHeight());


                dx = event.getX() - start.x;
                dy = event.getY() - start.y;

                //if image will go outside left bound
                if (matrixX + dx  > 0){
                    Log.e("dx","lef bound " + dx);
                    dx = -matrixX;
                }
                //if image will go outside right bound
                if(matrixX + dx + width < view.getWidth()){
                    dx = view.getWidth() - matrixX - width;
                }
                //if image will go oustside top bound
                if (matrixY + dy > 0){
                    dy = -matrixY;
                }
                //if image will go outside bottom bound
                if(matrixY + dy + height < view.getHeight()){
                    dy = view.getHeight() - matrixY - height;
                }

                matrix.postTranslate(dx, dy);
            }
            else if (mode == ZOOM) {
                Float newDist = spacing(event);
                Log.d(TAG, "newDist=" + newDist);
                if (newDist > 10f) {
                    matrix.set(savedMatrix);
                    float scale = newDist / oldDist;
                    float[] values = new float[9];
                    matrix.getValues(values);
                    float currentScale = values[Matrix.MSCALE_X];
                    if(scale * currentScale > MAX_ZOOM)
                        scale = MAX_ZOOM / currentScale;
                    else if (scale * currentScale < MIN_ZOOM)
                        scale = MIN_ZOOM / currentScale;
                    matrix.postScale(scale, scale, mid.x, mid.y);

                }
                break;
            }
    } //perform the transformation.

    view.setImageMatrix(matrix);
    return true; // indicate event was handled

}

private float spacing(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);
}

private void midPoint(PointF point, MotionEvent event) {
    float x = event.getX(0) + event.getX(1);
    float y = event.getY(0) + event.getY(1);
    point.set(x / 2, y / 2);
}

    return drawable;
}

Solution 4

  1. We need to create two Rect objects, one is for our view and another for our image according to the view coordinates.

    float[] values = new float[9];
    matrix.getValues(values);
    float globalX = values[Matrix.MTRANS_X];
    float globalY = values[Matrix.MTRANS_Y];
    float scaleX = values[Matrix.MSCALE_X];
    float scaleY = values[Matrix.MSCALE_Y];
    Rect viewRect = new Rect(0, 0, viewWidth, viewHeight);
    Rect imageRect = new Rect((int) globalX, (int) globalY, Math.round(bitmapWidth * scaleX + globalX),
            Math.round(bitmapHeight * scaleY + globalY));
    
    1. If we want our image be always inside our view, then we check this:

      if (!viewRect.contains(imageRect)) {
          matrix.set(lastSetMatrix); //return to last saved parameters
      }
      
    2. Sometimes we need our image always be bigger than our view to position a part of the image inside the view, then we can check this:

      if (!imageRect.contains(viewRect)) {
         matrix.set(lastSetMatrix);
      }
      
Share:
10,018
Vahid Javaherifar
Author by

Vahid Javaherifar

Updated on July 09, 2022

Comments

  • Vahid Javaherifar
    Vahid Javaherifar almost 2 years

    I have an ImageView in a layout and set OnTouchListener on ImageView to drag the ImageView. It's working perfectly. My problem is how can I prevent from move ImageView to out of layout range?

    This is my code:

    Activity class:

    public class RepositionTestActivity extends Activity {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.reposition_test_layout);
            ImageView imageView = (ImageView)findViewById(R.id.android);
            imageView.setOnTouchListener(new Touch());
        }
    }
    

    Touch class:

    public class Touch implements OnTouchListener {
    
        private static final int NONE = 0;
        private static final int DRAG = 1;
        private static final int ZOOM = 2;
    
        private static final float MIN_ZOOM = 1f;
        private static final float MAX_ZOOM = 5f;
    
        private Matrix matrix = new Matrix();
        private Matrix savedMatrix = new Matrix();
    
        private PointF start = new PointF();
        private PointF mid = new PointF();
    
        private int mode = NONE;
        private float oldDistance = 1f;
    
        public boolean onTouch(View view, MotionEvent event) {
            ImageView imageView = (ImageView)view;
    
            switch(event.getAction() & MotionEvent.ACTION_MASK) {
    
                case MotionEvent.ACTION_DOWN:
                    savedMatrix.set(matrix);
                    start.set(event.getX(), event.getY());
                    mode = DRAG;
                    break;
    
                case MotionEvent.ACTION_POINTER_DOWN:
                    oldDistance = spacing(event);
                    if(oldDistance > 10f) {
                        savedMatrix.set(matrix);
                        midPoint(mid, event);
                        mode = ZOOM;
                    }
                    break;
    
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_POINTER_UP:
                    mode = NONE;
                    break;
    
                case MotionEvent.ACTION_MOVE:
                    if(mode == DRAG) {
                        matrix.set(savedMatrix);
                        matrix.postTranslate(event.getX() - start.x, event.getY() - start.y);
                    }
                    else if(mode == ZOOM) {
                        float newDistance = spacing(event);
                        if(newDistance > 10f) {
                            matrix.set(savedMatrix);
                            float scale = newDistance / oldDistance;
                            float[] values = new float[9];
                            matrix.getValues(values);
                            float currentScale = values[Matrix.MSCALE_X];
                            if(scale * currentScale > MAX_ZOOM) 
                                scale = MAX_ZOOM / currentScale;
                            else if (scale * currentScale < MIN_ZOOM)
                                scale = MIN_ZOOM / currentScale;
                            matrix.postScale(scale, scale, mid.x, mid.y);
                        }
                    }
                    break;
            }
            imageView.setImageMatrix(matrix);
            return true;
        }
    
        private float spacing(MotionEvent event) {
            float x = event.getX(0) - event.getX(1);
            float y = event.getY(0) - event.getY(1);
            return FloatMath.sqrt(x * x + y * y);
        }
    
        private void midPoint(PointF point, MotionEvent event) {
            point.set((event.getX(0) + event.getX(1)) / 2, (event.getY(0) + event.getY(1)) / 2);
        }
    
    }
    

    Layout xml:

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/reposition_test_layout"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
    
        <ImageView
            android:id="@+id/android"
            android:src="@drawable/android"
            android:layout_width="300dp"
            android:layout_height="300dp"
            android:scaleType="matrix"
            android:contentDescription="@string/android_description" >
        </ImageView>
    
    </LinearLayout>
    

    enter image description here