Java Question Pan & zoom ImageView help needed

warwound

Expert
Licensed User
Longtime User
Hi.

I'm currently teaching myself some Android SDK stuff and completed a tutorial to make a pan and zoom ImageView.

The code is straightforward so i thought i'd try to make a B4A library based on it.

This is the Android SDK code:

B4X:
package uk.co.martinpearman.panandzoomimageview;

import android.app.Activity;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.os.Bundle;
import android.util.FloatMath;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;

public class PanAndZoomImageViewActivity extends Activity implements OnTouchListener{
    private static final String TAG="PanAndZoomImageViewActivity";
    
   Matrix matrix=new Matrix();
   Matrix savedMatrix=new Matrix();
   
   static final int NONE=0;
   static final int DRAG=1;
   static final int ZOOM=2;
   int mode=NONE;
   
   PointF start=new PointF();
   PointF mid=new PointF();
   float oldDist=1f;
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.image_view);
        ImageView imageView=(ImageView) findViewById(R.id.imageView);
        imageView.setOnTouchListener(this);
    }
    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);
    }
    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));
    }
   @Override
   public boolean onTouch(View v, MotionEvent event) {
      ImageView imageView=(ImageView) v;
      switch(event.getAction() & MotionEvent.ACTION_MASK){
      case MotionEvent.ACTION_DOWN:
         savedMatrix.set(matrix);
         start.set(event.getX(), event.getY());
         mode=DRAG;
         Log.d(TAG, "mode=DRAG");
         break;
      case MotionEvent.ACTION_POINTER_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_UP:
      case MotionEvent.ACTION_POINTER_UP:
         mode=NONE;
         Log.d(TAG, "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 newDist=spacing(event);
            Log.d(TAG, "newDist="+newDist);
            if(newDist>10f){
               matrix.set(savedMatrix);
               float scale=newDist/oldDist;
               matrix.postScale(scale, scale, mid.x, mid.y);
            }
         }
         break;
      }
      
      imageView.setImageMatrix(matrix);
      return true;
   }
}

It works as desired (but is far from perfect).
The ImageView has it's scaleType property set to matrix in the layout xml file:

B4X:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<ImageView
   android:id="@+id/imageView"
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent"
    android:scaleType="matrix"
    android:src="@drawable/ship">
</ImageView>
</FrameLayout>

So my library code is pretty much a copy/paste:

B4X:
package uk.co.martinpearman.b4a.imageviewextras;

import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.FloatMath;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
import anywheresoftware.b4a.BA.ActivityObject;
import anywheresoftware.b4a.BA.Author;
import anywheresoftware.b4a.BA.ShortName;
import anywheresoftware.b4a.BA.Version;

@ActivityObject
@Author("Martin Pearman")
@ShortName("ImageViewExtras")
@Version((float) 1.0)
public class ImageViewExtras {
   private static final String TAG = "B4A";

   public static void enablePanAndZoom(ImageView imageView1) {
      Log.d(TAG, "enablePanAndZoom");
      imageView1.setScaleType(ImageView.ScaleType.MATRIX);

      imageView1.setOnTouchListener(new OnTouchListener() {

         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);
         }

         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));
         }

         Matrix matrix = new Matrix();
         Matrix savedMatrix = new Matrix();

         final int NONE = 0;
         final int DRAG = 1;
         final int ZOOM = 2;
         int mode = NONE;

         PointF start = new PointF();
         PointF mid = new PointF();
         float oldDist = 1f;

         @Override
         public boolean onTouch(View v, MotionEvent event) {
            ImageView imageView = (ImageView) v;
            
            // imageView.setVisibility(View.GONE);
            switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
               savedMatrix.set(matrix);
               start.set(event.getX(), event.getY());
               mode = DRAG;
               Log.d(TAG, "mode=DRAG");
               break;
            case MotionEvent.ACTION_POINTER_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_UP:
            case MotionEvent.ACTION_POINTER_UP:
               mode = NONE;
               Log.d(TAG, "mode=NONE");
               break;
            case MotionEvent.ACTION_MOVE:
               if (mode == DRAG) {
                  matrix.set(savedMatrix);
                  matrix.postTranslate(event.getX() - start.x, event.getY() - start.y);
                  Log.d(TAG, "postTranslate");
               } else if (mode == ZOOM) {
                  float newDist = spacing(event);
                  Log.d(TAG, "newDist=" + newDist);
                  if (newDist > 10f) {
                     matrix.set(savedMatrix);
                     float scale = newDist / oldDist;
                     matrix.postScale(scale, scale, mid.x, mid.y);
                  }
               }
               break;
            }
            Log.d(TAG, imageView.getImageMatrix().toString());
            imageView.setImageMatrix(matrix);
            Log.d(TAG, imageView.getImageMatrix().toString());
            return true;
         }
      });
   }
}

