为啥在捏缩放手势期间调用 setScaleX 会导致闪烁?

Posted

技术标签:

【中文标题】为啥在捏缩放手势期间调用 setScaleX 会导致闪烁?【英文标题】:Why does calling setScaleX during pinch zoom gesture cause flicker?为什么在捏缩放手势期间调用 setScaleX 会导致闪烁? 【发布时间】:2013-06-28 01:12:53 【问题描述】:

我正在尝试创建一个可缩放的容器,并且我的目标是 API 14+

在我的 onScale 中(我使用 ScaleGestureDetector 来检测捏缩放)我正在做这样的事情:

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

   return true;
;

它可以工作,但缩放不平滑。事实上,它明显闪烁。

我还尝试了硬件层,认为一旦纹理上传就会在 GPU 上进行缩放,因此速度会非常快。但这并没有什么不同 - 缩放不流畅,有时会奇怪地闪烁。

我做错了什么?

【问题讨论】:

我不想发布答案,因为我无法测试解决方案,但你可以,所以在这里笑一下:***.com/questions/5790503/…。尝试后请返回此处并告诉我们它是否解决了问题。看看最后的回复。 我知道该解决方案,但它存在严重缺陷 - 它仅缩放画布以进行绘图 - 没有任何剪辑和接触点被转换。这就是为什么我使用 setScale 来更改容器中的变换矩阵并调整剪辑矩形并适当地变换触摸坐标(无需您自己进行矩阵数学运算) 你能分享更多的代码吗?特别是实际的绘图例程。您是否尝试过任何分析以查看绘图在哪里挣扎?我会考虑在紧密的绘图循环中最小化(可能为零)对象分配的数量。我知道这些是一般(可能很明显)点,但如果没有更多信息,很难说更多。 嘿@numansalati,你能解决这个问题吗?不幸的是,我也面临同样的问题。 @GautamM。对不起,不得不继续做其他事情。如果你发现了什么,请分享它。 【参考方案1】:

闪烁是否看起来像是在缩放和未缩放之间来回翻转的视图?这是由 ScaleGestureDetector 从您正在缩放的​​同一视图中处理运动事件引起的。当您setScaleX() 更改触摸的坐标时,会触发一个新的触摸事件,该事件被解释为反转您刚刚应用的缩放。

将可缩放视图的内容放在单个子视图 FrameLayout 中,然后在该视图上设置比例。

我在这里发布了一个有效的捏缩放布局:https://gist.github.com/anorth/9845602

【讨论】:

有没有别的去,具体原因是什么?我们可以在不增加应用程序层次结构的情况下以不同的方式解决吗? @亚历克斯【参考方案2】:

根据您的问题,setScaleXsetScaleY 方法闪烁,因为它们在“绘制到视图”上下文中执行(setScaleXsetScaleY 属于 View)。文档说:

当您想要绘制不需要动态更改且不属于性能密集型游戏的简单图形时,选项“a”绘制到视图是您的最佳选择。例如,当您想在其他静态应用程序中显示静态图形或预定义动画时,您应该将图形绘制到视图中。

另一方面,如果您遵循@g00dy 的答案,您将在“绘制到画布”上下文中执行(scale() 属于Canvas)。文档说:

当您的应用程序需要定期重新绘制自身时,选项“b”会更好地绘制到 Canvas。视频游戏等应用程序应自行绘制到画布上。

捏合手势很密集,因此需要在选项 b 中执行...所以您应该使用@g00dy 解决方案或任何类似的方法。

Here 是引用的文档。

希望对你有帮助。

【讨论】:

setScaleX(或 alpha 或 tranx/y)通过不创建重新创建显示列表进行优化,因为它需要做的就是使父显示列表无效。其次,使用层应该在 GPU 上进行缩放。因此,无论哪种方式,理论上它都应该比重绘每一帧(相当于重新创建 DL)更快。 你也看过我对gOOd评论的评论了吗?该解决方案是伪造的,因为我对仅显示缩放(这是微不足道的问题)并不感兴趣,而是创建一个通用缩放容器(更难的问题),其中所有孩子都有适当的剪辑和触摸点转换(以及显示缩放)。我可以做到这一点,我完全控制了容器的矩阵操作,但我想避免这种情况,因为这首先是 scaleX 和 scaleY 属性(在 api 11+ 中引入) 嗯...首先,您的问题是“为什么闪烁”,然后您就有了答案,实际上我不在乎您要做什么。如果您不喜欢@g00dy 解决方案,正如我之前所说,您可以使用任何其他类似的方法。其次,没有什么比“在画布上绘图”更快的了,这就是为什么视频游戏公司总是使用画布来动态更新其图形帧的原因。 您错过了问题的重点以及各种方法的细微差别。这不是一个菜鸟问题,但感谢您的尝试。【参考方案3】:

我遇到了同样的问题。任务是:

创建画布(在 View 或 SurfaceView 的子类中,无所谓) 在其上绘制一些图片和图形图元(使用方法 drawBitmap()、drawLine()...) 使画布可滚动和可缩放

我想,这里不需要显示所有的类代码。

缩放手势时闪烁问题的解决方案非常简单。 只需将缩放过程放入 onScaleEnd() 方法即可。

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
     
 

在真实设备三星 GALAXY S III mini 上测试。

【讨论】:

但是如果我想在 onScale 被调用以获得更好的用户体验的同时进行扩展怎么办?

以上是关于为啥在捏缩放手势期间调用 setScaleX 会导致闪烁?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们必须将手势识别器的旋转/缩放设置回 0/1?

UIScrollView 在捏合时自动向下滚动?

为啥我的 watchkit 应用程序没有在“开始”状态调用我的平移手势识别器处理代码?

缩放时更改 ImageView 的大小

为啥 Mac OS X Lion 滚动手势的方向反过来了

在 UIView.animateWithDuration() 期间启用手势识别器的用户交互