通过您(社区)的帮助,我找到了解决方案。我相信还有其他更好的方法来解决这个问题,但我的解决方案并不复杂,并且应该适用于任何图像和任何API级别为8及以上的Android系统。
解决方案是使用两个
ImageView
对象,而不是一个。
第一个
ImageView
将像以前一样工作,但加载的图像将被缩小,使其宽度小于
ImageView
的宽度,高度小于
ImageView
的高度。
第二个
ImageView
在开始时为空白的。每次
x、
y、
fw和
fh属性改变时,
AsyncTask
将被执行,仅加载图像的可见部分。当属性快速变化时,
AsyncTask
将无法及时完成。它将被取消并启动新的任务。当它完成后,结果
Bitmap
将加载到第二个
ImageView
上,因此用户可以看到它。当属性再次更改时,已加载的
Bitmap
将被删除,因此它不会覆盖加载到第一个
ImageView
的移动
Bitmap
。
注意:我将用于加载子图像的
BitmapRegionDecoder
自Android API级别10开始提供,因此API 8和API 9用户将只看到缩小的图像。我认为这是可以接受的。
所需代码:
- 将第一个(底部)
ImageView
设置为
scaleType="matrix"
(最好在XML中)
- 将第二个(顶部)
ImageView
设置为
scaleType="fitXY"
(最好在XML中)
- Android文档中的函数
(在此处) - 感谢用户
Vishavjeet Singh。
注意:在计算inSampleSize
时,使用的是||
运算符,而不是&&
。我们希望加载的图像比ImageView
小,以确保我们有足够的RAM来加载它。(我假设ImageView
的大小不大于设备显示器的大小。我还假设设备具有足够的内存来加载至少2个与设备显示器大小相同的Bitmaps
。如果我在这里犯了错误,请告诉我。)
注意2:我正在使用InputStream
加载图像。要以不同的方式加载文件,您需要更改try{...} catch(...){...}
块中的代码。
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
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;
while ((halfHeight / inSampleSize) > reqHeight
|| (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public Bitmap decodeSampledBitmapFromResource(Uri fileUri,
int reqWidth, int reqHeight) {
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;
}
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
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) {
if (Build.VERSION.SDK_INT >= 10) {
BitmapRegionDecoder brd;
try {
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> {
protected Bitmap doInBackground(Double... numbers) {
return getCroppedBitmap(
Uri.parse(imagesToCrop[0]),
numbers[0].intValue(), numbers[1].intValue(),
numbers[2], numbers[3], numbers[4], numbers[5]);
}
protected void onPostExecute(Bitmap result) {
ImageView hiresImage = (ImageView) findViewById(R.id.hiresImage);
hiresImage.setImageBitmap(result);
hiresImage.postInvalidate();
}
}
每当x、y、fw或fh属性更改时,都会调用此函数。
注意: 我的代码中的hiresImage是第二个(顶部)ImageView
的id
。
private void updateImageView () {
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(),
(double) x / d.getIntrinsicWidth(),
(double) y / d.getIntrinsicHeight(),
(double) x / d.getIntrinsicWidth()
+ fw / d.getIntrinsicWidth(),
(double) y / d.getIntrinsicHeight()
+ fh / d.getIntrinsicHeight());
}
}
BitmapFactory
加载图像,以便结果位图的宽度不大于显示器的宽度,结果位图的高度不大于显示器的高度。2.每当x、y、fw或fh更改时,启动(相同的)异步进程,生成所选区域的位图。如果该进程已经在工作,请使用其他参数重新启动它。3.如果该进程恰好完成,则将结果显示给用户,覆盖第一个位图。3b 当x、y、fw、fh更改时,请清除生成的位图。 - Filip Hazubski