Skip to content Skip to sidebar Skip to footer

Pinch-zoom On A Listview

I have a ListView which contains a few TextView's arranged using a custom adapter. What I would like to do, is implement pinch-to-zoom on this ListView, so that when the user pinch

Solution 1:

You should be able to follow the Pinch zoom for custom view and then do something like this when overriding the onTouchEvent. This will allow you to listen for a scale event, as well as all other touch events in the listview.

@OverridepublicbooleanonTouchEvent(MotionEvent ev) {
    // Let the ScaleGestureDetector inspect all events.
    mScaleDetector.onTouchEvent(ev);
    returnsuper.onTouchEvent(ev);
}    

Solution 2:

Yes we can do this.

/**
 * Zooming view.
 */publicclassZoomViewextendsFrameLayout {

/**
 * Zooming view listener interface.
 * 
 * @author karooolek
 * 
 */publicinterfaceZoomViewListener {

    voidonZoomStarted(float zoom, float zoomx, float zoomy);

    voidonZooming(float zoom, float zoomx, float zoomy);

    voidonZoomEnded(float zoom, float zoomx, float zoomy);
}

// zoomingfloatzoom=1.0f;
floatmaxZoom=2.0f;
floatsmoothZoom=1.0f;
float zoomX, zoomY;
float smoothZoomX, smoothZoomY;
privateboolean scrolling; // NOPMD by karooolek on 29.06.11 11:45// minimap variablesprivatebooleanshowMinimap=false;
privateintminiMapColor= Color.BLACK;
privateintminiMapHeight= -1;
private String miniMapCaption;
privatefloatminiMapCaptionSize=10.0f;
privateintminiMapCaptionColor= Color.WHITE;

// touching variablesprivatelong lastTapTime;
privatefloat touchStartX, touchStartY;
privatefloat touchLastX, touchLastY;
privatefloat startd;
privateboolean pinching;
privatefloat lastd;
privatefloat lastdx1, lastdy1;
privatefloat lastdx2, lastdy2;

// drawingprivatefinalMatrixm=newMatrix();
privatefinalPaintp=newPaint();

// listener
ZoomViewListener listener;

private Bitmap ch;

publicZoomView(final Context context) {
    super(context);
}

publicfloatgetZoom() {
    return zoom;
}

publicfloatgetMaxZoom() {
    return maxZoom;
}

publicvoidsetMaxZoom(finalfloat maxZoom) {
    if (maxZoom < 1.0f) {
        return;
    }

    this.maxZoom = maxZoom;
}

publicvoidsetMiniMapEnabled(finalboolean showMiniMap) {
    this.showMinimap = showMiniMap;
}

publicbooleanisMiniMapEnabled() {
    return showMinimap;
}

publicvoidsetMiniMapHeight(finalint miniMapHeight) {
    if (miniMapHeight < 0) {
        return;
    }
    this.miniMapHeight = miniMapHeight;
}

publicintgetMiniMapHeight() {
    return miniMapHeight;
}

publicvoidsetMiniMapColor(finalint color) {
    miniMapColor = color;
}

publicintgetMiniMapColor() {
    return miniMapColor;
}

public String getMiniMapCaption() {
    return miniMapCaption;
}

publicvoidsetMiniMapCaption(final String miniMapCaption) {
    this.miniMapCaption = miniMapCaption;
}

publicfloatgetMiniMapCaptionSize() {
    return miniMapCaptionSize;
}

publicvoidsetMiniMapCaptionSize(finalfloat size) {
    miniMapCaptionSize = size;
}

publicintgetMiniMapCaptionColor() {
    return miniMapCaptionColor;
}

publicvoidsetMiniMapCaptionColor(finalint color) {
    miniMapCaptionColor = color;
}

publicvoidzoomTo(finalfloat zoom, finalfloat x, finalfloat y) {
    this.zoom = Math.min(zoom, maxZoom);
    zoomX = x;
    zoomY = y;
    smoothZoomTo(this.zoom, x, y);
}

publicvoidsmoothZoomTo(finalfloat zoom, finalfloat x, finalfloat y) {
    smoothZoom = clamp(1.0f, zoom, maxZoom);
    smoothZoomX = x;
    smoothZoomY = y;
    if (listener != null) {
        listener.onZoomStarted(smoothZoom, x, y);
    }
}

public ZoomViewListener getListener() {
    return listener;
}

publicvoidsetListner(final ZoomViewListener listener) {
    this.listener = listener;
}

publicfloatgetZoomFocusX() {
    return zoomX * zoom;
}

publicfloatgetZoomFocusY() {
    return zoomY * zoom;
}

@OverridepublicbooleandispatchTouchEvent(final MotionEvent ev) {
    // single touchif (ev.getPointerCount() == 1) {
        processSingleTouchEvent(ev);
    }

    // // double touchif (ev.getPointerCount() == 2) {
        processDoubleTouchEvent(ev);
    }

    // redraw
    getRootView().invalidate();
    invalidate();

    returntrue;
}

