繁体   English   中英

Android ImageView.setMatrix()和.invalidate() - 重新绘制需要花费太多时间

[英]Android ImageView.setMatrix() and .invalidate() - repainting takes too much time

任务:我想调整大小并在屏幕上移动图像。 无论图像有多大,我都想顺利地做到这一点。 API级别8应该支持该代码。

问题:我尝试使用带有scaleType="matrix" ImageView 调用ImageView.setMatrix()然后ImageView.invalidate()的伟大工程的小图片,但可怕的与大的。 无论ImageView有多大。

我可以以某种方式加速重新绘制ImageView以便它不会重新计算整个图像? 也许有一种方法可以使用不同的组件完成任务?


编辑:有关我想要实现的更多信息。

  • pw,ph - 图片的宽度和高度(以像素为单位)
  • dw,dh - 设备显示的宽度和高度(以像素为单位)
  • fw,fh - 可见帧的宽度和高度(以像素为单位)
  • x,y - 框架左上角的位置(以像素为单位) 可视化问题。

我想在屏幕上显示图像的一部分。 属性xyfwfh不断变化。 我正在寻找代码的一部分(想法),或者为这8个指定变量快速生成并显示图像部分的组件。


编辑2:关于pwph的信息

我假设pwph可以保持从1到无穷大的值。 如果这种方法造成很多麻烦,我们可以假设图片不比用设备的相机拍摄的图片大。

在您的帮助下(社区),我找到了解决方案。 我确信还有其他更好的方法可以做到这一点,但我的解决方案不是很复杂,应该适用于任何图像,任何Android自API等级8。

解决方案是使用两个ImageView对象而不是一个。

第一个ImageView将像以前一样工作,但加载的图像将按比例缩小,以使其宽度小于ImageView的宽度,并且它的高度将小于ImageView的高度。

第二个ImageView在开始时将为空白。 每当xyfwfh属性发生变化时, 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) {
    // 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();
        }
    }
  • 功能将使它全部协同工作。

每次xyfwfh属性发生变化时,都会调用此函数。
注意:我的代码中的hiresImage是第二个(顶部) ImageViewid

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());
        }
    }

尝试在解码边界中通过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);
}

写完这两种方法之后

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

像这样在imageview上设置位图

然后尝试缩放它将有效地为大位图做的图像

你可以在这里进一步阅读

http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

正如您已经说过的那样,您可以裁剪大图像并将其逐个放置到新的imageView中。

但是从另一方面我可以建议你将你的大图像切成几个小图像,在这种情况下你可能会节省内存和速度,因为你不会将整个图像加载到你的记忆中。

您将获得与谷歌地图相似的东西 - 它们有许多按要求加载的小块。 它们不会加载整个世界地图,而是加载它的一小部分。

在这种情况下,当每个新项目都是一个imageview并包含一些大图像的小图像时,你将构建像ListView这样的东西。 顺便说一句,通过这种方法,您甚至可以获得重复的平铺背景并在运行时更改它们。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM