问题
I am currently developing for Android (my first app) an application which lets users see the subway map and be able to pinch zoom and drag around.
I am currently modifying the code found in Hello Android, 3rd Edition and got the pinch zooming and dragging to work. I\'m using Matrix as my layout scale.
However I now have 3 problems:
I tried many things to limit the drag parameters but I can\'t seem to stop it being dragged off the parent view (and can actually disappear from view). I\'ve tried setting layout parameters in the XML file and it just doesn\'t work.
I can pinch zoom fine but I have trouble, again, limiting the amount of zoom. I\'m trying to play around with setting a max_zoom and min_zoom to limit the scaling value (i will post my code after)
I also have trouble trying to map a coordinate on my image so that people can click on certain parts (the whole point of this is to let users click a station on the map and view information about it)
I have a feeling i\'m having trouble because I\'m using the matrix scale.
Here is my current code:
Touch.java
package org.example.touch;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.os.Bundle;
import android.util.FloatMath;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.GridView;
import android.widget.ImageView;
public class Touch extends Activity implements OnTouchListener {
private static final String TAG = \"Touch\";
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 5.0f;
// These matrices will be used to move and zoom image
Matrix matrix = new Matrix();
Matrix savedMatrix = new Matrix();
// We can be in one of these 3 states
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
int mode = NONE;
// Remember some things for zooming
PointF start = new PointF();
PointF mid = new PointF();
float oldDist = 1f;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageView view = (ImageView) findViewById(R.id.imageView);
//view.setLayoutParams(new GridView.LayoutParams(85, 85));
view.setScaleType(ImageView.ScaleType.FIT_CENTER);
view.setOnTouchListener(this);
}
public boolean onTouch(View v, MotionEvent event) {
ImageView view = (ImageView) v;
view.setScaleType(ImageView.ScaleType.MATRIX);
float scale;
// Dump touch event to log
dumpEvent(event);
// Handle touch events here...
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: //first finger down only
savedMatrix.set(matrix);
start.set(event.getX(), event.getY());
Log.d(TAG, \"mode=DRAG\" );
mode = DRAG;
break;
case MotionEvent.ACTION_UP: //first finger lifted
case MotionEvent.ACTION_POINTER_UP: //second finger lifted
mode = NONE;
Log.d(TAG, \"mode=NONE\" );
break;
case MotionEvent.ACTION_POINTER_DOWN: //second finger down
oldDist = spacing(event);
Log.d(TAG, \"oldDist=\" + oldDist);
if (oldDist > 5f) {
savedMatrix.set(matrix);
midPoint(mid, event);
mode = ZOOM;
Log.d(TAG, \"mode=ZOOM\" );
}
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) { //movement of first finger
matrix.set(savedMatrix);
if (view.getLeft() >= -392){
matrix.postTranslate(event.getX() - start.x, event.getY() - start.y);
}
}
else if (mode == ZOOM) { //pinch zooming
float newDist = spacing(event);
Log.d(TAG, \"newDist=\" + newDist);
if (newDist > 5f) {
matrix.set(savedMatrix);
scale = newDist / oldDist; **//thinking i need to play around with this value to limit it**
matrix.postScale(scale, scale, mid.x, mid.y);
}
}
break;
}
// Perform the transformation
view.setImageMatrix(matrix);
return true; // indicate event was handled
}
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
/** Show an event in the LogCat view, for debugging */
private void dumpEvent(MotionEvent event) {
String names[] = { \"DOWN\" , \"UP\" , \"MOVE\" , \"CANCEL\" , \"OUTSIDE\" ,
\"POINTER_DOWN\" , \"POINTER_UP\" , \"7?\" , \"8?\" , \"9?\" };
StringBuilder sb = new StringBuilder();
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
sb.append(\"event ACTION_\" ).append(names[actionCode]);
if (actionCode == MotionEvent.ACTION_POINTER_DOWN
|| actionCode == MotionEvent.ACTION_POINTER_UP) {
sb.append(\"(pid \" ).append(
action >> MotionEvent.ACTION_POINTER_ID_SHIFT);
sb.append(\")\" );
}
sb.append(\"[\" );
for (int i = 0; i < event.getPointerCount(); i++) {
sb.append(\"#\" ).append(i);
sb.append(\"(pid \" ).append(event.getPointerId(i));
sb.append(\")=\" ).append((int) event.getX(i));
sb.append(\",\" ).append((int) event.getY(i));
if (i + 1 < event.getPointerCount())
sb.append(\";\" );
}
sb.append(\"]\" );
Log.d(TAG, sb.toString());
}
}
main.xml (rather simple nothing really complicated):
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<FrameLayout
xmlns:android=\"http://schemas.android.com/apk/res/android\"
android:layout_width=\"fill_parent\"
android:layout_height=\"fill_parent\" >
<ImageView android:id=\"@+id/imageView\"
android:layout_width=\"fill_parent\"
android:layout_height=\"fill_parent\"
android:src=\"@drawable/map\"
android:scaleType=\"matrix\" >
</ImageView>
</FrameLayout>
AndroidManifest.xml (only added the theme so there is no title bar and is full screen)
<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"
package=\"org.example.touch\"
android:versionCode=\"7\"
android:versionName=\"1.0\" >
<application android:icon=\"@drawable/icon\" android:label=\"@string/app_name\" android:theme=\"@android:style/Theme.NoTitleBar.Fullscreen\" >
<activity android:name=\".Touch\"
android:label=\"@string/app_name\" >
<intent-filter>
<action android:name=\"android.intent.action.MAIN\" />
<category android:name=\"android.intent.category.LAUNCHER\" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion=\"3\" android:targetSdkVersion=\"7\" />
</manifest>
回答1:
I just created this:
https://github.com/jasonpolites/gesture-imageview
Might be useful for someone...
回答2:
Another option that might work for some is to use a WebView
, which has built in zoom controls.
WebView webView = new WebView(this);
webView.setBackgroundColor(0xff000000);
webView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
webView.getSettings().setBuiltInZoomControls(true);
webView.getSettings().setSupportZoom(true);
//webView.getSettings().setDisplayZoomControls(false); // API 11
webView.loadDataWithBaseURL(null, getHtml(), "text/html", "UTF-8", null);
mainView.addView(webView, -1, -2);
回答3:
I know this is old but I was looking into doing this and have a solution that works pretty well. Right after your switch statement and before you set the matrix, you can limit the zoom like so:
private void limitZoom(Matrix m) {
float[] values = new float[9];
m.getValues(values);
float scaleX = values[Matrix.MSCALE_X];
float scaleY = values[Matrix.MSCALE_Y];
if(scaleX > MAX_ZOOM) {
scaleX = MAX_ZOOM;
} else if(scaleX < MIN_ZOOM) {
scaleX = MIN_ZOOM;
}
if(scaleY > MAX_ZOOM) {
scaleY = MAX_ZOOM;
} else if(scaleY < MIN_ZOOM) {
scaleY = MIN_ZOOM;
}
values[Matrix.MSCALE_X] = scaleX;
values[Matrix.MSCALE_Y] = scaleY;
m.setValues(values);
}
I'm still working out how to limit the translation but this should work for the zoom limiting.
EDIT: Here's a solution for limiting the translation. Just as a note, I'm doing this for a full screen image view which is why I use the display width and height in my limiting factors but you could just as easily use the width and height of your view instead.
private void limitDrag(Matrix m) {
float[] values = new float[9];
m.getValues(values);
float transX = values[Matrix.MTRANS_X];
float transY = values[Matrix.MTRANS_Y];
float scaleX = values[Matrix.MSCALE_X];
float scaleY = values[Matrix.MSCALE_Y];
ImageView iv = (ImageView)findViewById(R.id.photo_view);
Rect bounds = iv.getDrawable().getBounds();
int viewWidth = getResources().getDisplayMetrics().widthPixels;
int viewHeight = getResources().getDisplayMetrics().heightPixels;
int width = bounds.right - bounds.left;
int height = bounds.bottom - bounds.top;
float minX = (-width + 20) * scaleX;
float minY = (-height + 20) * scaleY;
if(transX > (viewWidth - 20)) {
transX = viewWidth - 20;
} else if(transX < minX) {
transX = minX;
}
if(transY > (viewHeight - 80)) {
transY = viewHeight - 80;
} else if(transY < minY) {
transY = minY;
}
values[Matrix.MTRANS_X] = transX;
values[Matrix.MTRANS_Y] = transY;
m.setValues(values);
}
Once again, this would go right after your switch statement and right before you set the matrix for the image in the view. I broke out the zoom limiting into a function as well and it is reflected above.
回答4:
Download the source code. So you can also get this a) tap, b) drag, and c) pinch zoom. http://pragprog.com/titles/eband3/source_code
回答5:
Here is the complete code for pinch zoom and pan (Touch.java with some modifications that can be used practically)
public class Touch implements OnTouchListener {
// These matrices will be used to move and zoom image
public static Matrix matrix = new Matrix();
public static Matrix savedMatrix = new Matrix();
// We can be in one of these 3 states
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
private static final float MAX_ZOOM = (float) 3;
private static final float MIN_ZOOM = 1;
int mode = NONE;
// Remember some things for zooming
PointF start = new PointF();
PointF mid = new PointF();
float oldDist = 1f;
int width,height;
@Override
public boolean onTouch(View v, MotionEvent event) {
ImageView view = (ImageView) v;
Rect bounds = view.getDrawable().getBounds();
width = bounds.right - bounds.left;
height = bounds.bottom - bounds.top;
// Dump touch event to log
dumpEvent(event);
// Handle touch events here...
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
start.set(event.getX(), event.getY());
mode = DRAG;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
if (oldDist > 10f) {
savedMatrix.set(matrix);
midPoint(mid, event);
mode = ZOOM;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
// ...
matrix.set(savedMatrix);
matrix.postTranslate(event.getX() - start.x, event.getY() - start.y);
} else if (mode == ZOOM) {
float newDist = spacing(event);
if (newDist > 10f) {
matrix.set(savedMatrix);
float scale = newDist / oldDist;
matrix.postScale(scale, scale, mid.x, mid.y);
}
}
break;
}
//----------------------------------------------------
limitZoom(matrix);
limitDrag( matrix);
//----------------------------------------------------
view.setImageMatrix(matrix);
return true; // indicate event was handled
}
/** Show an event in the LogCat view, for debugging */
private void dumpEvent(MotionEvent event) {
String names[] = { "DOWN", "UP", "MOVE", "CANCEL", "OUTSIDE",
"POINTER_DOWN", "POINTER_UP", "7?", "8?", "9?" };
StringBuilder sb = new StringBuilder();
int action = event.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
sb.append("event ACTION_").append(names[actionCode]);
if (actionCode == MotionEvent.ACTION_POINTER_DOWN
|| actionCode == MotionEvent.ACTION_POINTER_UP) {
sb.append("(pid ").append(
action >> MotionEvent.ACTION_POINTER_ID_SHIFT);
sb.append(")");
}
sb.append("[");
for (int i = 0; i < event.getPointerCount(); i++) {
sb.append("#").append(i);
sb.append("(pid ").append(event.getPointerId(i));
sb.append(")=").append((int) event.getX(i));
sb.append(",").append((int) event.getY(i));
if (i + 1 < event.getPointerCount())
sb.append(";");
}
sb.append("]");
}
/** Determine the space between the first two fingers */
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
/** Calculate the mid point of the first two fingers */
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
private void limitZoom(Matrix m) {
float[] values = new float[9];
m.getValues(values);
float scaleX = values[Matrix.MSCALE_X];
float scaleY = values[Matrix.MSCALE_Y];
if(scaleX > MAX_ZOOM) {
scaleX = MAX_ZOOM;
} else if(scaleX < MIN_ZOOM) {
scaleX = MIN_ZOOM;
}
if(scaleY > MAX_ZOOM) {
scaleY = MAX_ZOOM;
} else if(scaleY < MIN_ZOOM) {
scaleY = MIN_ZOOM;
}
values[Matrix.MSCALE_X] = scaleX;
values[Matrix.MSCALE_Y] = scaleY;
m.setValues(values);
}
private void limitDrag(Matrix m) {
float[] values = new float[9];
m.getValues(values);
float transX = values[Matrix.MTRANS_X];
float transY = values[Matrix.MTRANS_Y];
float scaleX = values[Matrix.MSCALE_X];
float scaleY = values[Matrix.MSCALE_Y];
//--- limit moving to left ---
float minX = (-width + 0) * (scaleX-1);
float minY = (-height + 0) * (scaleY-1);
//--- limit moving to right ---
float maxX=minX+width*(scaleX-1);
float maxY=minY+height*(scaleY-1);
if(transX>maxX){transX = maxX;}
if(transX<minX){transX = minX;}
if(transY>maxY){transY = maxY;}
if(transY<minY){transY = minY;}
values[Matrix.MTRANS_X] = transX;
values[Matrix.MTRANS_Y] = transY;
m.setValues(values);
}
}
回答6:
Use the code in the comment by Phyxdevel in the below link ZDNET Pinch Zoom Example.
He has the code to restrict the pan and zoom level.
回答7:
you can use following code to limit bottom and right
float maxX = minX+viewWidth;
int offsetY = 80;
float maxY = minY+viewHeight-offsetY;
if(x>maxX){
mPosX = maxX;
}
if(x<minX){
mPosX = minX;
}
if(y>maxY){
mPosY = maxY;
}
if(y<minY){
mPosY = minY;
}
来源:https://stackoverflow.com/questions/4227451/android-imageview-setting-drag-and-pinch-zoom-parameters