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.











Wednesday, March 5, 2014

Migrating from Eclipse to Android Studio

I had a very difficult time to import an eclipse project to Android Studio.  So finally I found a method to do that.

Step 1 : Open up eclipse and export the project that need to import to Android Studio.


Step 2 : A window will be appeared. Select "Generate Gradle build files"


Step 3: Select the project you want to export


Step 4 : Now a window will be appeared like below. Click "Finish" button.


Step 5 :  Again click "Finish" in next window as well.


Step 6 : Now if all the things worked fine, a file named "build.gradle" has been generated inside your selected project folder.


Step 7 : This is the gradle version that is used to build the project. Open that build.gradle file to check your gradle version.



Step 8 :  Now open up the Android Studio. Select File->Open. Select that build.gradle  file. Press "Ok"



Step 9 : Then a message box will pop up like this. Select "Yes"


Step 10 : Now a new window will be appeared like below. I have installed gradle 1.9 version on my PC. So I select "Use local gradle distribution" option. If you want you can download  latest gradle version from here. Also you can select the "recommended" option as well.




Step 10 : Select "Ok". It will start to build the project and finally import into Android Studio.



Here is the final output.


Other references : https://www.youtube.com/watch?v=b84t7p9YuX8&index=11&list=LL7iK14mNE71scGEhxKuWNlw