Android ImageView.setMatrix() 和 .invalidate() - 重新绘制需要太多时间
Android ImageView.setMatrix() and .invalidate() - repainting takes too much time
任务: 我想调整图像大小并在屏幕上移动图像。无论图像有多大,我都想顺利完成。代码应该API level 8.
支持
问题: 我尝试将 ImageView
与 scaleType="matrix"
一起使用。调用 ImageView.setMatrix()
然后调用 ImageView.invalidate()
对小图像效果很好,但对大图像就很糟糕。不管ImageView
有多大。
我能否以某种方式加快 ImageView
的重绘速度,使其不会重新计算整个图像?也许有一种方法可以使用不同的组件来完成任务?
编辑:关于我想要实现的目标的更多信息。
- pw, ph - 图片的宽度和高度(以像素为单位)
- dw, dh - 设备显示屏的宽度和高度(以像素为单位)
- fw, fh - 可见帧的宽度和高度(以像素为单位)
- x, y - 画面左上角的位置(以像素为单位)
我想在屏幕上显示图像的一部分。属性 x、y、fw 和 fh 正在更改不断地。
我正在寻找代码(idea)的一部分,或者组件,针对这8个指定的变量,可以快速生成并显示图像的一部分。
编辑 2:关于 pw 和 ph
的信息
我假设 pw 和 ph 可以保存从 1 到无穷大的值。如果这种方法造成很多麻烦,我们可以假设图片不大于设备相机拍摄的图片。
尝试在解码范围内通过 BitmapFactory 选项加载源位图
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);
}
写完这2个方法
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
像这样在 imageview 上设置位图
然后尝试有效地缩放图像以用于大型位图
您可以在此处进一步阅读
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
在您(社区)的帮助下,我找到了解决方案。
我确信还有其他更好的方法可以做到这一点,但我的解决方案不是很复杂,应该适用于任何图像,任何 Android 自 API 级别 8.
解决方案是使用两个 ImageView
个对象而不是一个。
第一个 ImageView
将像以前一样工作,但加载的图像将按比例缩小,因此它的宽度将小于 ImageView
的宽度,并且它的高度将小于高度ImageView
.
第二个ImageView
开头是空白的。每次 x、y、fw 和 fh 属性正在更改 AsyncTask
将执行以仅加载图像的可见部分。当属性快速变化时,AsyncTask
将无法及时完成。它必须被取消,新的将开始。完成后,结果 Bitmap
将加载到第二个 ImageView
上,因此用户可以看到。当属性再次改变时加载Bitmap
将被删除,所以它不会覆盖移动Bitmap
加载到第一个ImageView
。 注意: BitmapRegionDecoder
我将使用它来加载子图像,因为 Android API 级别 10,所以 API 8 和API 9 位用户将只能看到按比例缩小的图像。我觉得没问题。
需要代码:
- 设置第一个(下)
ImageView
scaleType="matrix"
(最佳XML)
- 设置第二个(顶部)
ImageView
scaleType="fitXY"
(最佳XML)
- 来自 Android 文档 (here) - thanks to user Vishavjeet Singh 的函数。
注意: 注意在计算 inSampleSize
时使用 ||
运算符而不是 &&
。我们希望加载的图像小于 ImageView
以确保我们有足够的 RAM 来加载它。 (我假设 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
.
注意: 注意声明此 class 类型的变量。后面会用到。
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());
}
}
正如您已经说过的,您可以裁剪大图像并将其一张一张地放置到新的 imageView 中。
但从另一方面来说,我可以建议您将大图像切割成几个小图像,在这种情况下,您可能会节省内存和速度,因为您不会将整个图像加载到内存中。
您将获得类似于 google 地图所具有的功能 - 它们有许多按需加载的小图块。他们不会加载整个世界地图,只会加载其中的一小部分。
在这种情况下,您将构建类似于 ListView
的内容,此时每个新项目都是一个图像视图,并且包含一些作为大图像一部分的小图像。顺便说一句,使用这种方法,您甚至可以获得重复的平铺背景并在运行时更改它们。
任务: 我想调整图像大小并在屏幕上移动图像。无论图像有多大,我都想顺利完成。代码应该API level 8.
支持问题: 我尝试将 ImageView
与 scaleType="matrix"
一起使用。调用 ImageView.setMatrix()
然后调用 ImageView.invalidate()
对小图像效果很好,但对大图像就很糟糕。不管ImageView
有多大。
我能否以某种方式加快 ImageView
的重绘速度,使其不会重新计算整个图像?也许有一种方法可以使用不同的组件来完成任务?
编辑:关于我想要实现的目标的更多信息。
- pw, ph - 图片的宽度和高度(以像素为单位)
- dw, dh - 设备显示屏的宽度和高度(以像素为单位)
- fw, fh - 可见帧的宽度和高度(以像素为单位)
- x, y - 画面左上角的位置(以像素为单位)
我想在屏幕上显示图像的一部分。属性 x、y、fw 和 fh 正在更改不断地。 我正在寻找代码(idea)的一部分,或者组件,针对这8个指定的变量,可以快速生成并显示图像的一部分。
编辑 2:关于 pw 和 ph
的信息我假设 pw 和 ph 可以保存从 1 到无穷大的值。如果这种方法造成很多麻烦,我们可以假设图片不大于设备相机拍摄的图片。
尝试在解码范围内通过 BitmapFactory 选项加载源位图
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);
}
写完这2个方法
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
像这样在 imageview 上设置位图
然后尝试有效地缩放图像以用于大型位图
您可以在此处进一步阅读
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
在您(社区)的帮助下,我找到了解决方案。 我确信还有其他更好的方法可以做到这一点,但我的解决方案不是很复杂,应该适用于任何图像,任何 Android 自 API 级别 8.
解决方案是使用两个 ImageView
个对象而不是一个。
第一个 ImageView
将像以前一样工作,但加载的图像将按比例缩小,因此它的宽度将小于 ImageView
的宽度,并且它的高度将小于高度ImageView
.
第二个ImageView
开头是空白的。每次 x、y、fw 和 fh 属性正在更改 AsyncTask
将执行以仅加载图像的可见部分。当属性快速变化时,AsyncTask
将无法及时完成。它必须被取消,新的将开始。完成后,结果 Bitmap
将加载到第二个 ImageView
上,因此用户可以看到。当属性再次改变时加载Bitmap
将被删除,所以它不会覆盖移动Bitmap
加载到第一个ImageView
。 注意: BitmapRegionDecoder
我将使用它来加载子图像,因为 Android API 级别 10,所以 API 8 和API 9 位用户将只能看到按比例缩小的图像。我觉得没问题。
需要代码:
- 设置第一个(下)
ImageView
scaleType="matrix"
(最佳XML) - 设置第二个(顶部)
ImageView
scaleType="fitXY"
(最佳XML) - 来自 Android 文档 (here) - thanks to user Vishavjeet Singh 的函数。
注意: 注意在计算 inSampleSize
时使用 ||
运算符而不是 &&
。我们希望加载的图像小于 ImageView
以确保我们有足够的 RAM 来加载它。 (我假设 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
.
注意: 注意声明此 class 类型的变量。后面会用到。
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());
}
}
正如您已经说过的,您可以裁剪大图像并将其一张一张地放置到新的 imageView 中。
但从另一方面来说,我可以建议您将大图像切割成几个小图像,在这种情况下,您可能会节省内存和速度,因为您不会将整个图像加载到内存中。
您将获得类似于 google 地图所具有的功能 - 它们有许多按需加载的小图块。他们不会加载整个世界地图,只会加载其中的一小部分。
在这种情况下,您将构建类似于 ListView
的内容,此时每个新项目都是一个图像视图,并且包含一些作为大图像一部分的小图像。顺便说一句,使用这种方法,您甚至可以获得重复的平铺背景并在运行时更改它们。