hackbod 的出色回答提醒我,我需要发布我最终得出的解决方案。请注意,这个解决方案适用于我当时正在做的应用程序,可以通过 hackbod 的建议进一步改进。特别是我不需要处理触摸事件,并且在阅读 hackbod 的帖子之前,我没有想到如果我这样做了,那么我也需要扩展这些事件。
回顾一下,对于我的应用程序,我需要实现的是有一个大图(特别是建筑物的楼层布局),上面叠加了其他小的“标记”符号。背景图和前景符号都是使用矢量图形绘制的(即在 onDraw() 方法中应用于 Canvas 的 Path() 和 Paint() 对象)。想要以这种方式创建所有图形而不是仅使用位图资源的原因是因为图形是在运行时使用我的 SVG 图像转换器进行转换的。
要求是图表和相关的标记符号都将是 ViewGroup 的子项,并且都可以一起缩放。
很多代码看起来很乱(这是一个演示的匆忙工作),所以我不只是将它们全部复制进去,而是尝试解释我是如何使用引用的相关代码位来完成它的。
首先,我有一个 ZoomableRelativeLayout。
public class ZoomableRelativeLayout extends RelativeLayout { ...
此类包括扩展 ScaleGestureDetector 和 SimpleGestureListener 的侦听器类,以便可以平移和缩放布局。这里特别感兴趣的是缩放手势侦听器,它设置缩放因子变量,然后调用 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。相反,每个子 View 在通过 getter 方法从父 ZoomableRelativeLayout 获取缩放因子后缩放其 Canvas。
接下来,我将继续我在 ZoomableRelativeLayout 的子视图中必须做的事情。我在 ZoomableRelativeLayout 中只包含一种类型的 View 作为子项;这是一个用于绘制 SVG 图形的视图,我称之为 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 的建议并将它们纳入其中。我打算回来进一步编辑。同时,我希望它可以开始为其他人提供有关如何实现可缩放 ViewGroup 的有用指针。