开发者

Image zooming/panning inside of a Gallery

I am trying to zoom/pan images in 开发者_运维问答the android gallery widget. The images cover the full screen. Though I can zoom/pan images in the gallery, I am not able to swipe to the next/previous images. Zooming and panning for single image works fine.

I created a TouchImageView which extends ImageView with capability of zooming and panning from Hello Android book. Then I returned this TouchImageView in the getView() method of the Adapter class that returns images to the Gallery.

I found exactly same problem in google groups at http://groups.google.com/group/android-developers/msg/97421179bfc5a3b2 but no replies over there.

Thank you.


I solved this by making a new class derived from Gallery widget class. I called mine ZoomableGallery. I had mine implement a few gesture listeners to handle scaling and double tap for zoom on non multi-touch or pre 2.0 devices.

public class ZoomableGallery extends Gallery implements OnDoubleTapListener, OnGestureListener, OnScaleGestureListener {}

The key is to not have your inner widgets consume the touch events. It might seem like the right idea to create a ZoomablePannableImageView that responds to touch events and zooms in and out. And it seems like a great idea because this reusable component would work well outside of the Gallery as well. However I don't think it can be made to work well. The best way is to create a ZoomableImageView that doesn't handle touch events or set any gesturelisteners, but instead provides an api for setting the scale factor and panning in the X,Y dimensions as regular methods.

Then once you've gone this route you can have your descendant of the Gallery widget handle all the touch events smartly only forwarding the pieces of the touch motion that need to go to the widget. And what I mean by this is if say we are zoomed into the image and we are viewing almost the whole image except 4 pixels are out of screen to the left. If we receive a touch event to scroll the image 8 pixels to the right. Our gallery widget needs to send the image widget a pan 4 pixels right message. And then it needs to also consume 4 pixels of horizontal motion itself. So that not only does the image view reveal it's complete left edge, but then it slides a little to the right possibly revealing the next imageview in the gallery adapter.

The key functions to override in your new class that extends Gallery are:

    @Override
public boolean onTouchEvent(MotionEvent event) {}

and

    @Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
        float distanceY) {}

I don't want to copy and paste my entire implementation because it is messy and hard to understand and it also can display some glitchy behavior under stress. But I don't think that's because of the strategy I employed but simply because I haven't cleaned it up yet.

The key is to remember what mode the user is in (zooming, or panning inside an image, panning the entire gallery)

In the case where the user is panning inside an image here is what my code looks like handling the switch statement inside of the onScroll method:

        case INNERDRAG:
        float unhandledPan = mCurrentPannable.panHorizontally(distanceX);

        // Negative unhandled pan means that we are shifting over to the image to the right

        if (unhandledPan != 0.0f) {
            if (unhandledPan < 0.0f)
                mMode = GALPANRIGHT;
            else
                mMode = GALPANLEFT;

            return super.onScroll(e1,e2,0.0f-unhandledPan,distanceY);

        } else {
            return true;
        }       

In this code mCurrentPannable refers to the view that is currently "selected" by the gallery. Pannable is just an interface that defines panHorizontally and panVertically as functions that do two things: attempt to pan the inner view by so many pixels AND if that amount of pan takes it beyond the edges of what it can pan, it returns the number of pixels that it couldn't handle.

Then the gallery instead of passing the same arguments to super.onScroll it passes only what is left unconsumed by the pan.

I hope this helps.


Here is something that will get you started. It does need some work though, and I'll update this when I get around to it. This code needs Android 2.2 to work, but you can get it to work on earlier devices by getting ScaleGestureDetector out of AOSP.

