
Janusz
Gibt es eine gängige Methode, um ein großes Bild anzuzeigen und es dem Benutzer zu ermöglichen, das Bild zu vergrößern, zu verkleinern und zu schwenken?
Bisher habe ich zwei Wege gefunden:
- Überschreiben von ImageView, das scheint ein bisschen zu viel für ein so häufiges Problem zu sein.
- Verwenden einer Webansicht, aber mit weniger Kontrolle über das Gesamtlayout usw.

Mike Ortiz
AKTUALISIEREN
Ich habe gerade TouchImageView ein neues Update gegeben. Es enthält jetzt Double Tap Zoom und Fling zusätzlich zu Panning und Pinch Zoom. Der folgende Code ist sehr datiert. Sie können die überprüfen github-Projekt um den neuesten Code zu erhalten.
VERWENDUNGSZWECK
Platzieren Sie TouchImageView.java in Ihrem Projekt. Es kann dann genauso wie ImageView verwendet werden. Beispiel:
TouchImageView img = (TouchImageView) findViewById(R.id.img);
Wenn Sie TouchImageView in XML verwenden, müssen Sie den vollständigen Paketnamen angeben, da es sich um eine benutzerdefinierte Ansicht handelt. Beispiel:
<com.example.touch.TouchImageView
android:id="@+id/img”
android:layout_width="match_parent"
android:layout_height="match_parent" />
Hinweis: Ich habe meine vorherige Antwort entfernt, die einen sehr alten Code enthielt, und verlinke jetzt direkt auf den aktuellsten Code auf GitHub.
ViewPager
Wenn Sie TouchImageView in einen ViewPager einfügen möchten, lesen Sie diese Antwort.
Ich habe etwas Code angepasst, um ein TouchImageView zu erstellen, das Multitouch unterstützt (>2.1). Es ist von dem Buch inspiriert Hallo Android! (3. Auflage)
Es ist in den folgenden 3 Dateien TouchImageView.java WrapMotionEvent.java EclairMotionEvent.java enthalten
TouchImageView.java
import se.robertfoss.ChanImageBrowser.Viewer;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.FloatMath;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
public class TouchImageView extends ImageView {
private static final String TAG = "Touch";
// 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;
Context context;
public TouchImageView(Context context) {
super(context);
super.setClickable(true);
this.context = context;
matrix.setTranslate(1f, 1f);
setImageMatrix(matrix);
setScaleType(ScaleType.MATRIX);
setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent rawEvent) {
WrapMotionEvent event = WrapMotionEvent.wrap(rawEvent);
// Dump touch event to log
if (Viewer.isDebug == true){
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());
Log.d(TAG, "mode=DRAG");
mode = DRAG;
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
Log.d(TAG, "oldDist=" + oldDist);
if (oldDist > 10f) {
savedMatrix.set(matrix);
midPoint(mid, event);
mode = ZOOM;
Log.d(TAG, "mode=ZOOM");
}
break;
case MotionEvent.ACTION_UP:
int xDiff = (int) Math.abs(event.getX() - start.x);
int yDiff = (int) Math.abs(event.getY() - start.y);
if (xDiff < 8 && yDiff < 8){
performClick();
}
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
Log.d(TAG, "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);
Log.d(TAG, "newDist=" + newDist);
if (newDist > 10f) {
matrix.set(savedMatrix);
float scale = newDist / oldDist;
matrix.postScale(scale, scale, mid.x, mid.y);
}
}
break;
}
setImageMatrix(matrix);
return true; // indicate event was handled
}
});
}
public void setImage(Bitmap bm, int displayWidth, int displayHeight) {
super.setImageBitmap(bm);
//Fit to screen.
float scale;
if ((displayHeight / bm.getHeight()) >= (displayWidth / bm.getWidth())){
scale = (float)displayWidth / (float)bm.getWidth();
} else {
scale = (float)displayHeight / (float)bm.getHeight();
}
savedMatrix.set(matrix);
matrix.set(savedMatrix);
matrix.postScale(scale, scale, mid.x, mid.y);
setImageMatrix(matrix);
// Center the image
float redundantYSpace = (float)displayHeight - (scale * (float)bm.getHeight()) ;
float redundantXSpace = (float)displayWidth - (scale * (float)bm.getWidth());
redundantYSpace /= (float)2;
redundantXSpace /= (float)2;
savedMatrix.set(matrix);
matrix.set(savedMatrix);
matrix.postTranslate(redundantXSpace, redundantYSpace);
setImageMatrix(matrix);
}
/** Show an event in the LogCat view, for debugging */
private void dumpEvent(WrapMotionEvent 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());
}
/** Determine the space between the first two fingers */
private float spacing(WrapMotionEvent 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, WrapMotionEvent event) {
// ...
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
}
WrapMotionEvent.java
import android.view.MotionEvent;
public class WrapMotionEvent {
protected MotionEvent event;
protected WrapMotionEvent(MotionEvent event) {
this.event = event;
}
static public WrapMotionEvent wrap(MotionEvent event) {
try {
return new EclairMotionEvent(event);
} catch (VerifyError e) {
return new WrapMotionEvent(event);
}
}
public int getAction() {
return event.getAction();
}
public float getX() {
return event.getX();
}
public float getX(int pointerIndex) {
verifyPointerIndex(pointerIndex);
return getX();
}
public float getY() {
return event.getY();
}
public float getY(int pointerIndex) {
verifyPointerIndex(pointerIndex);
return getY();
}
public int getPointerCount() {
return 1;
}
public int getPointerId(int pointerIndex) {
verifyPointerIndex(pointerIndex);
return 0;
}
private void verifyPointerIndex(int pointerIndex) {
if (pointerIndex > 0) {
throw new IllegalArgumentException(
"Invalid pointer index for Donut/Cupcake");
}
}
}
EclairMotionEvent.java
import android.view.MotionEvent;
public class EclairMotionEvent extends WrapMotionEvent {
protected EclairMotionEvent(MotionEvent event) {
super(event);
}
public float getX(int pointerIndex) {
return event.getX(pointerIndex);
}
public float getY(int pointerIndex) {
return event.getY(pointerIndex);
}
public int getPointerCount() {
return event.getPointerCount();
}
public int getPointerId(int pointerIndex) {
return event.getPointerId(pointerIndex);
}
}

Janusz
Ich habe ein WebView verwendet und das Bild aus dem Speicher geladen
webview.loadUrl("file://...")
Die WebView übernimmt das gesamte Schwenken, Zoomen und Scrollen. Wenn Sie wrap_content verwenden, wird die Webansicht nicht größer als das Bild und es werden keine weißen Bereiche angezeigt. Der WebView ist der bessere ImageView 😉

Digiphd
Als Antwort auf die ursprüngliche Frage von Janusz gibt es mehrere Möglichkeiten, dies zu erreichen, die sich alle in ihrem Schwierigkeitsgrad unterscheiden und unten aufgeführt sind. Die Verwendung einer Webansicht ist gut, aber in Bezug auf Look and Feel und Steuerbarkeit sehr eingeschränkt. Wenn Sie eine Bitmap von einer Leinwand zeichnen, scheinen die vielseitigsten Lösungen, die vorgeschlagen wurden, die von MikeOrtiz, Robert Foss und/oder die von Jacob Nordfalk zu sein. Es gibt ein tolles Beispiel für die Einbindung des Android-Multitouch-Controllers von PaulBourkeund ist großartig für die Multi-Touch-Unterstützung und alle Arten von benutzerdefinierten Ansichten.
Persönlich finde ich die Lösung von MikeOrtiz am einfachsten, wenn Sie einfach eine Leinwand auf eine Bitmap zeichnen und sie dann in ImageView anzeigen und in der Lage sein möchten, mit Multi-Touch hineinzuzoomen und sich zu bewegen. Allerdings ist für meine Zwecke der Code aus der Git das er bereitgestellt hat, scheint nur zu funktionieren, wenn seine benutzerdefinierte ImageView-Klasse TouchImageView das einzige untergeordnete Element ist oder die Layoutparameter wie folgt bereitstellt:
android:layout_height="match_parent"
android:layout_height="match_parent"
Leider brauchte ich aufgrund meines Layoutdesigns “wrap_content” für “layout_height”. Als ich es so änderte, wurde das Bild unten beschnitten und ich konnte nicht in den beschnittenen Bereich scrollen oder zoomen. Also habe ich mir die angeschaut Quelle für ImageView, nur um zu sehen, wie Android “onMeasure” implementiert und MikeOrtiz angepasst hat.
@Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//**** ADDED THIS ********/////
int w = (int) bmWidth;
int h = (int) bmHeight;
width = resolveSize(w, widthMeasureSpec);
height = resolveSize(h, heightMeasureSpec);
//**** END ********///
// width = MeasureSpec.getSize(widthMeasureSpec); // REMOVED
// height = MeasureSpec.getSize(heightMeasureSpec); // REMOVED
//Fit to screen.
float scale;
float scaleX = (float)width / (float)bmWidth;
float scaleY = (float)height / (float)bmHeight;
scale = Math.min(scaleX, scaleY);
matrix.setScale(scale, scale);
setImageMatrix(matrix);
saveScale = 1f;
// Center the image
redundantYSpace = (float)height - (scale * (float)bmHeight) ;
redundantXSpace = (float)width - (scale * (float)bmWidth);
redundantYSpace /= (float)2;
redundantXSpace /= (float)2;
matrix.postTranslate(redundantXSpace, redundantYSpace);
origWidth = width - 2 * redundantXSpace;
origHeight = height - 2 * redundantYSpace;
// origHeight = bmHeight;
right = width * saveScale - width - (2 * redundantXSpace * saveScale);
bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);
setImageMatrix(matrix);
}
Hier ist resolveSize(int,int) ein “Dienstprogramm zum Abgleichen einer gewünschten Größe mit Einschränkungen, die durch eine MeasureSpec auferlegt werden, wobei:
Parameter:
- size How big the view wants to be
- MeasureSpec Constraints imposed by the parent
Kehrt zurück:
- The size this view should be."
So wird im Wesentlichen ein Verhalten bereitgestellt, das der ursprünglichen ImageView-Klasse etwas ähnlicher ist, wenn das Bild geladen wird. Einige weitere Änderungen könnten vorgenommen werden, um eine größere Vielfalt von Bildschirmen zu unterstützen, die das Seitenverhältnis ändern. Aber im Moment hoffe ich, dass dies hilft. Danke an MikeOrtiz für seinen Originalcode, großartige Arbeit.
Könnte man auch ausprobieren http://code.google.com/p/android-multitouch-controller/
Die Bibliothek ist wirklich toll, wenn auch anfangs etwas schwer zu fassen.

