Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - zoom in/out RelativeLayout with spread/pinch

I have an activity with a RelativeLayout and a private class in it, which extends the SimpleOnScaleGestureListener. In the onScale method of the listener I'd like to zoom in/out of the whole layout (everything the user sees) with the user spreading/pinching his fingers.

I'd like the changes to the layout NOT to be permanent, i.e. when the spread/pinch gesture is over I'd like the layout to go back to what it was in first place (any resetting could be done in the onScaleEnd method of the SimpleOnScaleGestureListener for example).

I've tried to implement it via calling setScaleX and setScaleY on the RelativeLayout and also by using a ScaleAnimation. Neither resulted in smooth zooming (or anything that could at all be called zooming). Is it even possible to zoom in/out a RelativeLayout?

The only idea I have left would be reading a screenshot from the cache and putting it as an ImageView on top of the whole layout and the zoom in/out of this image via setImageMatrix. I have no clue, however, how to implement this.

May layout also contains a container for a fragment, which is empty at the time the zooming is supposed to be possible. In the onScaleEnd gesture, the fragment is put into it's container (already implemented and working fine). Here is my layout:

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout_pinch" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" >      <!-- Layout containing the thumbnail ImageViews --> <LinearLayout     android:id="@+id/thumbnail_group_pui"     android:layout_width="match_parent"     android:layout_height="wrap_content"     android:layout_centerVertical="true"     android:orientation="horizontal" >      <ImageView         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:background="@drawable/tn_c1"/>      <ImageView         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:background="@drawable/tn_c2"/>      <ImageView         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:background="@drawable/tn_c3"/>      <ImageView         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:background="@drawable/tn_c4"/>      <ImageView         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:background="@drawable/tn_c5"/>      <ImageView         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:background="@drawable/tn_c6"/>      <ImageView         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:background="@drawable/tn_c7"/>  </LinearLayout>   <!-- Layout containing the dashed boxes --> <LinearLayout     android:layout_width="match_parent"     android:layout_height="152dp"     android:layout_centerVertical="true"     android:orientation="horizontal" >      <ImageView         android:layout_width="177dp"         android:layout_height="match_parent"         android:layout_marginLeft="3dp"         android:layout_marginRight="3dp"         android:background="@drawable/dashed_box"/>     <ImageView         android:layout_width="177dp"         android:layout_height="match_parent"         android:layout_marginLeft="3dp"         android:layout_marginRight="3dp"         android:background="@drawable/dashed_box"/>     <ImageView         android:layout_width="177dp"         android:layout_height="match_parent"         android:layout_marginLeft="3dp"         android:layout_marginRight="3dp"         android:background="@drawable/dashed_box"/>     <ImageView         android:layout_width="177dp"         android:layout_height="match_parent"         android:layout_marginLeft="3dp"         android:layout_marginRight="3dp"         android:background="@drawable/dashed_box"/>     <ImageView         android:layout_width="177dp"         android:layout_height="match_parent"         android:layout_marginLeft="3dp"         android:layout_marginRight="3dp"         android:background="@drawable/dashed_box"/>     <ImageView         android:layout_width="177dp"         android:layout_height="match_parent"         android:layout_marginLeft="3dp"         android:layout_marginRight="3dp"         android:background="@drawable/dashed_box"/>     <ImageView         android:layout_width="177dp"         android:layout_height="match_parent"         android:layout_marginLeft="3dp"         android:layout_marginRight="3dp"         android:background="@drawable/dashed_box"/>  </LinearLayout>   <!-- Container for the fragments --> <FrameLayout     android:id="@+id/fragment_container_pui"     android:layout_width="match_parent"     android:layout_height="match_parent" />   </RelativeLayout> 

EDIT I found these two related topics:
Extending RelativeLayout, and overriding dispatchDraw() to create a zoomable ViewGroup
Zoom Content in a RelativeLayout

