Canvas: acercar, cambiar y escalar en Android

Actualmente estoy implementando una función de dibujo en una imagen de webview (el elefante a continuación). No tengo ningún problema dibujándolo pero la función del zumbido hace algunas cosas enrrolladas (2da imagen abajo) en el dibujo. Cuando hago zoom, el dibujo no se amplía, sino que se desplaza. Dibujar en zoom también no funciona. Mi código está abajo:

Normal

Introduzca aquí la descripción de la imagen

@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint); canvas.drawPath(drawPath, drawPaint); canvas.scale(mScaleFactor, mScaleFactor); canvas.restore(); clipBounds = canvas.getClipBounds(); } @Override public boolean onTouchEvent(MotionEvent event) { mScaleDetector.onTouchEvent(event); float touchX = event.getX() / mScaleFactor + clipBounds.left; float touchY = event.getY() / mScaleFactor + clipBounds.top; if (Deal.on) switch (event.getAction()) { case MotionEvent.ACTION_DOWN: drawPath.moveTo(touchX, touchY); break; case MotionEvent.ACTION_MOVE: drawPath.lineTo(touchX, touchY); break; case MotionEvent.ACTION_UP: drawCanvas.drawPath(drawPath, drawPaint); drawPath.reset(); break; default: return false; } else { super.onTouchEvent(event); } invalidate(); return true; } public class ScaleGestureListener extends SimpleOnScaleGestureListener { @Override public boolean onScale(ScaleGestureDetector detector) { mScaleFactor *= detector.getScaleFactor(); // Don't let the object get too small or too large. mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f)); invalidate(); return true; } } 

Edit : Conseguí que el lienzo se vuelva a dibujar con el zoom usando el factor de escala del GestureDetector. Además, tengo un conmutador que cambia de dibujo a controles de zoom / webview. Un problema en el que me encuentro es que el doble toque en WebView no activa el gesto onScale. Lo que significa que el lienzo no se vuelve a dibujar y se desplaza en el zoom.

Tengo que implementar una característica que detecta cuánto el factor de escala se ve afectado por el zoom doble de acercar. Si alguien puede sugerir una solución a eso. Aquí está el código de actualización:

 @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); clipBounds = canvas.getClipBounds(); canvas.save(); drawPaint.setStrokeWidth(8/mScaleFactor); canvas.scale(mScaleFactor, mScaleFactor, 0, 0); canvas.drawPath(drawPath, drawPaint); canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint); canvas.restore(); } @Override public boolean onTouchEvent(MotionEvent event) { pointerCount = event.getPointerCount(); mScaleDetector.onTouchEvent(event); float touchX = (event.getX() + clipBounds.left) / mScaleFactor; float touchY = (event.getY() + clipBounds.top) / mScaleFactor; if (Deal.on){ if (event.getPointerCount() > 1){ return false; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: drawPath.moveTo(touchX, touchY); break; case MotionEvent.ACTION_MOVE: drawPath.lineTo(touchX, touchY); break; case MotionEvent.ACTION_UP: drawCanvas.drawPath(drawPath, drawPaint); drawPath.reset(); break; default: return false; } } else { super.onTouchEvent(event); } invalidate(); return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Try for a width based on our minimum int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth(); int w = resolveSizeAndState(minw, widthMeasureSpec, 1); //int minh = MeasureSpec.getSize(w) - (int)mTextWidth + getPaddingBottom() + getPaddingTop(); int h = resolveSizeAndState(MeasureSpec.getSize(w), heightMeasureSpec, 0); setMeasuredDimension(w, h); } public class ScaleGestureListener extends SimpleOnScaleGestureListener { @Override public boolean onScale(ScaleGestureDetector detector) { // Don't let the object get too small or too large. if(Deal.on == false) { mScaleFactor *= detector.getScaleFactor(); mScaleFactor = Math.max(1f, Math.min(mScaleFactor, 20.0f)); } invalidate(); return true; } } 

He implementado este comportamiento, pero de una manera ligeramente diferente. He utilizado una matriz para manejar todo el zoom y el desplazamiento (y la rotación, en mi caso). Hace para el código aseado y funciona como un reloj. No sé qué está causando su comportamiento funky, aunque.

Almacene una matriz y otro camino como miembros de la clase:

 Matrix drawMatrix = new Matrix(); Path transformedPath = new Path(); 

Sustituya su onScale:

 @Override public boolean onScale(ScaleGestureDetector detector) { Matrix transformationMatrix = new Matrix(); //Zoom focus is where the fingers are centered, transformationMatrix.postTranslate(-detector.getFocusX(), -detector.getFocusY()); transformationMatrix.postScale(detector.getScaleFactor(), detector.getScaleFactor()); /* Using getFocuShift allows for scrolling with two pointers down. Remove it to skip this functionality */ transformationMatrix.postTranslate(detector.getFocusX() + detector.getFocusShiftX(), detector.getFocusY() + detector.getFocusShiftY()); drawMatrix.postConcat(transformationMatrix); invalidate(); return true; } 

En onDraw; Saltar salvando el lienzo, en su lugar:

 @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawBitmap(canvasBitmap, drawMatrix, canvasPaint); transformedPath.rewind(); transformedMatrix.addPath(drawPath); transformedPath.transform(drawMatrix, null); canvas.drawPath(transformedPath, drawPaint); } 

Codificación feliz!

Una buena manera de lograr todos los diferentes efectos que está tratando de hacer de una manera muy simple es hacer uso del método de lienzo

Canvas.drawBitmap (Bitmap mapa de bits, Rect src, Rect dst, pintura de pintura);

Este método le permite tomar el mapa de bits de toda la fuente o sólo una pequeña muestra de ella (Rect src), y proyectarlo como desee (Rect dst) haciendo la escala / traducción de cálculos de matriz automáticamente para usted, como se muestra en el ejemplo siguiente :

Este ejemplo sólo escala / amplía la imagen completa …

  Canvas canvas = null; Bitmap elephantBmp = null; Rect src = new Rect(); Rect postRect = new Rect(); Paint paintIfAny = new Paint(); //To scale the whole image, first take the whole source and then postRect with a bigger size... src.top = src.left = 0; src.right = elephantBmp.getWidth(); src.bottom = elephantBmp.getHeight(); //Here you have the chance to translate / scale the image(in this case i will not translate it but just scale it...) postRect.top = postRect.left = 0; postRect.right = (int)(elephantBmp.getWidth() * 1.1F); postRect.bottom = (int)(elephantBmp.getHeight() * 1.1F); canvas.drawBitmap(elephantBmp, src, postRect, paintIfAny); 

Y este ejemplo toma sólo una muestra de la imagen y muestra "efecto de zoom interno"

  Canvas canvas = null; Bitmap elephantBmp = null; Rect src = new Rect(); Rect postRect = new Rect(); Paint paintIfAny = new Paint(); //To shift the whole image, first take the part of the original source image you want to show src.top = topPosition;//Top coordinate of piece of image to show src.left = leftPosition;//Left coordinate of piece of image to show src.right = rightPosition;//Right coordinate of piece of image to show src.bottom = bottomPosition;//Bottom coordinate of piece of image to show //Here you have the chance to show it as big as you want... postRect.top = postRect.left = 0; postRect.right = (int)(src.width() * 1.1F); postRect.bottom = (int)(src.height() * 1.1F); canvas.drawBitmap(elephantBmp, src, postRect, paintIfAny); 

Haciendo uso de estos objetos src / dst puede hacer prácticamente cualquier efecto que desee en android, es una herramienta simple y realmente poderosa

Espero que esto ayude.

¡Saludos!

¿Se puede intentar dibujar caminos sólo una vez, y mantener el zoom la responsabilidad del lienzo en sí

 private boolean pathsDrawn; protected void onDraw(Canvas canvas) { super.onDraw(canvas); clipBounds = canvas.getClipBounds(); canvas.save(); drawPaint.setStrokeWidth(8/mScaleFactor); canvas.scale(mScaleFactor, mScaleFactor, 0, 0); if(!pathsDrawn) { canvas.drawPath(drawPath, drawPaint); pathsDrawn = true; } canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint); canvas.restore(); } 
 check below code will help you. //CUSTOM IMAGEVIEW import java.io.ByteArrayOutputStream; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.FloatMath; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.widget.ImageView; public class ScaleImageView extends ImageView implements OnTouchListener { static final float STROKE_WIDTH = 10f; static final float HALF_STROKE_WIDTH = STROKE_WIDTH / 2; float lastTouchX; float lastTouchY; final RectF dirtyRect = new RectF(); private Context mContext; private float MAX_SCALE = 2f; private static Matrix mMatrix; private final float[] mMatrixValues = new float[9]; // display width height. private int mWidth; private int mHeight; private int mIntrinsicWidth; private int mIntrinsicHeight; private float mScale; private float mMinScale; private float mPrevDistance; private boolean isScaling; private int mPrevMoveX; private int mPrevMoveY; private GestureDetector mDetector; Paint paint = new Paint(); public static Path path = new Path(); public static int imageheight, imagewidth; String TAG = "ScaleImageView"; public ScaleImageView(Context context, AttributeSet attr) { super(context, attr); this.mContext = context; initialize(); } public ScaleImageView(Context context) { super(context); this.mContext = context; initialize(); } private void resetDirtyRect(float eventX, float eventY) { dirtyRect.left = Math.min(lastTouchX, eventX); dirtyRect.right = Math.max(lastTouchX, eventX); dirtyRect.top = Math.min(lastTouchY, eventY); dirtyRect.bottom = Math.max(lastTouchY, eventY); } @Override public void setImageBitmap(Bitmap bm) { super.setImageBitmap(bm); this.initialize(); } @Override public void setImageResource(int resId) { super.setImageResource(resId); this.initialize(); } private void initialize() { this.setScaleType(ScaleType.MATRIX); this.mMatrix = new Matrix(); Drawable d = getDrawable(); paint.setAntiAlias(true); paint.setColor(Color.RED); paint.setStyle(Paint.Style.STROKE); paint.setStrokeJoin(Paint.Join.ROUND); paint.setStrokeWidth(STROKE_WIDTH); if (d != null) { mIntrinsicWidth = d.getIntrinsicWidth(); mIntrinsicHeight = d.getIntrinsicHeight(); setOnTouchListener(this); } mDetector = new GestureDetector(mContext, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDoubleTap(MotionEvent e) { maxZoomTo((int) e.getX(), (int) e.getY()); cutting(); return super.onDoubleTap(e); } }); } @Override protected boolean setFrame(int l, int t, int r, int b) { mWidth = r - l; mHeight = b - t; mMatrix.reset(); int r_norm = r - l; mScale = (float) r_norm / (float) mIntrinsicWidth; int paddingHeight = 0; int paddingWidth = 0; // scaling vertical if (mScale * mIntrinsicHeight > mHeight) { mScale = (float) mHeight / (float) mIntrinsicHeight; mMatrix.postScale(mScale, mScale); paddingWidth = (r - mWidth) / 2; paddingHeight = 0; // scaling horizontal } else { mMatrix.postScale(mScale, mScale); paddingHeight = (b - mHeight) / 2; paddingWidth = 0; } mMatrix.postTranslate(paddingWidth, paddingHeight); setImageMatrix(mMatrix); mMinScale = mScale; zoomTo(mScale, mWidth / 2, mHeight / 2); cutting(); return super.setFrame(l, t, r, b); } protected float getValue(Matrix matrix, int whichValue) { matrix.getValues(mMatrixValues); return mMatrixValues[whichValue]; } protected float getScale() { return getValue(mMatrix, Matrix.MSCALE_X); } public float getTranslateX() { return getValue(mMatrix, Matrix.MTRANS_X); } protected float getTranslateY() { return getValue(mMatrix, Matrix.MTRANS_Y); } protected void maxZoomTo(int x, int y) { if (mMinScale != getScale() && (getScale() - mMinScale) > 0.1f) { // threshold 0.1f float scale = mMinScale / getScale(); zoomTo(scale, x, y); } else { float scale = MAX_SCALE / getScale(); zoomTo(scale, x, y); } } public void zoomTo(float scale, int x, int y) { if (getScale() * scale < mMinScale) { return; } if (scale >= 1 && getScale() * scale > MAX_SCALE) { return; } mMatrix.postScale(scale, scale); // move to center mMatrix.postTranslate(-(mWidth * scale - mWidth) / 2, -(mHeight * scale - mHeight) / 2); // move x and y distance mMatrix.postTranslate(-(x - (mWidth / 2)) * scale, 0); mMatrix.postTranslate(0, -(y - (mHeight / 2)) * scale); setImageMatrix(mMatrix); } public void cutting() { int width = (int) (mIntrinsicWidth * getScale()); int height = (int) (mIntrinsicHeight * getScale()); imagewidth = width; imageheight = height; if (getTranslateX() < -(width - mWidth)) { mMatrix.postTranslate(-(getTranslateX() + width - mWidth), 0); } if (getTranslateX() > 0) { mMatrix.postTranslate(-getTranslateX(), 0); } if (getTranslateY() < -(height - mHeight)) { mMatrix.postTranslate(0, -(getTranslateY() + height - mHeight)); } if (getTranslateY() > 0) { mMatrix.postTranslate(0, -getTranslateY()); } if (width < mWidth) { mMatrix.postTranslate((mWidth - width) / 2, 0); } if (height < mHeight) { mMatrix.postTranslate(0, (mHeight - height) / 2); } setImageMatrix(mMatrix); } private float distance(float x0, float x1, float y0, float y1) { float x = x0 - x1; float y = y0 - y1; return FloatMath.sqrt(x * x + y * y); } private float dispDistance() { return FloatMath.sqrt(mWidth * mWidth + mHeight * mHeight); } public void clear() { path.reset(); invalidate(); } public static void save() { Bitmap returnedBitmap = Bitmap.createBitmap( ScaleImageViewActivity.imageview.getWidth(), ScaleImageViewActivity.imageview.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(returnedBitmap); Drawable bgDrawable = ScaleImageViewActivity.imageview.getDrawable(); if (bgDrawable != null) bgDrawable.draw(canvas); else canvas.drawColor(Color.WHITE); ScaleImageViewActivity.imageview.draw(canvas); ByteArrayOutputStream bs = new ByteArrayOutputStream(); returnedBitmap.compress(Bitmap.CompressFormat.PNG, 50, bs); Bitmap FinalBitmap = BitmapFactory.decodeByteArray(bs.toByteArray(), 0, bs.toByteArray().length); ScaleImageViewActivity.imageview.setImageBitmap(FinalBitmap); path.reset(); // ScaleImageViewActivity.imageview.setImageMatrix(mMatrix); } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent event) { if (!ScaleImageViewActivity.flag) { if (mDetector.onTouchEvent(event)) { return true; } int touchCount = event.getPointerCount(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_1_DOWN: case MotionEvent.ACTION_POINTER_2_DOWN: if (touchCount >= 2) { float distance = distance(event.getX(0), event.getX(1), event.getY(0), event.getY(1)); mPrevDistance = distance; isScaling = true; } else { mPrevMoveX = (int) event.getX(); mPrevMoveY = (int) event.getY(); } case MotionEvent.ACTION_MOVE: if (touchCount >= 2 && isScaling) { float dist = distance(event.getX(0), event.getX(1), event.getY(0), event.getY(1)); float scale = (dist - mPrevDistance) / dispDistance(); mPrevDistance = dist; scale += 1; scale = scale * scale; zoomTo(scale, mWidth / 2, mHeight / 2); cutting(); } else if (!isScaling) { int distanceX = mPrevMoveX - (int) event.getX(); int distanceY = mPrevMoveY - (int) event.getY(); mPrevMoveX = (int) event.getX(); mPrevMoveY = (int) event.getY(); mMatrix.postTranslate(-distanceX, -distanceY); cutting(); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_POINTER_2_UP: if (event.getPointerCount() <= 1) { isScaling = false; } break; } } else { float eventX = event.getX(); float eventY = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: path.moveTo(eventX, eventY); lastTouchX = eventX; lastTouchY = eventY; return true; case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_UP: resetDirtyRect(eventX, eventY); int historySize = event.getHistorySize(); for (int i = 0; i < historySize; i++) { float historicalX = event.getHistoricalX(i); float historicalY = event.getHistoricalY(i); path.lineTo(historicalX, historicalY); } path.lineTo(eventX, eventY); break; } invalidate((int) (dirtyRect.left - HALF_STROKE_WIDTH), (int) (dirtyRect.top - HALF_STROKE_WIDTH), (int) (dirtyRect.right + HALF_STROKE_WIDTH), (int) (dirtyRect.bottom + HALF_STROKE_WIDTH)); lastTouchX = eventX; lastTouchY = eventY; } return true; } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); if (ScaleImageViewActivity.flag) canvas.drawPath(path, paint); } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouch(View v, MotionEvent event) { return super.onTouchEvent(event); } } //ACTIVITY import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class ScaleImageViewActivity extends Activity implements OnClickListener { Button btndraw, btnzoom, btnsave; public static ScaleImageView imageview; public static boolean flag = true; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); initwidget(); } private void initwidget() { imageview = (ScaleImageView) findViewById(R.id.image); btnsave = (Button) findViewById(R.id.activity_main_save); btndraw = (Button) findViewById(R.id.activity_main_zoom_draw); btnzoom = (Button) findViewById(R.id.activity_main_zoom_zoom); btndraw.setOnClickListener(this); btnzoom.setOnClickListener(this); btnsave.setOnClickListener(this); } @Override public void onClick(View arg0) { // TODO Auto-generated method stub if (btndraw.equals(arg0)) { flag = true; } else if (btnzoom.equals(arg0)) { flag = false; } else if (btnsave.equals(arg0)) { ScaleImageView.save(); } } } main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" > <Button android:id="@+id/activity_main_zoom_zoom" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Zoom" /> <Button android:id="@+id/activity_main_zoom_draw" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Draw" /> <Button android:id="@+id/activity_main_save" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Save" /> </LinearLayout> <com.matabii.dev.scaleimageview.ScaleImageView android:id="@+id/image" android:layout_width="fill_parent" android:layout_height="fill_parent" android:src="@drawable/sample" /> </LinearLayout> 
  • Convertir Rect a RectF
  • Android arrastrar y soltar / rotar mapa de bits en lienzo
  • Cómo dibujar imágenes de puntos en el borde del círculo de la imagen
  • ¿Cómo obtener el lienzo actual?
  • Superposición de fila en el listview
  • ¿Puedo borrar la parte o el contenido de una imagen que se carga desde la tarjeta SD al lienzo o DrawView
  • recorte circular bitmap en android
  • Editado: Android Dibuja la imagen de la aguja en este círculo similar al medidor
  • Android Canvas o Open GL ES para el juego 2d?
  • Android - Lienzo drawLine dentro de ImageView
  • Cómo encontrar los puntos girados de una forma en Android
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.