Android ImageView.setMatrix () y .invalidate () – el repintado toma demasiado tiempo
Tarea: Quiero cambiar el tamaño y mover una imagen a través de la pantalla. Quiero hacer esto sin problemas, no importa cuán grande sea la imagen. El código debe estar soportado por el API nivel 8.
Problema: Traté de usar ImageView
con scaleType="matrix"
. Llamar ImageView.setMatrix()
y luego ImageView.invalidate()
funciona muy bien con imágenes pequeñas pero horrible con las grandes. No importa cuán grande sea el ImageView
.
- ¿Cómo se encuentran los ParseRoles actualmente asociados con el ParseUser actual?
- Convertir o transmitir un objeto simple a objeto de otra clase
- Android Studio - OpenJDK 1.8 Vs. Oracle JDK
- Mostrar el diálogo TimePicker de la actividad PreferenceFragment
- Retrasar la ejecución del código en el método Java
¿Puedo de alguna manera acelerar el repintado de ImageView
para que no vuelva a calcular la imagen completa? Tal vez hay una manera de lograr la tarea utilizando diferentes componentes?
EDIT: Más información sobre lo que estoy tratando de lograr.
- Pw, ph – anchura y altura de la imagen (en píxeles)
- Dw, dh – anchura y altura de la pantalla del dispositivo (en píxeles)
- Fw, fh – anchura y altura del marco visible (en píxeles)
- X, y – posición de la esquina superior izquierda del marco (en píxeles)
Quiero mostrar una parte de la imagen en la pantalla. Las propiedades x , y , fw y fh están cambiando constantemente. Estoy buscando una parte de código (idea), o componentes que para estas 8 variables especificadas rápidamente generar y mostrar la parte de la imagen.
EDIT 2: Información sobre pw y ph
Supongo que pw y ph pueden contener valores de 1 a infinito. Si este enfoque causa muchos problemas, podemos suponer que la imagen no es mayor que la foto tomada con la cámara del dispositivo.
- Compostabilidad de FutureTask de Java
- ¿Puede el ícono de la aplicación en el cuadro de diálogo Construido en la búsqueda puede hacer clic?
- set androide del mapbox de la imagen de la demostración
- ¿Cómo clasificar en orden alfabético sin tener en cuenta las mayúsculas y minúsculas?
- Intento pendiente siempre hacer nueva actividad
- Cómo rellenar una base de datos SQLite con datos de un archivo xml?
- Traducción de código Python a JVM
- El signo de interrogación me parece nuevo
Con su ayuda (comunidad) me di cuenta de la solución. Estoy seguro de que hay otras mejores maneras de hacerlo, pero mi solución no es muy complicado y debería funcionar con cualquier imagen, cualquier Android desde API de nivel 8.
La solución es utilizar dos objetos ImageView
lugar de uno.
El primer ImageView
funcionará como antes, pero la imagen cargada se reducirá de manera que su ancho será menor que el ancho del ImageView
y su altura será menor que la altura del ImageView
.
El segundo ImageView
estará en blanco al principio. Cada vez que las propiedades x , y , fw y fh están cambiando, el AsyncTask
se ejecutará para cargar sólo parte visible de la imagen. Cuando las propiedades cambian rápidamente, el AsyncTask
no podrá terminar a tiempo. Tendrá que ser cancelado y se iniciará uno nuevo. Cuando termine el resultado Bitmap
se cargará en el segundo ImageView
para que sea visible para el usuario. Cuando las propiedades cambian de nuevo Cargado Bitmap
se eliminará, por lo que no cubrirá el movimiento Bitmap
cargado en el primer ImageView
. Nota: BitmapRegionDecoder
que BitmapRegionDecoder
para cargar BitmapRegionDecoder
está disponible desde Android API de nivel 10, por lo que los usuarios de API 8 y API 9 sólo verán imagen reducida. Decidí que estaba bien.
Código necesario:
- Configuración de la primera (inferior)
ImageView
scaleType="matrix"
(mejor en XML) - Configuración de la segunda (arriba)
ImageView
scaleType="fitXY"
(mejor en XML) - Funciones de la documentación de Android (aquí) – gracias al usuario Vishavjeet Singh .
NOTA: Observe el ||
Operador en lugar de &&
mientras calcula inSampleSize
. Queremos que la imagen cargada sea más pequeña que ImageView
para que tengamos suficiente RAM para cargarla. (Supongo que el tamaño de ImageView
no es mayor que el tamaño de la pantalla del dispositivo.También supongo que el dispositivo tiene suficiente memoria para cargar al menos 2 Bitmaps
del tamaño de la pantalla del dispositivo.Por favor, dígame si estoy cometiendo un error aquí. )
NOTA 2: Estoy cargando imágenes usando InputStream
. Para cargar un archivo de manera diferente tendrá que cambiar el código en try{...} catch(...){...}
bloques.
public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) > reqHeight || (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; } public Bitmap decodeSampledBitmapFromResource(Uri fileUri, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; try { InputStream is = this.getContentResolver().openInputStream(fileUri); BitmapFactory.decodeStream(is, null, options); } catch (Exception e) { e.printStackTrace(); return null; } // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; try { InputStream is = this.getContentResolver().openInputStream(fileUri); return BitmapFactory.decodeStream(is, null, options); } catch (Exception e) { e.printStackTrace(); return null; } }
- Función que devuelve una subimagen de una imagen.
NOTA: El tamaño del rectángulo que se cortará de la imagen de origen es relativo a la imagen. Los valores que lo especifican son de 0 a 1 porque el tamaño de ImageView
y de Bitmap
cargado s difiere del tamaño de la imagen original.
public Bitmap getCroppedBitmap (Uri fileUri, int outWidth, int outHeight, double rl, double rt, double rr, double rb) { // rl, rt, rr, rb are relative (values from 0 to 1) to the size of the image. // That is because image moving will be smaller than the original. if (Build.VERSION.SDK_INT >= 10) { // Ensure that device supports at least API level 10 // so we can use BitmapRegionDecoder BitmapRegionDecoder brd; try { // Again loading from URI. Change the code so it suits yours. InputStream is = this.getContentResolver().openInputStream(fileUri); brd = BitmapRegionDecoder.newInstance(is, true); BitmapFactory.Options options = new BitmapFactory.Options(); options.outWidth = (int)((rr - rl) * brd.getWidth()); options.outHeight = (int)((rb - rt) * brd.getHeight()); options.inSampleSize = calculateInSampleSize(options, outWidth, outHeight); return brd.decodeRegion(new Rect( (int) (rl * brd.getWidth()), (int) (rt * brd.getHeight()), (int) (rr * brd.getWidth()), (int) (rb * brd.getHeight()) ), options); } catch (Exception e) { e.printStackTrace(); return null; } } else return null; }
-
AsyncTask
cargando laAsyncTask
Bitmap
.
NOTA: aviso declarando una variable del tipo de esta clase. Se utilizará más tarde.
private LoadHiResImageTask loadHiResImageTask = new LoadHiResImageTask(); private class LoadHiResImageTask extends AsyncTask<Double, Void, Bitmap> { /** The system calls this to perform work in a worker thread and * delivers it the parameters given to AsyncTask.execute() */ protected Bitmap doInBackground(Double... numbers) { return getCroppedBitmap( // You will have to change first parameter here! Uri.parse(imagesToCrop[0]), numbers[0].intValue(), numbers[1].intValue(), numbers[2], numbers[3], numbers[4], numbers[5]); } /** The system calls this to perform work in the UI thread and delivers * the result from doInBackground() */ protected void onPostExecute(Bitmap result) { ImageView hiresImage = (ImageView) findViewById(R.id.hiresImage); hiresImage.setImageBitmap(result); hiresImage.postInvalidate(); } }
- Función que hará que todos trabajen juntos.
Esta función se llamará cada vez que cambie la propiedad x , y , fw o fh .
NOTA: hiresImage en mi código es el id
del segundo (superior) ImageView
private void updateImageView () { // ... your code to update ImageView matrix ... // // imageToCrop.setImageMatrix(m); // imageToCrop.postInvalidateDelayed(10); if (Build.VERSION.SDK_INT >= 10) { ImageView hiresImage = (ImageView) findViewById(R.id.hiresImage); hiresImage.setImageDrawable(null); hiresImage.invalidate(); if (loadHiResImageTask.getStatus() != AsyncTask.Status.FINISHED) { loadHiResImageTask.cancel(true); } loadHiResImageTask = null; loadHiResImageTask = new LoadHiResImageTask(); loadHiResImageTask.execute( (double) hiresImage.getWidth(), (double) hiresImage.getHeight(), // x, y, fw, fh are properties from the question (double) x / d.getIntrinsicWidth(), (double) y / d.getIntrinsicHeight(), (double) x / d.getIntrinsicWidth() + fw / d.getIntrinsicWidth(), (double) y / d.getIntrinsicHeight() + fh / d.getIntrinsicHeight()); } }
Intente cargar mapa de bits de origen por Opciones de BitmapFactory en solo límites de decodificación
public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; } public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); }
Después de escribir estos 2 métodos
mImageView.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
Set bitmap en imageview como éste
Después intente escalar la imagen que hará para un mapa de bits grande eficientemente
Puedes leer aquí más
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
Como ya dijiste, podrías recortar tu imagen grande y colocarla una por una en la nueva imagen.
Pero desde el otro lado podría sugerir que cortar su gran imagen en varios pequeños y en este caso, probablemente ahorrar memoria y velocidad, porque no se carga toda la imagen en su memoria.
Usted ganará similares que por ejemplo google maps tienen – tienen muchos pequeños azulejos que se cargan por la demanda. No cargan el mapa del mundo entero sino las partes pequeñas de él.
En este caso, se construirá algo como ListView
cuando cada nuevo elemento será una vista de imagen y contiene alguna pequeña imagen que forma parte de una imagen grande. Por cierto, con tal enfoque que podría obtener aún repetidos azulejos de fondo y cambiarlos en tiempo de ejecución.
- Data Binding Android – El parámetro de tipo T tiene límites superiores incompatibles: ViewDataBinding y MainActivity
- Detectar el cambio de ubicación de la URL de webview de Android