public class FullGallery extends Gallery implements OnDoubleTapListener, OnGestureListener, OnScaleGestureListener {
static final int NONE = 0;
static final int DRAG = 1;  
int mode = NONE;
String TAG = "Gallery";


private Context c;
private final LayoutInflater mInflater;

private ScaleGestureDetector mScaleDetector;
private GestureDetector mDetector;
private float mScaleFactor = 1.f;

float new_distance_touch, old_distance_touch, init_x, init_y;

Matrix matrix = new Matrix();
Matrix savedMatrix = new Matrix();

PointF mid = new PointF();
PointF start = new PointF();

ImageView imgPicture;

public FullGallery(Context context, AttributeSet attrSet) {
    super(context, attrSet);
    mInflater = LayoutInflater.from(context);
    c = context;
    mDetector = new GestureDetector(c,this);
    mScaleDetector = new ScaleGestureDetector(c, this);
}

private boolean isScrollingLeft(MotionEvent e1, MotionEvent e2){
    return e2.getX() > e1.getX();
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    imgPicture = (ImageView) super.getSelectedView();

    if (mDetector.onTouchEvent(event)) {
        Log.d("onTouchEvent", "--[ MOVEMENT ]--");
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            init_x = event.getX();
            init_y = event.getY();
            midPoint(mid, event);
            savedMatrix.set(matrix);
            start.set(event.getX(), event.getY());
            mode = DRAG;
            break;
        case MotionEvent.ACTION_MOVE:
            if (mode == DRAG) {
                matrix.set(savedMatrix);
                matrix.postTranslate(event.getX() - start.x, event.getY() - start.y);
            }
            break;
        }

        imgPicture = (ImageView) super.getSelectedView();
        imgPicture.setImageMatrix(matrix);

        return true;
    }
    else if(mScaleDetector.onTouchEvent(event)) { // scale detector for zoom
        Log.d("onTouchEvent", "--[ SCALE ]--");
        return true;
    }
    else 
        return false;
}


@Override
public boolean onScale(ScaleGestureDetector detector) {

    mScaleFactor *= detector.getScaleFactor();
    mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));

    if (new_distance_touch > 10f) {
        matrix.set(savedMatrix);
        matrix.postScale(mScaleFactor, mScaleFactor, mid.x, mid.y);
        Log.d("ZOOMMING",matrix.toShortString());
    }
    else {
        matrix.set(savedMatrix);
        matrix.postTranslate(init_x - start.x, init_y - start.y);
        Log.d("PANNING",matrix.toShortString());
    }

    imgPicture.setImageMatrix(matrix);

    imgPicture.invalidate();

    Log.d("MATRIX", matrix.toString());
    return true;
}

@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
    Log.d(TAG, "-- onScaleBegin --");
    matrix = imgPicture.getImageMatrix();
    savedMatrix.set(matrix);
    start.set(init_x, init_y);
    return true;
}

@Override
public void onScaleEnd(ScaleGestureDetector detector) {
    Log.d(TAG, "-- onScaleEnd --");
    old_distance_touch = detector.getPreviousSpan();
    new_distance_touch = detector.getCurrentSpan();

}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    Log.d(TAG, "-- onFling --");

    float velMax = 2500f;
    float velMin = 1000f;
    float velX = Math.abs(velocityX);
    if (velX > velMax) {
      velX = velMax;
    } else if (velX < velMin) {
      velX = velMin;
    }
    velX -= 600;
    int k = 500000;
    int speed = (int) Math.floor(1f / velX * k);
    setAnimationDuration(speed);

    int kEvent;
    if (isScrollingLeft(e1, e2)) {
      kEvent = KeyEvent.KEYCODE_DPAD_LEFT;
    } else {
      kEvent = KeyEvent.KEYCODE_DPAD_RIGHT;
    }
    onKeyDown(kEvent, null);

    return true;
}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
    Log.d(TAG, "-- onScroll --");
    return super.onScroll(e1, e2, distanceX, distanceY);
}




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

@Override
public void onGesture(GestureOverlayView overlay, MotionEvent event) {
    // TODO Auto-generated method stub

}

@Override
public void onGestureCancelled(GestureOverlayView overlay, MotionEvent event) {
    // TODO Auto-generated method stub

}

@Override
public void onGestureEnded(GestureOverlayView overlay, MotionEvent event) {
    // TODO Auto-generated method stub

}

@Override
public void onGestureStarted(GestureOverlayView overlay, MotionEvent event) {
    // TODO Auto-generated method stub

}

@Override
public boolean onDoubleTap(MotionEvent e) {
    // TODO Auto-generated method stub
    return false;
}

@Override
public boolean onDoubleTapEvent(MotionEvent e) {
    // TODO Auto-generated method stub
    return false;
}

@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
    // TODO Auto-generated method stub
    return false;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