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:
- Android: invalidar (sucio)
- Dibujo a la lona con la interacción del usuario es un poco laggy
- Dibujo de un corazón con Sharp Corner en Android
- Coloque un rectángulo alrededor de una cadena en un lienzo de Android
- Cómo dibujar una flecha utilizando la clase de gráficos de Android?
@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; } }
- Uso de lienzo y mapa de bits en Android, ¿cómo obtener esta imagen?
- Rectángulo de dibujo de lienzo de Android
- Línea de dibujo de lienzo de Android - hacer la línea más gruesa
- Visualización de lienzo / mapa de bits al instante durante la depuración en eclipse
- Android Cambiando dinámicamente el tamaño del círculo sólo la mitad del círculo se actualiza correctamente
- Facebook Canvas App en WebView no funciona en Android (en la aplicación nativa de Facebook)
- ¿Es posible mostrar texto multicolor con una llamada a Canvas.drawText ()?
- Cómo borrar lienzo en android en el botón de clic
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>
- Cómo tomar una instantánea de la aplicación de pantalla no sólo en Android con código
- ¿Cómo puedo volver a conectar mi aplicación en cada apertura al mismo dispositivo bluetooth de baja energía?