I did not get the implementation, however. What other methods do I have to include in the extended class to actually scale the layout or reset it?

like image 836
Schnodahipfe Avatar asked Apr 04 '12 15:04

Schnodahipfe


People also ask

What is pinch zoom in Android?

Pinch 2 or more fingers together or apart to adjust zoom. To zoom temporarily, quickly tap the screen 3 times and hold down your finger on the third tap. Drag your finger to move around the screen. Lift your finger to zoom out.

Which is better LinearLayout or RelativeLayout?

RelativeLayout is used more in applications. We can use LinearLayout inside RelativeLayout. We can also use RelativeLayout as a Child of LinearLayout.

What is the difference between RelativeLayout and LinearLayout?

LinearLayout : is a ViewGroup that aligns all children in a single direction, vertically or horizontally. RelativeLayout : is a ViewGroup that displays child views in relative positions.


2 Answers

So I created a subclass of RelativeLayout as described in the above mentioned topics. It looks like this:

public class ZoomableRelativeLayout extends RelativeLayout { float mScaleFactor = 1; float mPivotX; float mPivotY;  public ZoomableRelativeLayout(Context context) {     super(context);     // TODO Auto-generated constructor stub }  public ZoomableRelativeLayout(Context context, AttributeSet attrs) {     super(context, attrs);     // TODO Auto-generated constructor stub }  public ZoomableRelativeLayout(Context context, AttributeSet attrs,         int defStyle) {     super(context, attrs, defStyle);     // TODO Auto-generated constructor stub }  protected void dispatchDraw(Canvas canvas) {     canvas.save(Canvas.MATRIX_SAVE_FLAG);     canvas.scale(mScaleFactor, mScaleFactor, mPivotX, mPivotY);     super.dispatchDraw(canvas);     canvas.restore(); }  public void scale(float scaleFactor, float pivotX, float pivotY) {     mScaleFactor = scaleFactor;     mPivotX = pivotX;     mPivotY = pivotY;     this.invalidate(); }  public void restore() {     mScaleFactor = 1;     this.invalidate(); }  } 

My implementation of the SimpleOnScaleGestureListener looks like this:

private class OnPinchListener extends SimpleOnScaleGestureListener {      float startingSpan;      float endSpan;     float startFocusX;     float startFocusY;       public boolean onScaleBegin(ScaleGestureDetector detector) {         startingSpan = detector.getCurrentSpan();         startFocusX = detector.getFocusX();         startFocusY = detector.getFocusY();         return true;     }       public boolean onScale(ScaleGestureDetector detector) {         mZoomableRelativeLayout.scale(detector.getCurrentSpan()/startingSpan, startFocusX, startFocusY);         return true;     }      public void onScaleEnd(ScaleGestureDetector detector) {         mZoomableRelativeLayout.restore();     } } 

Hope this helps!

Update:

You can integrate OnPinchListener for your ZoomableRelativelayout by using ScaleGestureDetector:

ScaleGestureDetector scaleGestureDetector = new ScaleGestureDetector(this, new OnPinchListener()); 

And you are required to bind touch listener of Zoomable layout with the touch listener of ScaleGestureDetector:

mZoomableLayout.setOnTouchListener(new OnTouchListener() {                  @Override                 public boolean onTouch(View v, MotionEvent event) {                     // TODO Auto-generated method stub                     scaleGestureDetector.onTouchEvent(event);                     return true;                 }             }); 
like image 87
Schnodahipfe Avatar answered Oct 09 '22 00:10

Schnodahipfe


Create class called Zoomlayout which extends any layout that you want to zoom in my case it is Relative Layout.

public class ZoomLayout extends RelativeLayout implements ScaleGestureDetector.OnScaleGestureListener {  private enum Mode {    NONE,    DRAG,    ZOOM  }   private static final String TAG = "ZoomLayout";  private static final float MIN_ZOOM = 1.0f;  private static final float MAX_ZOOM = 4.0f;   private Mode mode = Mode.NONE;  private float scale = 1.0f;  private float lastScaleFactor = 0f;   // Where the finger first  touches the screen  private float startX = 0f;  private float startY = 0f;   // How much to translate the canvas  private float dx = 0f;  private float dy = 0f;  private float prevDx = 0f;  private float prevDy = 0f;   public ZoomLayout(Context context) {    super(context);    init(context);  }   public ZoomLayout(Context context, AttributeSet attrs) {    super(context, attrs);    init(context);  }   public ZoomLayout(Context context, AttributeSet attrs, int defStyle) {    super(context, attrs, defStyle);    init(context);  }   public void init(Context context) {    final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, this);    this.setOnTouchListener(new OnTouchListener() {      @Override      public boolean onTouch(View view, MotionEvent motionEvent) {        switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {          case MotionEvent.ACTION_DOWN:            Log.i(TAG, "DOWN");            if (scale > MIN_ZOOM) {              mode = Mode.DRAG;              startX = motionEvent.getX() - prevDx;              startY = motionEvent.getY() - prevDy;            }            break;          case MotionEvent.ACTION_MOVE:            if (mode == Mode.DRAG) {              dx = motionEvent.getX() - startX;              dy = motionEvent.getY() - startY;            }            break;          case MotionEvent.ACTION_POINTER_DOWN:            mode = Mode.ZOOM;            break;          case MotionEvent.ACTION_POINTER_UP:            mode = Mode.DRAG;            break;          case MotionEvent.ACTION_UP:            Log.i(TAG, "UP");            mode = Mode.NONE;            prevDx = dx;            prevDy = dy;            break;        }        scaleDetector.onTouchEvent(motionEvent);         if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {          getParent().requestDisallowInterceptTouchEvent(true);          float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;          float maxDy = (child().getHeight() - (child().getHeight() / scale))/ 2 * scale;          dx = Math.min(Math.max(dx, -maxDx), maxDx);          dy = Math.min(Math.max(dy, -maxDy), maxDy);          Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx            + ", max " + maxDx);          applyScaleAndTranslation();        }         return true;      }    });  }   // ScaleGestureDetector   @Override  public boolean onScaleBegin(ScaleGestureDetector scaleDetector) {    Log.i(TAG, "onScaleBegin");    return true;  }   @Override  public boolean onScale(ScaleGestureDetector scaleDetector) {    float scaleFactor = scaleDetector.getScaleFactor();    Log.i(TAG, "onScale" + scaleFactor);    if (lastScaleFactor == 0 || (Math.signum(scaleFactor) == Math.signum(lastScaleFactor))) {      scale *= scaleFactor;      scale = Math.max(MIN_ZOOM, Math.min(scale, MAX_ZOOM));      lastScaleFactor = scaleFactor;    } else {      lastScaleFactor = 0;    }    return true;  }   @Override  public void onScaleEnd(ScaleGestureDetector scaleDetector) {    Log.i(TAG, "onScaleEnd");  }   private void applyScaleAndTranslation() {    child().setScaleX(scale);    child().setScaleY(scale);    child().setTranslationX(dx);    child().setTranslationY(dy);  }   private View child() {    return getChildAt(0);  }  } 

After this add ZoomLayout in xml that has only one child.For example

<?xml version="1.0" encoding="utf-8"?> <com.focusmedica.digitalatlas.headandneck.ZoomLayout     xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="match_parent"     android:id="@+id/zoomLayout"     android:background="#000000"     android:layout_height="match_parent">  <RelativeLayout     android:layout_width="match_parent"     android:layout_height="match_parent">  <TextView     android:paddingTop="5dp"     android:textColor="#ffffff"     android:text="Heading"     android:gravity="center"     android:textAlignment="textStart"     android:paddingLeft="5dp"     android:textSize="20sp"     android:textStyle="bold"     android:layout_width="match_parent"     android:layout_height="wrap_content"     android:id="@+id/tvSubtitle2"     android:layout_toLeftOf="@+id/ivOn"     android:layout_alignParentLeft="true"     android:layout_alignParentStart="true" />  <ImageView     android:id="@+id/ivOff"     android:layout_width="40dp"     android:layout_height="40dp"     android:src="@drawable/off_txt"     android:layout_alignParentTop="true"     android:layout_alignParentRight="true"     android:layout_alignParentEnd="true" />  <ImageView     android:id="@+id/ivOn"     android:layout_width="40dp"     android:layout_height="40dp"     android:src="@drawable/on_txt"     android:layout_alignParentTop="true"     android:layout_alignLeft="@+id/pinOn"     android:layout_alignStart="@+id/pinOn" />  <ImageView     android:id="@+id/pinOff"     android:visibility="invisible"     android:layout_width="40dp"     android:layout_height="40dp"     android:src="@drawable/pin_off"     android:layout_alignParentTop="true"     android:layout_alignParentRight="true"     android:layout_alignParentEnd="true" />  <ImageView     android:id="@+id/pinOn"     android:layout_width="40dp"     android:layout_height="40dp"     android:src="@drawable/pin_on"     android:layout_alignParentTop="true"     android:layout_toLeftOf="@+id/ivOff"     android:layout_toStartOf="@+id/ivOff" />  <RelativeLayout     android:id="@+id/linear"     android:layout_width="match_parent"     android:layout_height="wrap_content"     android:layout_centerHorizontal="true"     android:layout_centerVertical="true">  <ImageView     android:src="@drawable/wait"     android:layout_width="match_parent"     android:layout_height="300dp"     android:id="@+id/fullIVideo"/>      <ImageView         android:src="@drawable/wait"         android:layout_width="match_parent"         android:layout_height="300dp"         android:id="@+id/colorCode"/>      <ImageView         android:src="@drawable/wait"         android:layout_width="match_parent"         android:layout_height="300dp"         android:id="@+id/labelText"/>  <ImageView     android:src="@drawable/download"     android:layout_marginTop="91dp"     android:layout_width="100dp"     android:layout_height="100dp"     android:id="@+id/label_play"     android:layout_alignTop="@+id/fullIVideo"     android:layout_centerVertical="true"     android:layout_centerHorizontal="true" />     </RelativeLayout>  <LinearLayout     android:orientation="vertical"     android:id="@+id/custom_toast_layout"     android:layout_width="300dp"     android:layout_above="@+id/up"     android:background="@drawable/rectangle_frame"     android:paddingLeft="10dp"     android:paddingBottom="10dp"     android:paddingTop="10dp"     android:paddingRight="10dp"     android:layout_centerHorizontal="true"     android:layout_centerVertical="true"     android:layout_height="wrap_content">      <TextView         android:textSize="15sp"         android:textColor="#ffffff"         android:layout_gravity="center"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:text="Medium Text"         android:id="@+id/tvLabel" />      <TextView         android:textColor="#ffffff"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_marginTop="5dp"         android:text="New Text"         android:layout_gravity="center"         android:id="@+id/tvLabelDescription" /> </LinearLayout>  <ImageView     android:layout_width="50dp"     android:layout_height="50dp"     android:src="@drawable/up"     android:layout_alignParentBottom="true"     android:layout_centerHorizontal="true"     android:id="@+id/up" />     </RelativeLayout> </com.focusmedica.digitalatlas.headandneck.ZoomLayout> 

Now in MainActivity create object of ZoomLayout and define id.Like

ZoomLayout zoomlayout= findViewbyId(R.id.zoomLayout); zoomlayout.setOnTouchListener(FullScreenVideoActivity.this);  public boolean onTouch(View v, MotionEvent event) {      linear.init(FullScreenVideoActivity.this);      return false;  } 
like image 28
Ashish Kumar Pal Avatar answered Oct 08 '22 23:10

Ashish Kumar Pal