And my B4A code:

B4X:
'Activity module
Sub Process_Globals
End Sub

Sub Globals
   Dim ImageView1 As ImageView
   Dim MyImageView As ImageViewExtras
End Sub

Sub Activity_Create(FirstTime As Boolean)
   Activity.LoadLayout("layoutMain")
End Sub

Sub Activity_Resume
   MyImageView.enablePanAndZoom(ImageView1)
   ImageView1.Bitmap=LoadBitmap(File.DirAssets, "ship.jpg")
End Sub

Sub Activity_Pause (UserClosed As Boolean)
End Sub

layoutMain contains just ImageView1 with all properties left at default, position at (0, 0) with both width and height set to -1 to fill the parent container.

The image doesn't pan or zoom :(

Here's a typical log output for an image drag using the emulator:

B4X:
LogCat connected to: emulator-5554
** Activity (main) Create, isFirst = true **


** Activity (main) Resume **


enablePanAndZoom


mode=DRAG


Matrix{[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]}
Matrix{[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]}
postTranslate
Matrix{[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]}
Matrix{[1.0, 0.0, -3.0][0.0, 1.0, 2.0][0.0, 0.0, 1.0]}
postTranslate


Matrix{[1.0, 0.0, -3.0][0.0, 1.0, 2.0][0.0, 0.0, 1.0]}


Matrix{[1.0, 0.0, -35.0][0.0, 1.0, 69.0][0.0, 0.0, 1.0]}


postTranslate


Matrix{[1.0, 0.0, -35.0][0.0, 1.0, 69.0][0.0, 0.0, 1.0]}


Matrix{[1.0, 0.0, -54.0][0.0, 1.0, 87.0][0.0, 0.0, 1.0]}
postTranslate


Matrix{[1.0, 0.0, -54.0][0.0, 1.0, 87.0][0.0, 0.0, 1.0]}


Matrix{[1.0, 0.0, -67.0][0.0, 1.0, 94.0][0.0, 0.0, 1.0]}


mode=NONE


Matrix{[1.0, 0.0, -67.0][0.0, 1.0, 94.0][0.0, 0.0, 1.0]}


Matrix{[1.0, 0.0, -67.0][0.0, 1.0, 94.0][0.0, 0.0, 1.0]}

It looks like the code is functioning correctly and the correct parts of the switch statement are being executed but the image just doesn't pan.

I added:

B4X:
Log.d(TAG, imageView.getScaleType().toString());

It reports MATRIX so it looks like the ImageView scaleType is being correctly set.

Now the only thing i can think is that the pan and zoom stuff will only work if the ImageView image is set as it's src property and NOT it's background image.
The B4A ImageViewWrapper.class looks to set ONLY the background image of a B4A ImageView:

B4X:
  public void setBitmap(Bitmap value) {
    int gravity = getGravity();
    anywheresoftware.b4a.objects.drawable.BitmapDrawable bd = new anywheresoftware.b4a.objects.drawable.BitmapDrawable();
    bd.Initialize(value);
    bd.setGravity(gravity);
    setBackground((Drawable)bd.getObject());
  }

  public void SetBackgroundImage(Bitmap Bitmap) {
    setBitmap(Bitmap);
  }

Could this be why the library doesn't work?

I looked at setting the ImageView src in the library but found no way to reference my ship.jpg image.
It's not in the Objects\res\drawable folder and not a part of the B4A generated R.java class but is added correctly to the B4A project using the Add files button.

