¿Cómo puedo mover y escalar una imagen como la herramienta de selección de imágenes de perfil de Facebook?

Quiero imagen de la cosecha como la selección de imagen de perfil de Facebook en Android, donde el usuario puede mover y escalar una imagen, haciendo que se cambie el tamaño y / o recortar:

Captura de pantalla de la herramienta en cuestión

¿Cómo podría lograr esto?

Yo tenía el mismo requisito. Lo solucioné combinando PhotoView y Cropper reemplazando ImageView con PhotoView en la lib de cultivo.

Tuve que modificar la clase CropWindow para evitar que los eventos de toque no se manejen correctamente:

  public void setImageView(PhotoView pv){ mPhotoView = pv; } @Override public boolean onTouchEvent(MotionEvent event) { // If this View is not enabled, don't allow for touch interactions. if (!isEnabled()) { return false; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: boolean dispatch = onActionDown(event.getX(), event.getY()); if(!dispatch) mPhotoView.dispatchTouchEvent(event); return dispatch; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: getParent().requestDisallowInterceptTouchEvent(false); onActionUp(); return true; case MotionEvent.ACTION_MOVE: onActionMove(event.getX(), event.getY()); getParent().requestDisallowInterceptTouchEvent(true); return true; default: return false; } } 

En la clase CropImageView cambió algunas cosas:

 private void init(Context context) { final LayoutInflater inflater = LayoutInflater.from(context); final View v = inflater.inflate(R.layout.crop_image_view, this, true); mImageView = (PhotoView) v.findViewById(R.id.ImageView_image2); setImageResource(mImageResource); mCropOverlayView = (CropOverlayView) v.findViewById(R.id.CropOverlayView); mCropOverlayView.setInitialAttributeValues(mGuidelines, mFixAspectRatio, mAspectRatioX, mAspectRatioY); mCropOverlayView.setImageView(mImageView); } 

Usted puede notar que he reemplazado ImageView con PhotoView dentro de R.layout.crop_image_view en la biblioteca de Cropper.

La biblioteca Cropper admite el tamaño fijo y PhotoView le permite mover y escalar la foto, ofreciéndole lo mejor de ambos mundos. 🙂

Espero eso ayude.

Editar, para aquellos que preguntaron cómo obtener la imagen que está sólo dentro del área de recorte:

 private Bitmap getCurrentDisplayedImage(){ Bitmap result = Bitmap.createBitmap(mImageView.getWidth(), mImageView.getHeight(), Bitmap.Config.RGB_565); Canvas c = new Canvas(result); mImageView.draw(c); return result; } public Bitmap getCroppedImage() { Bitmap mCurrentDisplayedBitmap = getCurrentDisplayedImage(); final Rect displayedImageRect = ImageViewUtil2.getBitmapRectCenterInside(mCurrentDisplayedBitmap, mImageView); // Get the scale factor between the actual Bitmap dimensions and the // displayed dimensions for width. final float actualImageWidth =mCurrentDisplayedBitmap.getWidth(); final float displayedImageWidth = displayedImageRect.width(); final float scaleFactorWidth = actualImageWidth / displayedImageWidth; // Get the scale factor between the actual Bitmap dimensions and the // displayed dimensions for height. final float actualImageHeight = mCurrentDisplayedBitmap.getHeight(); final float displayedImageHeight = displayedImageRect.height(); final float scaleFactorHeight = actualImageHeight / displayedImageHeight; // Get crop window position relative to the displayed image. final float cropWindowX = Edge.LEFT.getCoordinate() - displayedImageRect.left; final float cropWindowY = Edge.TOP.getCoordinate() - displayedImageRect.top; final float cropWindowWidth = Edge.getWidth(); final float cropWindowHeight = Edge.getHeight(); // Scale the crop window position to the actual size of the Bitmap. final float actualCropX = cropWindowX * scaleFactorWidth; final float actualCropY = cropWindowY * scaleFactorHeight; final float actualCropWidth = cropWindowWidth * scaleFactorWidth; final float actualCropHeight = cropWindowHeight * scaleFactorHeight; // Crop the subset from the original Bitmap. final Bitmap croppedBitmap = Bitmap.createBitmap(mCurrentDisplayedBitmap, (int) actualCropX, (int) actualCropY, (int) actualCropWidth, (int) actualCropHeight); return croppedBitmap; } public RectF getActualCropRect() { final Rect displayedImageRect = ImageViewUtil.getBitmapRectCenterInside(mBitmap, mImageView); final float actualImageWidth = mBitmap.getWidth(); final float displayedImageWidth = displayedImageRect.width(); final float scaleFactorWidth = actualImageWidth / displayedImageWidth; // Get the scale factor between the actual Bitmap dimensions and the displayed // dimensions for height. final float actualImageHeight = mBitmap.getHeight(); final float displayedImageHeight = displayedImageRect.height(); final float scaleFactorHeight = actualImageHeight / displayedImageHeight; // Get crop window position relative to the displayed image. final float displayedCropLeft = Edge.LEFT.getCoordinate() - displayedImageRect.left; final float displayedCropTop = Edge.TOP.getCoordinate() - displayedImageRect.top; final float displayedCropWidth = Edge.getWidth(); final float displayedCropHeight = Edge.getHeight(); // Scale the crop window position to the actual size of the Bitmap. float actualCropLeft = displayedCropLeft * scaleFactorWidth; float actualCropTop = displayedCropTop * scaleFactorHeight; float actualCropRight = actualCropLeft + displayedCropWidth * scaleFactorWidth; float actualCropBottom = actualCropTop + displayedCropHeight * scaleFactorHeight; // Correct for floating point errors. Crop rect boundaries should not exceed the // source Bitmap bounds. actualCropLeft = Math.max(0f, actualCropLeft); actualCropTop = Math.max(0f, actualCropTop); actualCropRight = Math.min(mBitmap.getWidth(), actualCropRight); actualCropBottom = Math.min(mBitmap.getHeight(), actualCropBottom); final RectF actualCropRect = new RectF(actualCropLeft, actualCropTop, actualCropRight, actualCropBottom); return actualCropRect; } private boolean onActionDown(float x, float y) { final float left = Edge.LEFT.getCoordinate(); final float top = Edge.TOP.getCoordinate(); final float right = Edge.RIGHT.getCoordinate(); final float bottom = Edge.BOTTOM.getCoordinate(); mPressedHandle = HandleUtil.getPressedHandle(x, y, left, top, right, bottom, mHandleRadius); if (mPressedHandle == null) return false; mTouchOffset = HandleUtil2.getOffset(mPressedHandle, x, y, left, top, right, bottom); invalidate(); return true; } 

Tengo algunas adiciones a @Nikola Despotoski respuesta. En primer lugar, no es necesario cambiar ImageView en R.layout.crop_image_view a PhotoView, porque la lógica de PhotoView se puede adjuntar simplemente en código como nuevo PhotoViewAttacher (mImageView).

También en la lógica predeterminada, el tamaño de superposición de CropView calcula sólo en su inicialización de acuerdo con el tamaño de mapa de bits de imageView. Por lo tanto, no es una lógica apropiada para nosotros, porque cambiamos el tamaño de mapa de bits por toques de acuerdo con el requisito. Por lo tanto, debemos cambiar los tamaños de bitmap almacenados en CropOverlayView e invalidarlo cada vez que cambiemos la imagen principal.

Y el último es que un rango, donde el usuario puede hacer el recorte normalmente basado en el tamaño de la imagen, pero si hacemos la imagen más grande, puede ir más allá del borde de la pantalla, por lo que será posible al usuario mover una vista de cultivo más allá de la Frontera, lo cual es incorrecto. Así que también debemos manejar esta situación y proporcionar limitación.

Y la parte correspondiente del código para estos tres temas: En CropImageView:

  private void init(Context context) { final LayoutInflater inflater = LayoutInflater.from(context); final View v = inflater.inflate(R.layout.crop_image_view, this, true); mImageView = (ImageView) v.findViewById(R.id.ImageView_image); setImageResource(mImageResource); mCropOverlayView = (CropOverlayView) v.findViewById(R.id.CropOverlayView); mCropOverlayView.setInitialAttributeValues(mGuidelines, mFixAspectRatio, mAspectRatioX, mAspectRatioY); mCropOverlayView.setOutlineTouchEventReceiver(mImageView); final PhotoViewAttacher photoAttacher = new PhotoViewAttacher(mImageView); photoAttacher.setOnMatrixChangeListener(new PhotoViewAttacher.OnMatrixChangedListener() { @Override public void onMatrixChanged(RectF imageRect) { final Rect visibleRect = ImageViewUtil.getBitmapRectCenterInside(photoAttacher.getVisibleRectangleBitmap(), photoAttacher.getImageView()); imageRect.top = Math.max(imageRect.top, visibleRect.top); imageRect.left = Math.max(imageRect.left, visibleRect.left); imageRect.right = Math.min(imageRect.right, visibleRect.right); imageRect.bottom = Math.min(imageRect.bottom, visibleRect.bottom); Rect bitmapRect = new Rect(); imageRect.round(bitmapRect); mCropOverlayView.changeBitmapRectInvalidate(bitmapRect); } }); } 

En CropOverlayView:

 @Override public boolean onTouchEvent(MotionEvent event) { // If this View is not enabled, don't allow for touch interactions. if (!isEnabled()) { return false; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: return onActionDown(event.getX(), event.getY()); case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: getParent().requestDisallowInterceptTouchEvent(false); return onActionUp(); case MotionEvent.ACTION_MOVE: boolean result = onActionMove(event.getX(), event.getY()); getParent().requestDisallowInterceptTouchEvent(true); return result; default: return false; } } public void changeBitmapRectInvalidate(Rect bitmapRect) { mBitmapRect = bitmapRect; invalidate(); } private boolean onActionDown(float x, float y) { final float left = Edge.LEFT.getCoordinate(); final float top = Edge.TOP.getCoordinate(); final float right = Edge.RIGHT.getCoordinate(); final float bottom = Edge.BOTTOM.getCoordinate(); mPressedHandle = HandleUtil.getPressedHandle(x, y, left, top, right, bottom, mHandleRadius); if (mPressedHandle == null){ return false; } // Calculate the offset of the touch point from the precise location // of the handle. Save these values in a member variable since we want // to maintain this offset as we drag the handle. mTouchOffset = HandleUtil.getOffset(mPressedHandle, x, y, left, top, right, bottom); invalidate(); return true; } /** * Handles a {@link MotionEvent#ACTION_UP} or * {@link MotionEvent#ACTION_CANCEL} event. * @return true if event vas handled, else - false */ private boolean onActionUp() { if (mPressedHandle == null) return false; mPressedHandle = null; invalidate(); return true; } /** * Handles a {@link MotionEvent#ACTION_MOVE} event. * * @param x the x-coordinate of the move event * @param y the y-coordinate of the move event */ private boolean onActionMove(float x, float y) { if (mPressedHandle == null) return false; // Adjust the coordinates for the finger position's offset (ie the // distance from the initial touch to the precise handle location). // We want to maintain the initial touch's distance to the pressed // handle so that the crop window size does not "jump". x += mTouchOffset.first; y += mTouchOffset.second; // Calculate the new crop window size/position. if (mFixAspectRatio) { mPressedHandle.updateCropWindow(x, y, mTargetAspectRatio, mBitmapRect, mSnapRadius); } else { mPressedHandle.updateCropWindow(x, y, mBitmapRect, mSnapRadius); } invalidate(); return true; } 

Para obtener correctamente la imagen recortada debe utilizar la segunda parte de @ Nikola Despotoski respuesta

Lo que usted desea puede ser logrado exactamente por este lib simple-crop-image-lib

Gracias a todos. Capaz de lograr esto utilizando las respuestas anteriores, utilizando Photoview y Cropper biblioteca. Se han añadido opciones para seleccionar imágenes de la cámara o la galería. Compartir el proyecto en Github. Se ha agregado un archivo apk en el proyecto. Utilice el dispositivo verdadero para probar la cámara pues el emulador no maneja bien la cámara. Aquí está el enlace a mi proyecto.

https://github.com/ozeetee/AndroidImageZoomCrop

  • Guardar vista de mapa de bits con getDrawingCache da una imagen en negro
  • Recortar imagen a cualquier forma Android
  • Encontrar un patrón dentro de una imagen
  • ListView con imagen de fondo
  • Cómo puedo mostrar imágenes de una carpeta específica en la galería de Android
  • Almacenamiento en caché de imágenes de Android
  • Android ImageButton setImageResource de la variable
  • Cargar imagen de compresión en un servidor mediante retroadaptación
  • Asignación externa demasiado grande para este proceso
  • Las imágenes que no se almacenan en caché localmente (con Universal Image Loader) - tiempos de carga lenta de la imagen
  • Poner una imagen grande en un ImageView no funciona
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.