Ответ 1
С вашей помощью (сообщества) я выяснил решение. Я уверен, что есть и другие способы сделать это, но мое решение не очень сложно и должно работать с любым изображением, любым Android с уровня API 8.
Решение состоит в использовании двух ImageView
объектов вместо одного.
Первый ImageView
будет работать как раньше, но загруженное изображение будет уменьшено, так что ширина будет меньше ширины ImageView
, а высота будет меньше высоты ImageView
.
Второй ImageView
будет пустым в начале. Каждый раз, когда изменяются свойства x, y, fw и fh, AsyncTask
будет выполняться только для загрузки видимая часть изображения. Когда свойства быстро меняются, AsyncTask
не сможет закончить вовремя. Он должен быть отменен, и новый будет запущен. Когда он завершит результат, Bitmap
будет загружен на второй ImageView
, чтобы он был видимым для пользователя. Когда свойства снова будут изменены, Bitmap
будет удален, поэтому он не будет перемещать перенос Bitmap
в первый ImageView
. Примечание. BitmapRegionDecoder
, который я буду использовать для загрузки суб-изображения, доступен с уровня API Android 10, поэтому пользователи API 8 и API 9 будут видеть только уменьшенное изображение. Я решил, что все в порядке.
Необходимый код:
- Установка первого (нижнего)
ImageView
scaleType="matrix"
(лучше всего в XML) - Установка второго (верхнего)
ImageView
scaleType="fitXY"
(лучше всего в XML) - Функции из документации для Android (здесь) - благодаря пользователю Vishavjeet Singh,
ПРИМЕЧАНИЕ. Обратите внимание на оператор ||
вместо &&
при вычислении inSampleSize
. Мы хотим, чтобы загруженное изображение было меньше, чем ImageView
, так что мы уверены, что для его загрузки достаточно ОЗУ. (Я предполагаю, что размер ImageView
не больше размера дисплея устройства. Я также предполагаю, что на устройстве достаточно памяти для загрузки не менее 2 Bitmaps
размера дисплея устройства. Скажите, пожалуйста, ошибка здесь.)
ПРИМЕЧАНИЕ 2: Загружаю изображения с помощью InputStream
. Чтобы загрузить файл по-другому, вам нужно будет изменить код в try{...} catch(...){...}
блоках.
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;
}
}
- Функция, возвращающая суб-изображение изображения.
ПРИМЕЧАНИЕ. Размер прямоугольника, который будет вырезан из исходного изображения, относится к изображению. Значения, определяющие его, составляют от 0 до 1, поскольку размер ImageView
и загруженного Bitmap
отличается от размера исходного изображения.
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
Загрузка суб-изображенияBitmap
.
ПРИМЕЧАНИЕ: уведомление, объявляющее переменную типа этого класса. Он будет использоваться позже.
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();
}
}
- Функция, которая заставит все работать вместе.
Эта функция будет вызываться каждый раз при изменении свойств x, y, fw или fh. < ш > ПРИМЕЧАНИЕ. hiresImage в моем коде - это id
второго (верхнего) 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());
}
}