扩展RelativeLayout,并重写dispatchDraw()来创build一个可缩放的ViewGroup

我见过一些人问如何一次性缩放整个ViewGroup(比如RelativeLayout)。 目前,这是我需要实现的。 对我而言,最明显的方法是将缩放比例因子作为单个variables保存在某处; 并在每个子视图的onDraw()方法中,绘制graphics之前,该比例因子将应用于Canvas。

然而,在做这件事之前,我试图变聪明(哈 – 通常是一个坏主意),并将RelativeLayout扩展成一个名为ZoomableRelativeLayout的类。 我的想法是任何缩放转换只能在重写的dispatchDraw()函数中应用到Canvas中,因此绝对不需要在任何子视图中单独应用缩放。

这是我在ZoomableRelativeLayout中所做的。 这只是RelativeLayout的一个简单扩展,dispatchDraw()被覆盖:

protected void dispatchDraw(Canvas canvas){ canvas.save(Canvas.MATRIX_SAVE_FLAG); canvas.scale(mScaleFactor, mScaleFactor); super.dispatchDraw(canvas); canvas.restore(); } 

mScaleFactor由同一个类中的ScaleListener操纵。

它确实工作。 我可以捏放大ZoomableRelativeLayout,所有视图保持适当重新调整。

除了有问题。 其中一些孩子的意见是animation,因此我周期性地调用invalidate()。 当比例为1时,那些孩子的观点被认为是周期性重绘的完美。 当比例不等于1时,那些animation儿童视图只能看到在他们的观看区域的一部分中更新 – 或根本不会 – 根据缩放比例。

我最初的想法是,当一个单独的子视图的invalidate()被调用时,它可能会被系统单独重画,而不是从父亲RelativeLayout的dispatchDraw()传递一个Canvas,这意味着子视图最终会刷新自己没有应用缩放比例。 但奇怪的是,在屏幕上重绘的子视图的元素是正确的缩放比例。 这几乎就像系统决定在后台位图中实际更新的区域保持未缩放 – 如果这是有道理的。 换句话说,如果我有一个单独的animation子视图,并且我从初始缩放比例1逐渐放大,并且如果我们在放大比例为1时在该子视图所在的区域放置一个虚拟框,那么对invalidate()的调用只会导致该虚拟框中的graphics刷新。 但是看到更新的graphics正在被正确地完成。 如果放大到目前为止,子视图现在已经完全从1的比例移开,那么根本没有任何部分被刷新。 我将举另一个例子:假设我的孩子的视图是一个通过在黄色和红色之间切换而animation的球。 如果我放大一点,球就会向右下方移动,在某个点上,您只会看到球的左上angular四分之一的animation颜色。

如果我不断地放大和缩小,我就会看到孩子正确完整地看到animation。 这是因为整个ViewGroup正在重绘。

我希望这是有道理的; 我试着尽我所能解释。 我可以用我的可缩放的ViewGroup策略做一个失败者吗? 有另一种方法吗?

谢谢,

崔佛

Solutions Collecting From Web of "扩展RelativeLayout,并重写dispatchDraw()来创build一个可缩放的ViewGroup"

如果您将比例因子应用于孩子的绘图,则还需要将适当的比例因子应用于与其他人的所有其他交互 – 调度触摸事件,无效等。

因此,除了dispatchDraw()之外,还需要重写并适当调整至less这些其他方法的行为。 要照顾无效,您将需要重写此方法来适当调整子坐标:

http://developer.android.com/reference/android/view/ViewGroup.html#invalidateChildInParent(int [],android.graphics.Rect)

如果您希望用户能够与子视图进行交互,则还需要覆盖此设置,以便在将其分派给子项之前适当调整触摸坐标:

http://developer.android.com/reference/android/view/ViewGroup.html#dispatchTouchEvent(android.view.MotionEvent

另外,我强烈build议你在一个简单的ViewGroup子类中实现它,这个子类有一个子视图。 这将消除RelativeLayout在其ViewGroup中引入的任何行为复杂性,从而简化您需要在自己的代码中处理和debugging的内容。 将RelativeLayout作为特殊缩放ViewGroup的子元素。

最后,对代码进行一次改进 – 在dispatchDraw()中,您希望应用缩放因子保存canvas状态。 这可以确保孩子无法修改您设置的转换。

hackbod的优秀回答让我想起了自己最终需要解决的问题。 请注意,这个解决scheme在我当时正在使用的应用程序中工作,可以通过hackbod的build议进一步改进。 特别是我不需要处理触摸事件,直到阅读hackbod的post,我才想到如果我这样做,那么我也需要扩展这些。

