2011年4月30日 星期六

[Android] extend Gallery support Click/Zooming/Panning with scale limit/pan bounds and scroll

You can learnd some zooming/panning knowledge from link below.
http://www.zdnet.com/blog/burnette/how-to-use-multi-touch-in-android-2/1747

Then you will want to know how to add scale limit and pan bounds, and the answer is as follow link.
http://www.zdnet.com/blog/burnette/how-to-use-multi-touch-in-android-2-part-6-implementing-the-pinch-zoom-gesture/1847

Maybe you want to built-in the zooming/panning function in a ImageView.
http://code.google.com/p/4chan-image-browser/source/browse/src/se/robertfoss/MultiTouchZoom/TouchImageView.java?r=02836650d3b69dd6a2fce1304c34ded1531d6ad5

http://code.google.com/p/4chan-image-browser/source/browse/src/se/robertfoss/MultiTouchZoom/WrapMotionEvent.java?r=c1ea13ef76d6f3ec6024cecf4a6db68e42030916

We all want to watch pictures in a scroll view as Gallery, but the OnTouch event will conflict with OnScroll and OnFling event.
I try to work it out by evaluate if the image bound cross over the bound of container, then raise OnKeyDown event when reach the condition.
In order to avoid the TouchImageView intercept the OnTouch event, so built-in the multi-touch function in Gallery view.
Maybe we want to click on a image to retrieve original size, so I expend the multi-touch mode with "TAP" to reset the matrix.


The source code of "GalleryEx" as below
 ============================================
package com.example.twnin;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.util.Log;
import android.view.*;
import android.widget.Gallery;
import android.widget.ImageView;


public class GalleryEx extends Gallery
{
    private Context m_Context;
    private int m_horizontalMargin;
    private int m_verticalMargin;


    //variables for zoom limit and pan bounds   
    private float[] m_matrixValues = new float[9];
    private float m_maxZoom = 2f;
    private float m_minZoom = 0.75f;
    private RectF m_viewRect;


    //variables for multi-touch   
    private Matrix m_matrix = new Matrix();
    private Matrix m_savedMatrix = new Matrix();


    static final int NONE = 0;
    static final int TAP = 1;
    static final int DRAG = 2;
    static final int ZOOM = 3;
    int m_mode = NONE;


    private PointF m_start = new PointF();
    private PointF m_mid = new PointF();


    private float m_oldDist = 1f;

    private int m_eventPageIndex = 0;

    MotionEvent m_startEvent;

    public GalleryEx(Context context)
    {
        super(context);
        m_Context = context;
    }

    public void setImageHorizontalMargin(int horizontalMargin)
    {
        m_horizontalMargin = horizontalMargin;
    }

    public void setImageVerticalMargin(int verticalMargin)
    {
        m_verticalMargin = verticalMargin;
    }

