Goal
I am currently working on a small project that effectively does to two things:
Problem (this is what I can't figure out!)
What I cannot make work, is positioning of the marker while scaling. I have managed to place the marker at the right place on the device's screen (in the image of the ViewGroup) when launching the Activity, but when I start scaling the ViewGroup, the marker seem to be using the same x/y relative coordinates as the ViewGroup itself. This means that the marker will move longer off its "pinned" place the more I scale the view.
More information
The layout component tree, since that may be a possible source of grief, I'm currently using is:
I'm using two matrices; one for the ViewGroup and one for the marker. I think I need two matrices so that the marker can move independently of the ViewGroup (even if it would follow a lot of the ViewGroup's patterns).
Code in the 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);
}
}
...
}
----EDIT #1----
I have now made changes to the code, in which I am now using what I believe is a more proper matrix implementation, thanks to pskinks tip. I see how my code has improved by it and I appreciate that.
However, what happens now is that I'm in a state where the marker is placed properly in the ViewGroup and either of the following things happen:
New code in the 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);
}
}
...
}
I managed to figure this out, and it came down to keeping track of the marker's initial position and scale and reuse those values to calculate its new position when dragging and scaling.
The marker's initial position code
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);
}
The usage of the marker's initial position
/*
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);
}
I also uploaded the whole project to GotHub: https://github.com/shellstrom/ffa