privatevoidprocessSingleTouchEvent(final MotionEvent ev) {

    finalfloatx= ev.getX();
    finalfloaty= ev.getY();

    finalfloatw= miniMapHeight * (float) getWidth() / getHeight();
    finalfloath= miniMapHeight;
    finalbooleantouchingMiniMap= x >= 10.0f && x <= 10.0f + w
            && y >= 10.0f && y <= 10.0f + h;

    if (showMinimap && smoothZoom > 1.0f && touchingMiniMap) {
        processSingleTouchOnMinimap(ev);
    } else {
        processSingleTouchOutsideMinimap(ev);
    }
}

privatevoidprocessSingleTouchOnMinimap(final MotionEvent ev) {
    finalfloatx= ev.getX();
    finalfloaty= ev.getY();

    finalfloatw= miniMapHeight * (float) getWidth() / getHeight();
    finalfloath= miniMapHeight;
    finalfloatzx= (x - 10.0f) / w * getWidth();
    finalfloatzy= (y - 10.0f) / h * getHeight();
    smoothZoomTo(smoothZoom, zx, zy);
}

privatevoidprocessSingleTouchOutsideMinimap(final MotionEvent ev) {
    finalfloatx= ev.getX();
    finalfloaty= ev.getY();
    floatlx= x - touchStartX;
    floatly= y - touchStartY;
    finalfloatl= (float) Math.hypot(lx, ly);
    floatdx= x - touchLastX;
    floatdy= y - touchLastY;
    touchLastX = x;
    touchLastY = y;

    switch (ev.getAction()) {
    case MotionEvent.ACTION_DOWN:
        touchStartX = x;
        touchStartY = y;
        touchLastX = x;
        touchLastY = y;
        dx = 0;
        dy = 0;
        lx = 0;
        ly = 0;
        scrolling = false;
        break;

    case MotionEvent.ACTION_MOVE:
        if (scrolling || (smoothZoom > 1.0f && l > 30.0f)) {
            if (!scrolling) {
                scrolling = true;
                ev.setAction(MotionEvent.ACTION_CANCEL);
                super.dispatchTouchEvent(ev);
            }
            smoothZoomX -= dx / zoom;
            smoothZoomY -= dy / zoom;
            return;
        }
        break;

    case MotionEvent.ACTION_OUTSIDE:
    case MotionEvent.ACTION_UP:

        // tapif (l < 30.0f) {
            // check double tapif (System.currentTimeMillis() - lastTapTime < 500) {
                if (smoothZoom == 1.0f) {
                    smoothZoomTo(maxZoom, x, y);
                } else {
                    smoothZoomTo(1.0f, getWidth() / 2.0f,
                            getHeight() / 2.0f);
                }
                lastTapTime = 0;
                ev.setAction(MotionEvent.ACTION_CANCEL);
                super.dispatchTouchEvent(ev);
                return;
            }

            lastTapTime = System.currentTimeMillis();

            performClick();
        }
        break;

    default:
        break;
    }

    ev.setLocation(zoomX + (x - 0.5f * getWidth()) / zoom, zoomY
            + (y - 0.5f * getHeight()) / zoom);

    ev.getX();
    ev.getY();

    super.dispatchTouchEvent(ev);
}

privatevoidprocessDoubleTouchEvent(final MotionEvent ev) {
    finalfloatx1= ev.getX(0);
    finalfloatdx1= x1 - lastdx1;
    lastdx1 = x1;
    finalfloaty1= ev.getY(0);
    finalfloatdy1= y1 - lastdy1;
    lastdy1 = y1;
    finalfloatx2= ev.getX(1);
    finalfloatdx2= x2 - lastdx2;
    lastdx2 = x2;
    finalfloaty2= ev.getY(1);
    finalfloatdy2= y2 - lastdy2;
    lastdy2 = y2;

    // pointers distancefinalfloatd= (float) Math.hypot(x2 - x1, y2 - y1);
    finalfloatdd= d - lastd;
    lastd = d;
    finalfloatld= Math.abs(d - startd);

    Math.atan2(y2 - y1, x2 - x1);
    switch (ev.getAction()) {
    case MotionEvent.ACTION_DOWN:
        startd = d;
        pinching = false;
        break;

    case MotionEvent.ACTION_MOVE:
        if (pinching || ld > 30.0f) {
            pinching = true;
            finalfloatdxk=0.5f * (dx1 + dx2);
            finalfloatdyk=0.5f * (dy1 + dy2);
            smoothZoomTo(Math.max(1.0f, zoom * d / (d - dd)), zoomX - dxk
                    / zoom, zoomY - dyk / zoom);
        }

        break;

    case MotionEvent.ACTION_UP:
    default:
        pinching = false;
        break;
    }

    ev.setAction(MotionEvent.ACTION_CANCEL);
    super.dispatchTouchEvent(ev);
}