    @Override   
    public boolean onTouchEvent(MotionEvent event)
    {
        ImageView myImageView = (ImageView) super.getSelectedView();
        m_viewRect = new RectF(0, 0, myImageView.getWidth(), myImageView.getHeight());

        switch (event.getAction() & MotionEvent.ACTION_MASK)
        {
            case MotionEvent.ACTION_DOWN:
                m_savedMatrix.set(m_matrix);
                m_start.set(event.getX(), event.getY());
                m_startEvent = MotionEvent.obtain(event);
                m_mode = TAP;
                m_eventPageIndex = myImageView.getId();
                break;

            case MotionEvent.ACTION_POINTER_DOWN:
                m_oldDist = spacing(event);
                if (m_oldDist > 10f)
                {
                    m_savedMatrix.set(m_matrix);
                    midPoint(m_mid, event);
                    m_mode = ZOOM;
                }
                break;

            case MotionEvent.ACTION_UP:
                if (m_mode == TAP)
                {
                    // do something when user click                   
                    m_matrix.reset();
                    m_mode = NONE;
                }
                onScroll(m_startEvent, event, myImageView.getLeft()-25, 0);
                break;

            case MotionEvent.ACTION_POINTER_UP:
                m_mode = NONE;
                break;

            case MotionEvent.ACTION_MOVE:
                if (m_mode == TAP || m_mode == DRAG)
                {
                    // if page changed then return                   
                     if (m_eventPageIndex != myImageView.getId())
                    {
                        m_matrix.reset();
                        this.setSelection(myImageView.getId());
                        return true;
                    }

                    m_mode = DRAG;
                    m_matrix.set(m_savedMatrix);

                    // limit pan boundary                   
                    m_matrix.getValues(m_matrixValues);
                    float currentY = m_matrixValues[Matrix.MTRANS_Y];
                    float currentX = m_matrixValues[Matrix.MTRANS_X];
                    float currentScale = m_matrixValues[Matrix.MSCALE_X];
                    float currentHeight = myImageView.getHeight() * currentScale;
                    float currentWidth = myImageView.getWidth() * currentScale;
                    float dx = event.getX() - m_start.x;
                    float dy = event.getY() - m_start.y;
                    float newX = currentX+dx;
                    float newY = currentY+dy;

                    RectF drawingRect = new RectF(newX, newY, newX+currentWidth, newY+currentHeight);
                    float diffUp = Math.min(m_viewRect.bottom-drawingRect.bottom, m_viewRect.top-drawingRect.top);
                    float diffDown = Math.max(m_viewRect.bottom-drawingRect.bottom, m_viewRect.top-drawingRect.top);
                    float diffLeft = Math.min(m_viewRect.left-drawingRect.left, m_viewRect.right-drawingRect.right);
                    float diffRight = Math.max(m_viewRect.left-drawingRect.left, m_viewRect.right-drawingRect.right);
                    if(diffUp > 0)
                    {
                        dy += diffUp;
                    }
                    if(diffDown < 0)
                    {
                        dy += diffDown;
                    }
                    if(diffLeft > 0)
                    {
                        dx += diffLeft;
                    }
                    if(diffRight < 0)
                    {
                        dx += diffRight;
                    }
                    m_matrix.postTranslate(dx, dy);

                    // if image exceed boundary then scroll, otherwise align center                
                     if(currentWidth < myImageView.getWidth())
                    {
                        if(newX < -m_horizontalMargin)
                        {
                            float offset = -newX - (600 - myImageView.getRight());
                            onScroll(m_startEvent, event, offset, 0);
                        }
                        else if (newX + currentWidth > myImageView.getWidth() + m_horizontalMargin)
                        {
                            float offset = -(newX + currentWidth - myImageView.getWidth()) - (0 - myImageView.getLeft());
                            onScroll(m_startEvent, event, offset, 0);
                        }
                        else
                        {
                            onScroll(m_startEvent, event, myImageView.getLeft() - m_horizontalMargin, 0);
                        }
                    }
                    else
                    {
                        if (newX + currentWidth <= myImageView.getWidth())
                        {
                            float offset = -(newX + currentWidth - myImageView.getWidth()) - (600 - myImageView.getRight());
                            onScroll(m_startEvent, event, offset, 0);
                        }
                        else if (newX > 0)
                        {
                            float offset = -newX - (0 - myImageView.getLeft());
                            onScroll(m_startEvent, event, offset, 0);
                        }
                        else
                        {
                            onScroll(m_startEvent, event, myImageView.getLeft() - m_horizontalMargin, 0);
                        }
                    }

                }
                else if (m_mode == ZOOM)
                {
                    float newDist = spacing(event);
                    if (newDist > 10f)
                    {
                        m_matrix.set(m_savedMatrix);
                        float scale = newDist / m_oldDist;
                        // limit zoom
                        m_matrix.getValues(m_matrixValues);
                        float currentScale = m_matrixValues[Matrix.MSCALE_X];
                        if (scale * currentScale > m_maxZoom)
                        {
                            scale = m_maxZoom / currentScale;
                        }
                        else if (scale * currentScale < m_minZoom)
                        {
                            scale = m_minZoom / currentScale;
                        }
                        m_matrix.postScale(scale, scale, m_mid.x, m_mid.y);
                    }
                }
        }
        myImageView.setImageMatrix(m_matrix);
        return true;
    }

    /** Determine the space between the first two fingers */   
    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);
    }

    /** Calculate the mid point of the first two fingers */   
    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);
    }
}

2 則留言:

  1. gr8 job done ...........it is really a helpful document..........thanks

    回覆刪除
  2. Sorry, when I tried it, I find it not stable when scrolling. Can I ask:

    1. what is setting the horizontalMargin and verticalMargin?
    2. what scaleType are the images?
    3. how are the images loaded? via ImageAdapter?

    回覆刪除