9

I am trying to create a zoomable container and I am targeting API 14+

In my onScale (i am using the ScaleGestureDetector to detect pinch-zoom) I am doing something like this:

public boolean onScale (ScaleGestureDetector detector) {
   float scaleFactor = detector.getScaleFactor();
   setScaleX(getScaleX() * scaleFactor);
   setScaleY(getScaleY() * scaleFactor);

   return true;
};

It works but the zoom is not smooth. In fact it noticeably flickers.

I also tried it with hardware layer thinking that the scaling would happen on the GPU once the texture was uploaded and thus would be super fast. But it made no difference - the zoom is not smooth and flickers weirdly sometimes.

What am I doing wrong?

numan salati
  • 19,394
  • 9
  • 63
  • 66
  • I don't want to post an answer, because I can't test the solution, but you can, so take a lokk here: http://stackoverflow.com/questions/5790503/can-we-use-scale-gesture-detector-for-pinch-zoom-in-android . After you try it please return back here and tell us if it solved the issue. Take a look at the last reply. – g00dy Jul 03 '13 at 06:55
  • 1
    i am aware of that solution but it has critical flaws - it only scales the canvas for drawing - none of the clips and touch points are transformed. which is why i used setScale which changes the transform matrix at container and adjusts the clip rects and transforms touch coordinates appropriately (without you having to do the matrix math yourself) – numan salati Jul 03 '13 at 15:25
  • 1
    Can you share a bit more of the code? Specifically the actual drawing routines. Have you tried any profiling to see where the drawing is struggling? I'd look at minimizing (possibly to zero) the amount of object allocations in tight drawing loops. I know these are general (maybe obvious) points, but it's hard to say more without further info. – snowdragon Jul 09 '13 at 08:52
  • Hey @numansalati , were you able to solve this problem? I am unfortunately facing the same. – Gautam Mandsorwale Feb 11 '14 at 13:35
  • @GautamM. sorry, had to move on to other things. if you find something do share it. – numan salati Feb 11 '14 at 16:57

3 Answers3

9

Does the flicker look like the view flipping back and forth between zoomed and not-zoomed? That's caused by having the ScaleGestureDetector handling motion events from the same view that you're scaling. When you setScaleX() that changes the coordinates of the touch, which triggers a new touch event interpreted as reversing the zoom you just applied.

Put the content of your zoomable view inside a single child FrameLayout and set the scale on that view instead.

I've posted a working pinch-zoom layout here: https://gist.github.com/anorth/9845602

Alex North
  • 1,189
  • 13
  • 12
  • is there no other go what is the specific reason ? can we resolve in a different way without increasing the hierarchy of the Application? @Alex – Anmol Mar 28 '19 at 13:09
0

According with your question, the methods setScaleX and setScaleY flicker because they perform inside the "drawing to a View" context (setScaleX and setScaleY belong to View). Documentation says:

Option "a," drawing to a View, is your best choice when you want to draw simple graphics that do not need to change dynamically and are not part of a performance-intensive game. For example, you should draw your graphics into a View when you want to display a static graphic or predefined animation, within an otherwise static application.

On the other hand, if you follow the @g00dy answer, you will be performing inside the "drawing to a Canvas" context (scale() belongs to Canvas). Documentation says:

Option "b," drawing to a Canvas, is better when your application needs to regularly re-draw itself. Applications such as video games should be drawing to the Canvas on its own.

A pinch gesture is intensive, so it needs to be performed in Option b... so you should use the @g00dy solution or any approach like that.

Here is the documentation cited.

I hope it helps you.

Jorge Gil
  • 4,265
  • 5
  • 38
  • 57
  • 1
    setScaleX (or alpha or tranx/y) are optimized by not creating the recreating the display lists because all it needs to do is invalidate the display list of the parent. and secondly using layers it is supposed to do the scaling on the GPU. So either way in theory it should be faster than redrawing every frame (which is equivalent to recreating the DL). – numan salati Jul 06 '13 at 00:19
  • 1
    also did you read my comment to gOOd's comment? that solution is faking it because i am not interesting in just showing zoom (which is trivial problem) but creating a generic zoom container (way harder problem) where all children have appropriate clips and touch points transformed (along with showing the zooming). I can do this my totally taking control of the container's matrix manipulation but i wanted to avoid that because thats what scaleX and scaleY properties are there in the first place (introduced in api 11+) – numan salati Jul 06 '13 at 00:20
  • Well... first, your question is "why flicker", then you have your answer, actually I don't care what do you want to do. And if you don't like the @g00dy solution, as I said before, you can use any other approach like that. Second, Nothing is faster that "drawing to a Canvas", that's the reason why videogame companies always use the canvas to update dynamically its graphic frames. – Jorge Gil Jul 06 '13 at 00:28
  • 1
    you are missing the point of the question by a mile and the nuances of the various approaches. this is not a noob question but thanks for trying. – numan salati Jul 06 '13 at 00:38
0

I faced the same problem. The task was:

  • create canvas (in subclass of View or SurfaceView, doesn't matter)
  • draw on it some picture and graphic primitives (with methods drawBitmap(), drawLine()...)
  • make canvas scrollable and scallable

I suppose, no need to show all the class code here.

The solution of the problem with flickering during scalling gesture was very simple. Just put the scaling procedure into the onScaleEnd() method.

private class MyScaleGestureListener implements OnScaleGestureListener
{
    public boolean onScale(ScaleGestureDetector detector)
    {   
        scaleFactor *= detector.getScaleFactor();  // class variable of type float
        if (scaleFactor > 5) scaleFactor = 5;      // some limitations
        if (scaleFactor < 1) scaleFactor = 1;
        return true;
    }
    public boolean onScaleBegin(ScaleGestureDetector detector)
    { return true;}

    public void onScaleEnd(ScaleGestureDetector detector) 
     {
       setScaleX(scaleFactor); setScaleY(scaleFactor); 
       invalidate();     // it seems to me - no effect
     }
} 

Tested on real device Samsung GALAXY S III mini.