Why does calling setScaleX during pinch zoom gesture cause flicker?
Asked Answered
A

3

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?

Adda answered 30/6, 2013 at 22:59 Comment(5)
I don't want to post an answer, because I can't test the solution, but you can, so take a lokk here: #5791003 . After you try it please return back here and tell us if it solved the issue. Take a look at the last reply.Snowbound
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)Adda
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.Crawl
Hey @numansalati , were you able to solve this problem? I am unfortunately facing the same.Sylas
@GautamM. sorry, had to move on to other things. if you find something do share it.Adda
R
10

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

Rush answered 29/3, 2014 at 0:7 Comment(1)
is there no other go what is the specific reason ? can we resolve in a different way without increasing the hierarchy of the Application? @AlexOrvah
W
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.

Waterbuck answered 5/7, 2013 at 23:40 Comment(4)
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).Adda
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+)Adda
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 @Snowbound 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.Waterbuck
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.Adda
C
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.

Correia answered 27/9, 2014 at 20:53 Comment(1)
But what if i want to scale even while onScale is called for better UX?Orvah

© 2022 - 2024 — McMap. All rights reserved.