zontar
Ich habe gerade TouchImageView von Robert Foss integriert: es funktionierte sofort tadellos! Danke!
Ich habe den Code nur ein wenig geändert, damit ich ihn aus meiner layout.xml instanziieren kann.
Fügen Sie einfach zwei Konstruktoren hinzu
public TouchImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public TouchImageView(Context context) {
super(context);
init(context);
}
und transformiere den alten Konstruktor in eine Init-Methode:
private void init(Context context){
//...old code ofconstructor of Robert Moss's code
}

Roman Truba
@Robert Foss, @Mike Ortiz, vielen Dank für Ihre Arbeit. Ich habe Ihre Arbeit zusammengeführt und die Robert-Klassen für Android > 2.0 mit Mikes zusätzlicher Arbeit abgeschlossen.
Als Ergebnis meiner Arbeit präsentiere ich Android Touch Gallery, basierend auf ViewPager und verwendet modifiziertes TouchImageView. Bilder werden per URL geladen und Sie können sie zoomen und ziehen. Sie finden es hier https://github.com/Dreddik/AndroidTouchGallery
9648200cookie-checkWie erhalte ich die Zoomfunktion für Bilder?yes
Es gibt eine ZOOM-STEUERUNG (Widget) und Sie können das OnTouch-Ereignis abhören, um das Schwenken zu handhaben.
– Tobrien
29. März 2010 um 13:33 Uhr
Eine ähnliche Frage, stackoverflow.com/questions/2537396/…, hat einen Link zu diesem Tutorial anddev.org/…. Sie finden das vielleicht nützlich, um Ihr Bild zu schwenken. Ich habe es nicht im Detail gelesen, aber es könnte Ihnen auch einige Ideen geben, wie Sie die Zoom-Funktion nutzen können.
– Steve Haley
29. März 2010 um 13:45 Uhr
Hat jemand versucht, das Bild beim Zoomen zu speichern? Ich möchte, dass das gespeicherte Bild einen Standardzustand anstelle des gezoomten Zustands hat. Siehe meine Frage: stackoverflow.com/questions/24730793/… Danke
– Blaze Tama
18. Juli 2014 um 5:00 Uhr