Schnodahipfe Schnodahipfe - 10 days ago 11
Android Question

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?

Answer

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