Any ideas?

My B4A code and Eclipse project are attached.

Thanks.

Martin.
 
Last edited:

agraham

Expert
Licensed User
Longtime User
Now the only thing i can think is that the pan and zoom stuff will only work if the ImageView image is set as it's src property and NOT it's background image.
I would guess that is correct. It looks like you might need to use setImageResource , setImageDrawable or setImageBitmap.

Why not extend ImageViewWrapper and override setBitmap or provide a separate setPanAndZoomBitmap methd?

B4X:
 public void setBitmap(Bitmap value) {
    int gravity = getGravity();
    anywheresoftware.b4a.objects.drawable.BitmapDrawable bd = new anywheresoftware.b4a.objects.drawable.BitmapDrawable();
    bd.Initialize(value);
    bd.setGravity(gravity);
    getObject().setBackground((Drawable)bd.getObject());
  }
 

warwound

Expert
Licensed User
Longtime User
Great stuff agraham!

I just added a new method to my existing library code to use the native ImageView setImageBitmap() method and it all works.

My first post mentioned that the pinch to zoom was far from perfect and i'll now take some time to make this library actually useable.

I'm hoping to implement the MultiTouchController class.

It looks pretty complicated but i'll give it a go :)

Martin.
 
Last edited:

warwound

Expert
Licensed User
Longtime User
Following on from this thread:

http://www.b4x.com/forum/bugs-wishlist/12756-webview-does-not-reload-when-zoomed.html#post71919

I have added a new method to the library.

resetPanAndZoom(ImageView1 As ImageView)

This removes the existing touch event listener, resets the image position to default and then re-adds the event listener.

Please note i don't consider this library to be complete or even really suitable for a production app.
And i do not currently have the time to learn what i'd need to learn to make it a proper library.

But for now here is the latest (alpha) version with the new method.

Martin.
 
Last edited:

janfnielsen

New Member
Licensed User
Longtime User
Following on from this thread:

http://www.b4x.com/forum/bugs-wishlist/12756-webview-does-not-reload-when-zoomed.html#post71919

I have added a new method to the library.

resetPanAndZoom(ImageView1 As ImageView)

This removes the existing touch event listener, resets the image position to default and then re-adds the event listener.

Please note i don't consider this library to be complete or even really suitable for a production app.
And i do not currently have the time to learn what i'd need to learn to make it a proper library.

But for now here is the latest (alpha) version with the new method.

Martin.

Thanks for the great lib, i have a question regarding the pulling on an image to scroll over it, when zoomed :

If i load a picture, it positions it fine starting at x,y 0,0, but if i pull/move the image to the side, it seems to move it further than the picture actually is sized for, so in effect a big wide gap between the picture and the edge of the screen comes up, is this intended?, i would love to have the picture only move in the direction where the is actual picture data ? Hope this makes sense :)
 

Attachments

  • Image3.jpg
    Image3.jpg
    30.8 KB · Views: 528

warwound

Expert
Licensed User
Longtime User
Hi there.

I know exactly what you mean - and that's why in an earlier post in this thread i said:

Please note i don't consider this library to be complete or even really suitable for a production app.
And i do not currently have the time to learn what i'd need to learn to make it a proper library.

The code for this library is actually an example from the book Hello Android, 3rd Edition.

The example being a basic introduction to Android touch events, that's why the code isn't really a completed and ready to use View.

And (unfortunately) updating it so that it doesn't pan the image beyond it's bounds and also fixing the rather unpredictable pinch zoom code is far too advanced for me.

Maybe another forum member could help...

Martin.
 

janfnielsen

New Member
Licensed User
Longtime User
Hi Martin,
Thanks for replying, unfortunately for me at this stage in learning android/java/b4a i am also unable to do this level of coding myself.

Jan
 

warwound

Expert
Licensed User
Longtime User
I have removed the ImageViewExtras attachment from post #4 in this thread.

My new library TouchImageView supercedes ImageViewExtras and is far better and fixes all outstanding issues raised in this thread.

Martin.
 
Top