privatefloatclamp(finalfloat min, finalfloat value, finalfloat max) {
    return Math.max(min, Math.min(value, max));
}

privatefloatlerp(finalfloat a, finalfloat b, finalfloat k) {
    return a + (b - a) * k;
}

privatefloatbias(finalfloat a, finalfloat b, finalfloat k) {
    return Math.abs(b - a) >= k ? a + k * Math.signum(b - a) : b;
}

@OverrideprotectedvoiddispatchDraw(final Canvas canvas) {
    // do zoom
    zoom = lerp(bias(zoom, smoothZoom, 0.05f), smoothZoom, 0.2f);
    smoothZoomX = clamp(0.5f * getWidth() / smoothZoom, smoothZoomX,
            getWidth() - 0.5f * getWidth() / smoothZoom);
    smoothZoomY = clamp(0.5f * getHeight() / smoothZoom, smoothZoomY,
            getHeight() - 0.5f * getHeight() / smoothZoom);

    zoomX = lerp(bias(zoomX, smoothZoomX, 0.1f), smoothZoomX, 0.35f);
    zoomY = lerp(bias(zoomY, smoothZoomY, 0.1f), smoothZoomY, 0.35f);
    if (zoom != smoothZoom && listener != null) {
        listener.onZooming(zoom, zoomX, zoomY);
    }

    finalbooleananimating= Math.abs(zoom - smoothZoom) > 0.0000001f
            || Math.abs(zoomX - smoothZoomX) > 0.0000001f
            || Math.abs(zoomY - smoothZoomY) > 0.0000001f;

    // nothing to drawif (getChildCount() == 0) {
        return;
    }

    // prepare matrix
    m.setTranslate(0.5f * getWidth(), 0.5f * getHeight());
    m.preScale(zoom, zoom);
    m.preTranslate(
            -clamp(0.5f * getWidth() / zoom, zoomX, getWidth() - 0.5f
                    * getWidth() / zoom),
            -clamp(0.5f * getHeight() / zoom, zoomY, getHeight() - 0.5f
                    * getHeight() / zoom));

    // get viewfinalViewv= getChildAt(0);
    m.preTranslate(v.getLeft(), v.getTop());

    // get drawing cache if availableif (animating && ch == null && isAnimationCacheEnabled()) {
        v.setDrawingCacheEnabled(true);
        ch = v.getDrawingCache();
    }

    // draw using cache while animatingif (animating && isAnimationCacheEnabled() && ch != null) {
        p.setColor(0xffffffff);
        canvas.drawBitmap(ch, m, p);
    } else { // zoomed or cache unavailable
        ch = null;
        canvas.save();
        canvas.concat(m);
        v.draw(canvas);
        canvas.restore();
    }

    // draw minimapif (showMinimap) {
        if (miniMapHeight < 0) {
            miniMapHeight = getHeight() / 4;
        }

        canvas.translate(10.0f, 10.0f);

        p.setColor(0x80000000 | 0x00ffffff & miniMapColor);
        finalfloatw= miniMapHeight * (float) getWidth() / getHeight();
        finalfloath= miniMapHeight;
        canvas.drawRect(0.0f, 0.0f, w, h, p);

        if (miniMapCaption != null && miniMapCaption.length() > 0) {
            p.setTextSize(miniMapCaptionSize);
            p.setColor(miniMapCaptionColor);
            p.setAntiAlias(true);
            canvas.drawText(miniMapCaption, 10.0f,
                    10.0f + miniMapCaptionSize, p);
            p.setAntiAlias(false);
        }

        p.setColor(0x80000000 | 0x00ffffff & miniMapColor);
        finalfloatdx= w * zoomX / getWidth();
        finalfloatdy= h * zoomY / getHeight();
        canvas.drawRect(dx - 0.5f * w / zoom, dy - 0.5f * h / zoom, dx
                + 0.5f * w / zoom, dy + 0.5f * h / zoom, p);

        canvas.translate(-10.0f, -10.0f);
    }

    // redraw// if (animating) {
    getRootView().invalidate();
    invalidate();
    // }
}
}

when you want to use it make your listview dynamically and add it to zoomview like this.

bodyContainer = (LinearLayout) findViewById(R.id.container);

    lvDateList = newListView(ListDateActivity.this);
    lvDateList.setLayoutParams(newLinearLayout.LayoutParams(
            LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

    ZoomViewzoomView=newZoomView(ListDateActivity.this);
    zoomView.setLayoutParams(newLinearLayout.LayoutParams(
            LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    zoomView.addView(lvDateList);

    container.addView(zoomView);

Post a Comment for "Pinch-zoom On A Listview"