总而言之,对于我的应用程序,我需要实现的是有一个大图(具体来说,build筑物的地面布局),其上叠加了其他小的“标记”符号。 背景图和前景符号都是使用vectorgraphics(即在onDraw()方法中应用于Canvas的Path()和Paint()对象)绘制的。 想要以这种方式创build所有graphics的原因,而不是仅仅使用位图资源,是因为使用我的SVG图像转换器在运行时转换graphics。

要求是图表和相关的标记符号都是ViewGroup的子元素,并且都可以捏在一起。

很多代码看起来很乱(这是一个演示急于工作),而不是只是全部复制,而是试着解释我是如何用相关代码引用的。

首先,我有一个ZoomableRelativeLayout。

 public class ZoomableRelativeLayout extends RelativeLayout { ... 

该类包含扩展ScaleGestureDetector和SimpleGestureListener的监听器类,以便可以平移和缩放布局。 这里特别感兴趣的是缩放手势监听器,它设置一个比例因子variables,然后调用invalidate()和requestLayout()。 我现在还不确定是否有必要使用invalidate(),但无论如何 – 这里是:

 private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { @Override public boolean onScale(ScaleGestureDetector detector){ mScaleFactor *= detector.getScaleFactor(); // Apply limits to the zoom scale factor: mScaleFactor = Math.max(0.6f, Math.min(mScaleFactor, 1.5f); invalidate(); requestLayout(); return true; } } 

我在ZoomableRelativeLayout中必须做的下一件事是重写onLayout()。 要做到这一点,我发现看看其他人在可缩放布局的尝试是有用的,而且我发现查看RelativeLayout的原始Android源代码非常有用。 我重写的方法复制了RelativeLayout的onLayout()中的大部分内容,但进行了一些修改。

 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); for(int i=0;i<count;i++){ View child = getChildAt(i); if(child.getVisibility()!=GONE){ RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)child.getLayoutParams(); child.layout( (int)(params.leftMargin * mScaleFactor), (int)(params.topMargin * mScaleFactor), (int)((params.leftMargin + child.getMeasuredWidth()) * mScaleFactor), (int)((params.topMargin + child.getMeasuredHeight()) * mScaleFactor) ); } } } 

这里最重要的是,在所有孩子上调用“layout()”时,我将这个比例因子应用于这些孩子的布局参数。 这是解决裁剪问题的一个步骤,也是重要的正确设置孩子的X,Y位置相对于对方不同的比例因子。

另一个关键的是,我不再试图在dispatchDraw()中缩放Canvas。 相反,每个子视图通过getter方法从父ZoomableRelativeLayout获取缩放因子后缩放它的canvas。

接下来,我将介绍我在ZoomableRelativeLayout的子视图中所做的事情。 在我的ZoomableRelativeLayout中只有一种types的视图被包含在子视图中; 这是一个绘制SVGgraphics的视图,我称之为SVGView。 当然SVG的东西在这里不相关。 这是它的onMeasure()方法:

 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); float parentScale = ((FloorPlanLayout)getParent()).getScaleFactor(); int chosenWidth, chosenHeight; if( parentScale > 1.0f){ chosenWidth = (int) ( parentScale * (float)svgImage.getDocumentWidth() ); chosenHeight = (int) ( parentScale * (float)svgImage.getDocumentHeight() ); } else{ chosenWidth = (int) ( (float)svgImage.getDocumentWidth() ); chosenHeight = (int) ( (float)svgImage.getDocumentHeight() ); } setMeasuredDimension(chosenWidth, chosenHeight); } 

和onDraw():

 @Override protected void onDraw(Canvas canvas){ canvas.save(Canvas.MATRIX_SAVE_FLAG); canvas.scale(((FloorPlanLayout)getParent()).getScaleFactor(), ((FloorPlanLayout)getParent()).getScaleFactor()); if( null==bm || bm.isRecycled() ){ bm = Bitmap.createBitmap( getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888); ... Canvas draw operations go here ... } Paint drawPaint = new Paint(); drawPaint.setAntiAlias(true); drawPaint.setFilterBitmap(true); // Check again that bm isn't null, because sometimes we seem to get // android.graphics.Canvas.throwIfRecycled exception sometimes even though bitmap should // have been drawn above. I'm guessing at the moment that this *might* happen when zooming into // the house layout quite far and the system decides not to draw anything to the bitmap because // a particular child View is out of viewing / clipping bounds - not sure. if( bm != null){ canvas.drawBitmap(bm, 0f, 0f, drawPaint ); } canvas.restore(); } 

再次 – 作为一个免责声明,我可能已经发布了一些瑕疵,我还没有仔细阅读hackbod的build议并加以合并。 我打算再回来编辑一下。 与此同时,我希望能够为如何实现一个可缩放的ViewGroup提供有用的指针。