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

标签 java android imageview invalidation scaletype

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

问题:我尝试将 ImageViewscaleType="matrix" 一起使用。调用 ImageView.setMatrix() 然后调用 ImageView.invalidate() 对小图像效果很好,但对大图像就很糟糕。无论 ImageView 有多大。

我能否以某种方式加快 ImageView 的重绘速度,使其不会重新计算整个图像?也许有一种方法可以使用不同的组件来完成任务?


编辑:关于我想要实现的目标的更多信息。

  • pw, ph - 图片的宽度和高度(以像素为单位)
  • dw, dh - 设备显示屏的宽度和高度(以像素为单位)
  • fw, fh - 可见帧的宽度和高度(以像素为单位)
  • x, y - 框架左上角的位置(以像素为单位) Visualisation of the problem.

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


编辑 2:关于 pwph

的信息

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

最佳答案

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

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

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

第二个 ImageView 开始时是空白的。每当 xyfwfh 属性发生变化时,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) - 感谢用户Vishavjeet Singh .

注意:在计算 inSampleSize 时,请注意 || 运算符而不是 &&。我们希望加载的图像小于 ImageView 以便我们确定我们有足够的 RAM 来加载它。 (我假设 ImageView 大小不大于设备显示的大小。我还假设设备有足够的内存来加载至少 2 个 Bitmap设备显示。如果我在这里犯了错误,请告诉我。)
注意 2: 我正在使用 InputStream 加载图像。要以不同的方式加载文件,您必须更改 try{...} catch(...){...} block 中的代码。

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 属性更改时都会调用此函数。< br/> 注意: hiresImage 在我的代码中是第二个(顶部)ImageView

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

关于java - Android ImageView.setMatrix() 和 .invalidate() - 重新绘制需要太多时间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32937763/

相关文章:

android - ImageView 背景太大

android - 在Android Studio中找不到约束布局

android - 缩放 ImageView 以适应 ToolBar 并重叠

java - Selenium/Java - 使用 PLUpload 上传文件

java - 非 UTF 8 编码的 CSV 特殊字符问题

android - 如何使用模糊库获取直接位图结果

java - lang.ClassCastException : java. lang.Integer android xml rpc

android - 在android中拖动和缩放多个图像

java - 使用线程插入mysql

java - 使用 SHA1 和 DSA 的数字签名