Friday, March 7, 2014

How to make an image with clickable areas with pinch/zoom and move effects

First I need to mention that I got this code samples from below articles and all the credits should go to these authors.

1 . Android Images with Clickable Areas - Part 1 by Bill Lahti

2 . Java Pan / Zoom listener for Android by John Stewien

From first article you can learn how to make an image with clickable areas very easily and from the second article you can learn how to get the pan/zoom effect to an image.

I just combined these two ideas and created an application which has both features. (Image with clickable and at the same time it can be zoomed as well)

I am going to explain only the changes I did for those codes. I mention the name of the java file with codes for your easy reference.You can find the link to source code at the bottom of this article.

PanAndZoomListener class declaration  : PanAndZoomListener extends View.OnTouchListener

(1) Since Images with Clickable areas have two images and we need to zoom both images at the same time I passed both ImageViews as a View array to PanAndZoomListener constructor. Also we need to pass the Context of the Activity class as well. Here is the code,

Inside PanAndZoomListener.java

public PanAndZoomListener(FrameLayout containter, View[] views, int anchor,Context c) {
        panZoomCalculator = new PanZoomCalculator(containter, views, anchor);

        //iv_front and iv_back are ImageViews declared as instance variables.
         iv_front = (ImageView) views[0];
         iv_back = (ImageView)views[1];
         context = c;
         iv_front.setOnTouchListener(this);
 }

(2) Now we need to change the panZoomCalculator constructor accordingly. Because earlier it accepted one View. But now we are passing a View array. So lets change it like this,

Inside PanAndZoomListener.java

PanZoomCalculator(View container, View[] child, int anchor) {
     // Initialize class fields
     currentPan = new PointF(0, 0);
     currentZoom = 1f;

     this.window = container;
     this.child1 = child[0];
     this.child2 = child[1];

     matrix = new Matrix();
     this.anchor = anchor;
     onPanZoomChanged();

    this.child1.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
   
    public void onLayoutChange(View v, int left, int top, int right, int                 bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    onPanZoomChanged();
         }
    });

   this.child2.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {

   public void onLayoutChange(View v, int left, int top, int right, int                    bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    onPanZoomChanged();
       }
   });
}

(3) Then modify onPanZoomChanged() method like this,
you need to create another if-else block for child2

Inside PanAndZoomListener.java

if (child1 instanceof ImageView && ((ImageView) child1).getScaleType() == ImageView.ScaleType.MATRIX) {


 ImageView view = (ImageView) child1;
 Drawable drawable = view.getDrawable();
  if (drawable != null) {
   Bitmap bm = ((BitmapDrawable) drawable).getBitmap();
   if (bm != null) {
      float bmWidth = bm.getWidth();
      float bmHeight = bm.getHeight();

      float fitToWindow = Math.min(winWidth / bmWidth, winHeight / bmHeight);
      float xOffset = (winWidth - bmWidth * fitToWindow) * 0.5f * currentZoom;
      float yOffset = (winHeight - bmHeight * fitToWindow) * 0.5f * currentZoom;

      matrix.reset();
      matrix.postScale(currentZoom * fitToWindow, currentZoom * fitToWindow);
      matrix.postTranslate(currentPan.x + xOffset, currentPan.y + yOffset);
      ((ImageView) child1).setImageMatrix(matrix);
  }
 }
}else {
        ViewGroup.MarginLayoutParams lp =                (ViewGroup.MarginLayoutParams)child1.getLayoutParams();

         lp.leftMargin = (int) currentPan.x + panJitter;
         lp.topMargin = (int) currentPan.y;
         lp.width = (int) (window.getWidth() * currentZoom);
         lp.height = (int) (window.getHeight() * currentZoom);
         panJitter ^= 1;

         child1.setLayoutParams(lp);

}

(4) Inside onTouch() we need to add the code for what to happen when a user is pressed a particular area on the image. It should be triggered when MotionEvent.ACTION_UP event has occurred.

Inside PanAndZoomListener.java

case MotionEvent.ACTION_UP: {
 if (view.getId() == iv_front.getId()) {

  final int x = (int) event.getX();
  final int y = (int) event.getY();
  int touch_color = getHotspotColor(x, y);
  int tolerance = 25;

  if (closeMatch(Color.BLUE, touch_color, tolerance)) 
    Toast.makeText(context, "Pressed Blue color box", Toast.LENGTH_SHORT).show();
  else if (closeMatch(Color.RED, touch_color, tolerance)) 
    Toast.makeText(context, "Pressed Red color box", Toast.LENGTH_SHORT).show();
  else if (closeMatch(Color.GREEN, touch_color, tolerance)) 
   Toast.makeText(context, "Pressed Green color box", Toast.LENGTH_SHORT).show();
  else if (closeMatch(Color.YELLOW, touch_color, tolerance)) 
  Toast.makeText(context, "Pressed Yellow color box", Toast.LENGTH_SHORT).show();
 }
}

(5) Now inside our onCreate() of  the Activity class, we need to initialize the views and make a new PanAndZoomListener object and pass the values like this,

Inside MainActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  FrameLayout fl = (FrameLayout)findViewById(R.id.frame_layout);
  iv_front = (ImageView)findViewById(R.id.pan_and_zoom_image);
  iv_back = (ImageView)findViewById(R.id.pan_and_zoom_image_areas);
  View[] views = {iv_front,iv_back};
  fl.setOnTouchListener(new PanAndZoomListener(fl, views, PanAndZoomListener.Anchor.TOPLEFT,MainActivity.this));
}


That is it. 

Here is the source code (I used Android Studio, min sdk =11)

In my source code I used the below image. When you pressed the  tables at right side  toast messages will be appeared accordingly.











4 comments:

  1. Great Work!!!! You helped me a lot, I'm so happy that I want to come in Sri Lanka and kiss you!!! thank you very much and congratulation for your job!!
    Marco from Italy, Tuscany

    ReplyDelete
  2. I tried to download the source code with no luck. Can you host it somewhere else?

    ReplyDelete
  3. Great implementation! I may extend it a bit, but it's pretty nice already.

    ReplyDelete
  4. repost a link to the source code, please!
    ty

    ReplyDelete