I'm using TouchImageView from github and using matrix.posttranslate to handle user control of dragging zoom image. Problem is I need to know when user drag to border of image, so image can't move horizontally any more. Has anyone have ideal about that?
If you are not rotating your Matrix then second and fifth elements are translateX and translateY:
final Matrix matrix = ...;
final float[] values = new float[9];
matrix.getValues(values);
final float translateX = values[2];
final float translateY = values[5];
Thanks. And after deeply read sourcecode of gallery on github, i got solution. To check whether swipe to right border or not:
float scaledWidth = Math.round(mOrigWidth * mSaveScale);
if (Math.abs(mViewWidth - translateX - scaledWidth) <= 1.0) {
// drag to right border
ALog.d("touchimageview", "right x: "
+ (mViewWidth - translateX - scaledWidth));
touchEventListener.onZoomToOriginal();
break;
}
Related
i think this question has been asked quite often, but I couldn`t find an appropriate solution for my implementation. I built an custom imageview with an onScaleListener and an onGestureListener that scales and pans the containing image. The scaling is done with a matrix scaling. The function looks like that:
#Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
scaleFactor *= scaleGestureDetector.getScaleFactor();
scaleFactor = Math.max(initScale, Math.min(scaleFactor, initScale + 3.0f));
matrix = getImageMatrix();
matrix.setScale(scaleFactor, scaleFactor);
matrix.getValues(values);
matrix.postTranslate(-values[Matrix.MTRANS_X] + Math.max(0, centerX - centerImageX),
-values[Matrix.MTRANS_Y] + Math.max(0, centerY - centerImageY));
setImageMatrix(matrix);
}
postTranslate() is needed to center the image if needed. To finish this I need to scroll to (scrollTo(x,y)) the position where the focus of the scaling gesture stays in the same position on the screen. At the end it should look like scaling in a webview.
Can anybody help me with this?
When I use:
float scrollPosX = ((scrollwidth) * ((getScrollX() + touchX) / imagewidth));
float scrollPosY = ((scrollheight) * ((getScrollY() + touchY) / imageheight));
it will work for the first scaling, but when scaling in a scaled image it will scroll to the relative position. I think it is all related to the fact that I only get the touch position with getScrollX() and getScrollY() [it`s difficult to explain]
Now I found a solution that works for me. In my case I have to use the scaling step instead of the scaling factor.
scrollTo((int) (getScrollX() - x + (scaleGestureDetector.getScaleFactor() * x) ),
(int) (getScrollY() - y + (scaleGestureDetector.getScaleFactor() * y) ));
with x and y as the touch position on the scaled image.
I'm using a custom ImageView to display a rather large bitmap. The bitmap display is being handled by a matrix that transforms it to what the user sees. I'm trying to implement a "double-tap-to-zoom" but I can't quite get it right. I'm trying to have the image zoom on the point where the user touched with this point ending up in the center of the screen at the end.
I worked through a lot of the matrix math and transformations and basically the following transformation is what I need to do
float dx = centerX - focusX;
float dy = centerY - focusY;
Matrix m = new Matrix( baseMatrix );
m.postTranslate( -focusX, -focusY );
m.postScale( scale, scale );
m.postTranslate( focusX + dx, focusY + dy );
Which if I was just swapping the matrices would be fine but I need to animate from the baseMatrix to this new one. Is there a way I can interpolate between these two matrices?
I tried to interpolate the scale and translation separately but that didn't work out well for me (quite possible that I did it wrong and it is the correct way to go). The way I am currently interpolating just for the scale is below. I've tried adding a translation interpolation in the handler as well and it just didn't work out
mHandler.post( new Runnable() {
#Override
public void run() {
mZooming = true;
long now = System.currentTimeMillis();
float currentMs = Math.min( durationMs, now - startTime );
float newScale = (float) mEasing.easeInOut( currentMs, 0, deltaScale, durationMs );
zoomTo( oldScale + newScale, destX, destY );
if ( currentMs < durationMs ) {
mHandler.post( this );
} else {
onZoomAnimationCompleted( getScale() );
scrollBy( dx, dy, durationMs )
}
}
});
Has anyone done something like this before? Am I approaching it completely wrong?
Thanks in advance
I have a background image as a drawable in my custom view. This drawable may be pinch zoomed or moved.
Currently I need a green dot that is drawn on the image to be stationary relative to the screen. That is, it should be always at the same position with the pin as shown below. (Of course, the pin is simply an ImageView and does NOT move at all!)
I have successfully made it stationary relative to the screen, when the map behind is moved as follows in my custom view, MapView:
#Override
public boolean onTouchEvent(MotionEvent ev) {
// Let the ScaleGestureDetector inspect all events.
mScaleDetector.onTouchEvent(ev);
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
mLastTouchX = x;
mLastTouchY = y;
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE: { // triggered as long as finger movers
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
// Only move if the ScaleGestureDetector isn't processing a gesture.
if (!mScaleDetector.isInProgress()) {
final float dx = x - mLastTouchX;
final float dy = y - mLastTouchY;
mPosX += dx;
mPosY += dy;
// update the starting point if the 'Start' button is not yet pressed
// to ensure the screen center (i.e. the pin) is always the starting point
if (!isStarted) {
Constant.setInitialX(Constant.INITIAL_X - dx);
Constant.setInitialY(Constant.INITIAL_Y - dy);
if ((historyXSeries.size() > 0) && (historyYSeries.size() > 0)) {
// new initial starting point
historyXSeries.set(0, Constant.INITIAL_X);
historyYSeries.set(0, Constant.INITIAL_Y);
}
}
invalidate();
}
mLastTouchX = x;
mLastTouchY = y;
break;
}
By doing that above, my green dot stays there, when the background image is moved.
But I have problems in trying to make it stay there, when the background image is zoomed.
Essentially, I don't really understand how canvas.scale(mScaleFactor, mScaleFactor) works, and therefore I cannot move the green dot accordingly like what I have done in the simple moving case.
I think something should be added in the scale listener handler below, could anybody help me fill that part?
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor();
// Don't let the object get too small or too large.
mScaleFactor = Math.max(1f, Math.min(mScaleFactor, 10.0f)); // 1 ~ 10
// HOW TO MOVE THE GREEN DOT HERE??
invalidate();
return true;
}
Or please at least explain how canvas.scale(mScaleFactor, mScaleFactor) works, and how may I move the green dot accordingly?
Keep in mind that the canvas is thought to scale everything according to the scale factor, so while going against the zoom is possible, it is probably not the best approach. However, if this is what you're looking for, I will help you as best as I can.
I am assuming the following:
Scale factor is relative to the current zoom (old zoom is always scale factor 1). If this is not the case, then you should observe the zoom values after scaling roughly 200% two times and seeing if the resulting scale factor is 4 or 3 (exponential or linear). You can achieve the results below by normalizing the scale factor to 2 for a zoom factor of 200%, for example. You'll have to remember the old scale factor in order to do so.
No rotation is performed
If this is the case then following can be said for a marker with respect to the zoom center.
For every horizonal pixel x away from the zoom center after zoom, its original position could be calculated to be: zoom_center_x + *x* / scale_factor (or alternatively zoom_center_x + (marker_x - zoom_center_x) / scale_factor). In other words, if zoom center is (50, 0) and the marker is (100, 0) with a scale factor of 2, then the x position of the marker prior to the zoom was 50 + (100 - 50) / 2 or 75. Obviously, if the marker is in the same position of the zoom center, then the x position will be the same as the zoom center. Similarly, if the scale is 1, then the x position for the marker will be the same as it is now.
The same can be applied to the y axis.
While I can't know exactly how to set the position of your marker, I would expect the code to look something like:
Point zoomCenter = detector.getZoomCenter();
// Set marker variable here
marker.setX(Math.round(zoomCenter.getX() + ((double)(marker.getX() - zoomCenter.getX())) / mScaleFactor));
marker.setY(Math.round(zoomCenter.getY() + ((double)(marker.getY() - zoomCenter.getY())) / mScaleFactor));
I hope that helps.
I use this method to add a Sprite to the screen randomly.
private void addFace() {
Random rand = new Random();
float x = (int) mCamera.getHeight() + mBallTextureRegion.getHeight();
float minY = mBallTextureRegion.getHeight();
float maxY = (int)(mCamera.getWidth() - mBallTextureRegion.getWidth());
float rangeY = maxY - minY;
float y = rand.nextInt((int)rangeY) + minY;
this.mFaceCount++;
Log.e("Faces: ", "Face" + this.mFaceCount);
Sprite face = null;
Body body = null;
The only problem is i would like for the sprites to be added at the top of the screen(which is the camera) but instead they are added on the side of the screen.
Any suggestions on how to do this?
First of all, the (x,y) coordinates of an entity are on it's top left. So, minY should be 0, or you could just do:
float y = rand.nextFloat(maxY);
You don't need all of these (int) casts, you can delete them.
In order to make to position random, get a random X too:
float maxX = this.mCamera.getWidth() - this.mBallTextureRegion.getWidth();
float x = rand.nextFloat(maxX);
This should work.
On the android screen, the origin of the coordinate system is in the upper left corner.
(0,0)......(1,0)
(0,1)......(1,1)
So if you want something to always spawn at the top of the screen, then Y will need to be 0. Always. the X value can be anything from 0 to the width, randomly. This will place the object at the top, and randomly in the X (left-right) direction
So I have an ImageView using a Matrix to scale the Bitmap I'm displaying. I can double-tap to zoom to full-size, and my ScaleAnimation handles animating the zoom-in, it all works fine.
Now I want to double-tap again to zoom out, but when I animate this with ScaleAnimation, the ImageView does not draw the newly exposed areas of the image (as the current viewport shrinks), instead you see the portion of visible image shrinking in. I have tried using ViewGroup.setClipChildren(false), but this only leaves the last-drawn artifacts from the previous frame - leading to an trippy telescoping effect, but not quite what I was after.
I know there are many zoom-related questions, but none cover my situation - specifically animating the zoom-out operation. I do have the mechanics working - ie aside from the zoom-out animation, double-tapping to zoom in and out works fine.
Any suggestions?
In the end I decided to stop using the Animation classes offered by Android, because the ScaleAnimation applies a scale to the ImageView as a whole which then combines with the scale of the ImageView's image Matrix, making it complicated to work with (aside from the clipping issues I was having).
Since all I really need is to animate the changes made to the ImageView's Matrix, I implemented the OnDoubleTapListener (at the end of this post - I leave it as an "exercise to the reader" to add the missing fields and methods - I use a few PointF and Matrix fields to avoid excess garbage creation). Basically the animation itself is implemented by using View.post to keep posting a Runnable that incrementally changes the ImageView's image Matrix:
public boolean onDoubleTap(MotionEvent e) {
final float x = e.getX();
final float y = e.getY();
matrix.reset();
matrix.set(imageView.getImageMatrix());
matrix.getValues(matrixValues);
matrix.invert(inverseMatrix);
doubleTapImagePoint[0] = x;
doubleTapImagePoint[1] = y;
inverseMatrix.mapPoints(doubleTapImagePoint);
final float scale = matrixValues[Matrix.MSCALE_X];
final float targetScale = scale < 1.0f ? 1.0f : calculateFitToScreenScale();
final float finalX;
final float finalY;
// assumption: if targetScale is less than 1, we're zooming out to fit the screen
if (targetScale < 1.0f) {
// scaling the image to fit the screen, we want the resulting image to be centred. We need to take
// into account the shift that is applied to zoom on the tapped point, easiest way is to reuse
// the transformation matrix.
RectF imageBounds = new RectF(imageView.getDrawable().getBounds());
// set up matrix for target
matrix.reset();
matrix.postTranslate(-doubleTapImagePoint[0], -doubleTapImagePoint[1]);
matrix.postScale(targetScale, targetScale);
matrix.mapRect(imageBounds);
finalX = ((imageView.getWidth() - imageBounds.width()) / 2.0f) - imageBounds.left;
finalY = ((imageView.getHeight() - imageBounds.height()) / 2.0f) - imageBounds.top;
}
// else zoom around the double-tap point
else {
finalX = x;
finalY = y;
}
final Interpolator interpolator = new AccelerateDecelerateInterpolator();
final long startTime = System.currentTimeMillis();
final long duration = 800;
imageView.post(new Runnable() {
#Override
public void run() {
float t = (float) (System.currentTimeMillis() - startTime) / duration;
t = t > 1.0f ? 1.0f : t;
float interpolatedRatio = interpolator.getInterpolation(t);
float tempScale = scale + interpolatedRatio * (targetScale - scale);
float tempX = x + interpolatedRatio * (finalX - x);
float tempY = y + interpolatedRatio * (finalY - y);
matrix.reset();
// translate initialPoint to 0,0 before applying zoom
matrix.postTranslate(-doubleTapImagePoint[0], -doubleTapImagePoint[1]);
// zoom
matrix.postScale(tempScale, tempScale);
// translate back to equivalent point
matrix.postTranslate(tempX, tempY);
imageView.setImageMatrix(matrix);
if (t < 1f) {
imageView.post(this);
}
}
});
return false;
}