缩放自定义视图组时如何重新定位图像视图?
How to reposition imageview when scaling custom viewgroup?
目标
我目前正在做一个小项目,它有效地做了两件事:
- 使用自定义 ViewGroup 显示 scalable/dragable 图像(效果很好)
- 在图像上显示 1-N markers/pins,使用图像内的坐标(x/y 像素)作为锚点。这不应与 ViewGroup 一起缩放,只能重新定位自身。 (这不太好用)
问题(这个我想不通!)
我无法解决的问题是缩放时标记的定位。启动 Activity 时,我设法将标记放置在设备屏幕上的正确位置(在 ViewGroup 的图像中),但是当我开始缩放 ViewGroup 时,标记似乎使用相同的 x/y 相对坐标作为ViewGroup本身。这意味着标记将离开其 "pinned" 位置的时间越长,我缩放视图的次数就越大。
更多信息
布局组件树,因为这可能是一个悲伤的来源,我目前使用的是:
- 相对布局
- 自定义视图组 (scalable/dragable)
- ImageView
- ImageView(目前只有一个标记)
我正在使用两个矩阵;一个用于 ViewGroup,一个用于标记。我想我需要两个矩阵,以便标记可以独立于 ViewGroup 移动(即使它会遵循很多 ViewGroup 的模式)。
ViewGroup中的代码
// Used to initially position the marker on the screen
public void placeMarker(float x, float y) {
RelativeLayout parent = (RelativeLayout) this.getParent();
mMapMarker = (ImageView) parent.findViewById(R.id.map_marker);
// Set initial placement of marker (redundant if this can be fixed in dispatchDraw)
mMapMarker.setX(x);
mMapMarker.setY(y);
// Setting the marker's matrix to the same initial values as the placement for use when the view translates/scales
mMatrixMarker.postTranslate(x, y);
mMatrixMarker.postScale(1f,1f,mid.x,mid.y);
mMatrixMarker.invert(mMatrixInverseMarker);
}
@Override
protected void dispatchDraw(Canvas canvas) {
// Draw the marker using ImageView.setX/Y
float[] markerValues = new float[9];
mMatrixMarker.getValues(markerValues);
mMapMarker.setX(markerValues[Matrix.MTRANS_X]);
mMapMarker.setY(markerValues[Matrix.MTRANS_Y]);
float[] values = new float[9];
matrix.getValues(values);
canvas.save();
// Translate the canvas for panning and scaling the view
canvas.translate(values[Matrix.MTRANS_X], values[Matrix.MTRANS_Y]);
canvas.scale(values[Matrix.MSCALE_X], values[Matrix.MSCALE_Y]);
super.dispatchDraw(canvas);
canvas.restore();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
...
} else if (mode == ZOOM) {
if (event.getPointerCount() > 1) {
float newDist = spacing(event);
if (newDist > 10f * density) {
matrix.set(savedMatrix);
float scale = (newDist / oldDist);
float[] values = new float[9]; matrix.getValues(values);
if(scale*values[Matrix.MSCALE_X] >= MAX_ZOOM) {
scale = MAX_ZOOM/values[Matrix.MSCALE_X];
}
if(scale*values[Matrix.MSCALE_X] <= MIN_ZOOM) {
scale = MIN_ZOOM/values[Matrix.MSCALE_X];
}
// Save the change in scale to the ViewGroup's matrix
matrix.postScale(scale, scale, mid.x, mid.y);
matrix.invert(matrixInverse);
// Save the same change in scale for the marker (redundant if the scale is not supposed to be different)
mMatrixMarker.set(mSavedMatrixMarker);
mMatrixMarker.postScale(scale, scale, mid.x, mid.y);
mMatrixMarker.invert(mMatrixInverseMarker);
}
} else {
// Save the change in scale to the ViewGroup's matrix
matrix.set(savedMatrix);
float scale = event.getY() / start.y;
matrix.postScale(scale, scale, mid.x, mid.y);
matrix.invert(matrixInverse);
// Save the same change in scale for the marker (redundant if the scale is not supposed to be different)
mMatrixMarker.set(mSavedMatrixMarker);
mMatrixMarker.postScale(scale, scale, mid.x, mid.y);
mMatrixMarker.invert(mMatrixInverseMarker);
}
}
...
}
----编辑#1----
我现在对代码进行了更改,我现在正在使用我认为更合适的矩阵实现,这要感谢 pskinks 提示。我看到我的代码如何因此得到改进,我很感激。
但是,现在发生的情况是,我处于标记正确放置在 ViewGroup 中的状态,并且发生了以下任一情况:
- 标记随视图组缩放一起缩放。这使标记保持在正确的 "pinned" 位置,但也使它与 ViewGroup 一起 shrink/grow,这是我试图避免的。
- 标记不随 ViewGroup 缩放而缩放。这使得标记固定并且错位越多 ViewGroup 缩放。固定标记大小是正确的,但我需要一种方法来 "counteract" 缩放期间的偏移量。
ViewGroup 中的新代码
// Used to initially position the marker on the screen
public void placeMarker(float x, float y) {
RelativeLayout parent = (RelativeLayout) this.getParent();
mMapMarker = (ImageView) parent.findViewById(R.id.map_marker);
mMapMarker.setScaleType(ImageView.ScaleType.MATRIX);
// Setting the marker's matrix to the same initial values as the placement for use when the view translates/scales
mMatrixMarker.postTranslate(x, y);
mMatrixMarker.postScale(1f,1f,mid.x,mid.y);
mMatrixMarker.invert(mMatrixInverseMarker);
}
@Override
protected void dispatchDraw(Canvas canvas) {
// Draw the marker using a Matrix
mMapMarker.setImageMatrix(mMatrixMarker);
canvas.save();
// Translate the canvas for panning and scaling the view
canvas.translate(values[Matrix.MTRANS_X], values[Matrix.MTRANS_Y]);
canvas.scale(values[Matrix.MSCALE_X], values[Matrix.MSCALE_Y]);
super.dispatchDraw(canvas);
canvas.restore();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
...
} else if (mode == ZOOM) {
if (event.getPointerCount() > 1) {
float newDist = spacing(event);
if (newDist > 10f * density) {
matrix.set(savedMatrix);
float scale = (newDist / oldDist);
float[] values = new float[9]; matrix.getValues(values);
if(scale*values[Matrix.MSCALE_X] >= MAX_ZOOM) {
scale = MAX_ZOOM/values[Matrix.MSCALE_X];
}
if(scale*values[Matrix.MSCALE_X] <= MIN_ZOOM) {
scale = MIN_ZOOM/values[Matrix.MSCALE_X];
}
// Save the change in scale to the ViewGroup's matrix
matrix.postScale(scale, scale, mid.x, mid.y);
matrix.invert(matrixInverse);
// If this is included, the marker shrink/grows with the scaling of the ViewGroup
// If this is not included, the marker stays in a fixed position, making it become misaligned during scaling
mMatrixMarker.set(mSavedMatrixMarker);
mMatrixMarker.postScale(scale, scale, mid.x, mid.y);
mMatrixMarker.invert(mMatrixInverseMarker);
}
} else {
// Save the change in scale to the ViewGroup's matrix
matrix.set(savedMatrix);
float scale = event.getY() / start.y;
matrix.postScale(scale, scale, mid.x, mid.y);
matrix.invert(matrixInverse);
// If this is included, the marker shrink/grows with the scaling of the ViewGroup
// If this is not included, the marker stays in a fixed position, making it become misaligned during scaling
mMatrixMarker.set(mSavedMatrixMarker);
mMatrixMarker.postScale(scale, scale, mid.x, mid.y);
mMatrixMarker.invert(mMatrixInverseMarker);
}
}
...
}
我设法解决了这个问题,归结为跟踪标记的初始位置和比例,并在拖动和缩放时重复使用这些值来计算其新位置。
标记的初始位置代码
public void placeMarker(float x, float y) {
RelativeLayout parent = (RelativeLayout) this.getParent();
mMapMarker = (ImageView) parent.findViewById(R.id.map_marker);
mMapMarker.setScaleType(ImageView.ScaleType.MATRIX);
mMatrixMarker.postTranslate(x, y);
mMatrixMarker.postScale(1f,1f,mid.x,mid.y);
mMatrixMarker.invert(mMatrixInverseMarker);
mMatrixMarkerSource.postTranslate(x,y);
mMatrixMarkerSource.postScale(1f,1f,mid.x,mid.y);
mMatrixMarkerSource.invert(mMatrixInverseMarker);
float[] valuesMatrixSource = new float[9];
mMatrixMarkerSource.getValues(valuesMatrixSource);
}
标记初始位置的用法
/*
Draw the requested changes taking pan and zoom into account
*/
@Override
protected void dispatchDraw(Canvas canvas) {
// Keep a reference to the marker's initial location and scale for comparison
float[] valuesMarkerSource = new float[9];
mMatrixMarkerSource.getValues(valuesMarkerSource);
float sx = valuesMarkerSource[Matrix.MTRANS_X];
float sy = valuesMarkerSource[Matrix.MTRANS_Y];
// Keep a reference to the current location and scale of out ViewGroup
float[] values = new float[9];
matrix.getValues(values);
Drawable d = getResources().getDrawable(R.drawable.ic_map_marker_150);
// Adjust marker image position based on its measured size
float vX = ((sx * values[Matrix.MSCALE_X]) + values[Matrix.MTRANS_X]) - (d.getIntrinsicWidth()/2);
float vY = ((sy * values[Matrix.MSCALE_Y]) + values[Matrix.MTRANS_Y]) - (d.getIntrinsicHeight());
mMatrixMarker.reset();
mMatrixMarker.postTranslate(vX, vY);
float[] valuesMarker = new float[9];
mMatrixMarker.getValues(valuesMarker);
if(mMapMarker != null) {
// We want to use a marker, so save the translated matrix to it
mMapMarker.setImageMatrix(mMatrixMarker);
} else {
/*
TODO This is suboptimal. We really should just hide the marker once and not deal with it any more
We don't want to use a marker, so hide it
*/
RelativeLayout parent = (RelativeLayout) this.getParent();
mMapMarker = (ImageView) parent.findViewById(R.id.map_marker);
mMapMarker.setVisibility(ImageView.INVISIBLE);
}
我也把整个项目上传到了GotHub:https://github.com/shellstrom/ffa
目标
我目前正在做一个小项目,它有效地做了两件事:
- 使用自定义 ViewGroup 显示 scalable/dragable 图像(效果很好)
- 在图像上显示 1-N markers/pins,使用图像内的坐标(x/y 像素)作为锚点。这不应与 ViewGroup 一起缩放,只能重新定位自身。 (这不太好用)
问题(这个我想不通!)
我无法解决的问题是缩放时标记的定位。启动 Activity 时,我设法将标记放置在设备屏幕上的正确位置(在 ViewGroup 的图像中),但是当我开始缩放 ViewGroup 时,标记似乎使用相同的 x/y 相对坐标作为ViewGroup本身。这意味着标记将离开其 "pinned" 位置的时间越长,我缩放视图的次数就越大。
更多信息
布局组件树,因为这可能是一个悲伤的来源,我目前使用的是:
- 相对布局
- 自定义视图组 (scalable/dragable)
- ImageView
- ImageView(目前只有一个标记)
- 自定义视图组 (scalable/dragable)
我正在使用两个矩阵;一个用于 ViewGroup,一个用于标记。我想我需要两个矩阵,以便标记可以独立于 ViewGroup 移动(即使它会遵循很多 ViewGroup 的模式)。
ViewGroup中的代码
// Used to initially position the marker on the screen
public void placeMarker(float x, float y) {
RelativeLayout parent = (RelativeLayout) this.getParent();
mMapMarker = (ImageView) parent.findViewById(R.id.map_marker);
// Set initial placement of marker (redundant if this can be fixed in dispatchDraw)
mMapMarker.setX(x);
mMapMarker.setY(y);
// Setting the marker's matrix to the same initial values as the placement for use when the view translates/scales
mMatrixMarker.postTranslate(x, y);
mMatrixMarker.postScale(1f,1f,mid.x,mid.y);
mMatrixMarker.invert(mMatrixInverseMarker);
}
@Override
protected void dispatchDraw(Canvas canvas) {
// Draw the marker using ImageView.setX/Y
float[] markerValues = new float[9];
mMatrixMarker.getValues(markerValues);
mMapMarker.setX(markerValues[Matrix.MTRANS_X]);
mMapMarker.setY(markerValues[Matrix.MTRANS_Y]);
float[] values = new float[9];
matrix.getValues(values);
canvas.save();
// Translate the canvas for panning and scaling the view
canvas.translate(values[Matrix.MTRANS_X], values[Matrix.MTRANS_Y]);
canvas.scale(values[Matrix.MSCALE_X], values[Matrix.MSCALE_Y]);
super.dispatchDraw(canvas);
canvas.restore();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
...
} else if (mode == ZOOM) {
if (event.getPointerCount() > 1) {
float newDist = spacing(event);
if (newDist > 10f * density) {
matrix.set(savedMatrix);
float scale = (newDist / oldDist);
float[] values = new float[9]; matrix.getValues(values);
if(scale*values[Matrix.MSCALE_X] >= MAX_ZOOM) {
scale = MAX_ZOOM/values[Matrix.MSCALE_X];
}
if(scale*values[Matrix.MSCALE_X] <= MIN_ZOOM) {
scale = MIN_ZOOM/values[Matrix.MSCALE_X];
}
// Save the change in scale to the ViewGroup's matrix
matrix.postScale(scale, scale, mid.x, mid.y);
matrix.invert(matrixInverse);
// Save the same change in scale for the marker (redundant if the scale is not supposed to be different)
mMatrixMarker.set(mSavedMatrixMarker);
mMatrixMarker.postScale(scale, scale, mid.x, mid.y);
mMatrixMarker.invert(mMatrixInverseMarker);
}
} else {
// Save the change in scale to the ViewGroup's matrix
matrix.set(savedMatrix);
float scale = event.getY() / start.y;
matrix.postScale(scale, scale, mid.x, mid.y);
matrix.invert(matrixInverse);
// Save the same change in scale for the marker (redundant if the scale is not supposed to be different)
mMatrixMarker.set(mSavedMatrixMarker);
mMatrixMarker.postScale(scale, scale, mid.x, mid.y);
mMatrixMarker.invert(mMatrixInverseMarker);
}
}
...
}
----编辑#1----
我现在对代码进行了更改,我现在正在使用我认为更合适的矩阵实现,这要感谢 pskinks 提示。我看到我的代码如何因此得到改进,我很感激。
但是,现在发生的情况是,我处于标记正确放置在 ViewGroup 中的状态,并且发生了以下任一情况:
- 标记随视图组缩放一起缩放。这使标记保持在正确的 "pinned" 位置,但也使它与 ViewGroup 一起 shrink/grow,这是我试图避免的。
- 标记不随 ViewGroup 缩放而缩放。这使得标记固定并且错位越多 ViewGroup 缩放。固定标记大小是正确的,但我需要一种方法来 "counteract" 缩放期间的偏移量。
ViewGroup 中的新代码
// Used to initially position the marker on the screen
public void placeMarker(float x, float y) {
RelativeLayout parent = (RelativeLayout) this.getParent();
mMapMarker = (ImageView) parent.findViewById(R.id.map_marker);
mMapMarker.setScaleType(ImageView.ScaleType.MATRIX);
// Setting the marker's matrix to the same initial values as the placement for use when the view translates/scales
mMatrixMarker.postTranslate(x, y);
mMatrixMarker.postScale(1f,1f,mid.x,mid.y);
mMatrixMarker.invert(mMatrixInverseMarker);
}
@Override
protected void dispatchDraw(Canvas canvas) {
// Draw the marker using a Matrix
mMapMarker.setImageMatrix(mMatrixMarker);
canvas.save();
// Translate the canvas for panning and scaling the view
canvas.translate(values[Matrix.MTRANS_X], values[Matrix.MTRANS_Y]);
canvas.scale(values[Matrix.MSCALE_X], values[Matrix.MSCALE_Y]);
super.dispatchDraw(canvas);
canvas.restore();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
...
} else if (mode == ZOOM) {
if (event.getPointerCount() > 1) {
float newDist = spacing(event);
if (newDist > 10f * density) {
matrix.set(savedMatrix);
float scale = (newDist / oldDist);
float[] values = new float[9]; matrix.getValues(values);
if(scale*values[Matrix.MSCALE_X] >= MAX_ZOOM) {
scale = MAX_ZOOM/values[Matrix.MSCALE_X];
}
if(scale*values[Matrix.MSCALE_X] <= MIN_ZOOM) {
scale = MIN_ZOOM/values[Matrix.MSCALE_X];
}
// Save the change in scale to the ViewGroup's matrix
matrix.postScale(scale, scale, mid.x, mid.y);
matrix.invert(matrixInverse);
// If this is included, the marker shrink/grows with the scaling of the ViewGroup
// If this is not included, the marker stays in a fixed position, making it become misaligned during scaling
mMatrixMarker.set(mSavedMatrixMarker);
mMatrixMarker.postScale(scale, scale, mid.x, mid.y);
mMatrixMarker.invert(mMatrixInverseMarker);
}
} else {
// Save the change in scale to the ViewGroup's matrix
matrix.set(savedMatrix);
float scale = event.getY() / start.y;
matrix.postScale(scale, scale, mid.x, mid.y);
matrix.invert(matrixInverse);
// If this is included, the marker shrink/grows with the scaling of the ViewGroup
// If this is not included, the marker stays in a fixed position, making it become misaligned during scaling
mMatrixMarker.set(mSavedMatrixMarker);
mMatrixMarker.postScale(scale, scale, mid.x, mid.y);
mMatrixMarker.invert(mMatrixInverseMarker);
}
}
...
}
我设法解决了这个问题,归结为跟踪标记的初始位置和比例,并在拖动和缩放时重复使用这些值来计算其新位置。
标记的初始位置代码
public void placeMarker(float x, float y) {
RelativeLayout parent = (RelativeLayout) this.getParent();
mMapMarker = (ImageView) parent.findViewById(R.id.map_marker);
mMapMarker.setScaleType(ImageView.ScaleType.MATRIX);
mMatrixMarker.postTranslate(x, y);
mMatrixMarker.postScale(1f,1f,mid.x,mid.y);
mMatrixMarker.invert(mMatrixInverseMarker);
mMatrixMarkerSource.postTranslate(x,y);
mMatrixMarkerSource.postScale(1f,1f,mid.x,mid.y);
mMatrixMarkerSource.invert(mMatrixInverseMarker);
float[] valuesMatrixSource = new float[9];
mMatrixMarkerSource.getValues(valuesMatrixSource);
}
标记初始位置的用法
/*
Draw the requested changes taking pan and zoom into account
*/
@Override
protected void dispatchDraw(Canvas canvas) {
// Keep a reference to the marker's initial location and scale for comparison
float[] valuesMarkerSource = new float[9];
mMatrixMarkerSource.getValues(valuesMarkerSource);
float sx = valuesMarkerSource[Matrix.MTRANS_X];
float sy = valuesMarkerSource[Matrix.MTRANS_Y];
// Keep a reference to the current location and scale of out ViewGroup
float[] values = new float[9];
matrix.getValues(values);
Drawable d = getResources().getDrawable(R.drawable.ic_map_marker_150);
// Adjust marker image position based on its measured size
float vX = ((sx * values[Matrix.MSCALE_X]) + values[Matrix.MTRANS_X]) - (d.getIntrinsicWidth()/2);
float vY = ((sy * values[Matrix.MSCALE_Y]) + values[Matrix.MTRANS_Y]) - (d.getIntrinsicHeight());
mMatrixMarker.reset();
mMatrixMarker.postTranslate(vX, vY);
float[] valuesMarker = new float[9];
mMatrixMarker.getValues(valuesMarker);
if(mMapMarker != null) {
// We want to use a marker, so save the translated matrix to it
mMapMarker.setImageMatrix(mMatrixMarker);
} else {
/*
TODO This is suboptimal. We really should just hide the marker once and not deal with it any more
We don't want to use a marker, so hide it
*/
RelativeLayout parent = (RelativeLayout) this.getParent();
mMapMarker = (ImageView) parent.findViewById(R.id.map_marker);
mMapMarker.setVisibility(ImageView.INVISIBLE);
}
我也把整个项目上传到了GotHub:https://github.com/shellstrom/ffa