canvas 旋转后图像模糊,仅在 Android 6

Blurry image after canvas rotate, only in Android 6

我有一个自定义视图,代码如下:

private final Drawable outerGauge;
private final Drawable innerGauge;
private float rotateX;
private float rotateY;
private int rotation = 0;

{
    outerGauge = getContext().getDrawable(R.drawable.gauge_outer);
    innerGauge = getContext().getDrawable(R.drawable.gauge_inner);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    outerGauge.draw(canvas);
    canvas.rotate(rotation, rotateX, rotateY);
    innerGauge.draw(canvas);
    canvas.rotate(-rotation, rotateX, rotateY);
}

大多数情况下,这会生成非常清晰的图像。然而,有时结果看起来像这样: 这似乎只发生在我的两个测试设备之一上。该设备是摩托罗拉 moto G,具有 Android 6 升级。另一个似乎总是能产生非常清晰图像的测试设备是 Oneplus X,Android 5。它也不一致,有时会发生,然后下一刻又不会。根据我的测试,它甚至不依赖于应用的旋转量。不过,我从未见过它发生在直角(0、90、180 度)上,而且在接近 45 或 135 度的角度上它似乎更糟。

有问题的图像是导入的 SVG,直接放在 res/drawable 文件夹中。因此它不可能是决议。 (此外,gauge_outer 被放置在完全相同的文件夹中并以完全相同的方式制作,尽管这个并没有变得模糊。)

关于如何解决这个问题有什么想法吗?


编辑:

好吧,别管我说的完全不一致了。它似乎完全一致,并且当旋转越来越接近 90 度时最差。另外,只要旋转正好 90 度,指示器就会完全消失。


编辑:

看:两个模拟器,一个 运行 Android 5 和一个 运行 Android 6:

完整源码如下:

package nl.dvandenberg.gauge;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;

public class GaugeView extends View {
    private static final int ORIGINAL_ROTATE_Y = 510;
    private static final int ORIGINAL_IMAGE_HEIGHT = 613;
    private static final int ORIGINAL_IMAGE_WIDTH = 1046;
    private final Drawable outerGauge;
    private final Drawable innerGauge;
    private float rotateX;
    private float rotateY;
    private int rotation = 0;

    {
        outerGauge = getContext().getDrawable(R.drawable.gauge_outer);
        innerGauge = getContext().getDrawable(R.drawable.gauge_inner);
    }

    public GaugeView(Context context) {
        super(context);
        setProgress(48);
    }

    public GaugeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setProgress(48);
    }

    public GaugeView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setProgress(48);
    }

    public void setProgress(double percentage) {
        this.rotation = (int) (180 * Math.min(100, Math.max(0, percentage)) / 100);
        invalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        double width = MeasureSpec.getSize(widthMeasureSpec);
        double idealHeight = ORIGINAL_IMAGE_HEIGHT * width / ORIGINAL_IMAGE_WIDTH;
        double height = Math.min(idealHeight, MeasureSpec.getSize(heightMeasureSpec));
        width = width * height / idealHeight;
        heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) height, MeasureSpec.getMode(heightMeasureSpec));

        rotateX = (float) (width / 2f);
        rotateY = (float) (height / ORIGINAL_IMAGE_HEIGHT * ORIGINAL_ROTATE_Y);

        outerGauge.setBounds(0, 0, (int) width, (int) height);
        innerGauge.setBounds(0, 0, (int) width, (int) height);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        outerGauge.draw(canvas);
        canvas.rotate(rotation, rotateX, rotateY);
        innerGauge.draw(canvas);
    }
}

和drawable/gauge_inner.xml

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="1046dp"
    android:height="613dp"
    android:viewportWidth="1046"
    android:viewportHeight="613">

    <path
        android:fillColor="#aa3939"
        android:pathData="M142.541,516.071 C145.053,517.623,156.088,519.334,183.255,522.586
C203.832,525.024,251.438,530.676,289.03,535.184
C326.708,539.641,359.782,543.523,362.537,543.896
C365.292,544.268,388.127,547.018,413.445,550.067 L459.289,555.468
L462.946,560.401 C468.075,567.485,479.691,577.405,489.255,582.968
C499.701,589.062,520.069,594.737,531.817,594.883
C571.623,595.225,607.57,570.083,620.01,533.226
C624.956,518.592,626.123,507.412,624.269,492.201
C622.686,479.259,620.262,472.461,612.212,458.518
C602.012,440.852,592.681,431.69,575.424,422.602
C537.988,402.763,489.163,413.401,462.78,447.108 L458.957,452.086
L449.523,453.146 C444.316,453.727,420.115,456.614,395.829,459.552
C371.456,462.538,346.451,465.429,340.177,466.165
C333.904,466.9,293.067,471.772,249.427,476.991
C205.788,482.211,164.951,487.082,158.678,487.817
C144.122,489.408,139.036,491.998,136.796,498.719
C134.433,505.626,136.72,512.388,142.541,516.07 Z" />
</vector>

和drawable/gauge_outer.xml

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="1046dp"
    android:height="613dp"
    android:viewportWidth="1046"
    android:viewportHeight="613">

    <path
        android:fillColor="#aa3939"
        android:pathData="M488.981,0.56719 C465.882,2.06727,430.783,6.96753,412.984,11.0677
C392.285,15.768,387.285,17.0681,375.285,20.6683
C231.691,63.4706,113.696,164.376,49.898,299.183
C16.6993,369.187,0,444.491,0,523.495
C0,540.296,0.0999961,541.696,1.99992,543.596
C3.99984,545.596,5.29979,545.596,59.4977,545.596
C113.696,545.596,114.996,545.596,116.995,543.596
C118.895,541.696,118.995,540.296,118.995,522.595
C118.995,504.894,118.895,503.494,116.995,501.594
C115.095,499.694,113.695,499.594,85.2962,499.594 L55.6974,499.594
L56.2974,489.793 C60.0973,433.69,76.3966,372.387,101.396,320.384
C103.996,314.984,106.496,310.383,106.896,310.183
C107.396,309.883,110.796,311.483,114.596,313.683
C118.396,315.983,124.396,319.483,127.995,321.583
C131.595,323.583,139.195,328.083,144.994,331.484
C155.694,337.684,159.993,338.884,163.193,336.284
C164.893,334.984,171.293,324.483,177.992,312.083
C183.292,302.282,183.092,299.882,176.492,295.782
C173.992,294.282,162.593,287.582,151.093,281.081 L130.294,269.08 L135.294,261.58
C166.593,214.877,210.691,170.375,258.589,137.273
C268.189,130.673,269.889,129.873,270.489,131.273
C272.389,136.273,298.388,179.776,299.988,180.576
C300.988,181.176,302.788,181.576,303.888,181.576
C306.288,181.576,334.787,165.275,336.787,162.775
C339.187,159.575,337.987,155.575,330.887,143.274
C326.987,136.574,322.987,129.773,322.087,128.273
C321.187,126.673,318.087,121.273,315.287,116.372
C312.387,111.372,309.987,107.072,309.987,106.671
C309.987,105.371,342.586,90.7702,360.385,84.0698
C388.684,73.5692,427.382,63.5687,455.981,59.6685
C468.68,57.8684,490.98,55.5683,495.579,55.5683 L499.979,55.5683 L499.979,85.0699
C499.979,113.271,500.079,114.671,501.979,116.572
C503.879,118.472,505.279,118.572,522.978,118.572
C540.677,118.572,542.077,118.472,543.977,116.572
C545.877,114.672,545.977,113.272,545.977,84.8703 L545.977,55.2687
L555.977,55.9687 C581.776,57.5688,617.875,63.7691,644.874,71.0695
C670.273,77.9699,702.072,89.7705,722.771,99.871
C729.071,102.971,734.671,105.671,735.271,105.871
C735.871,106.071,730.171,117.072,722.172,131.072
C713.772,145.773,707.973,156.973,707.973,158.573
C707.973,162.273,709.373,163.573,718.973,169.274
C741.272,182.375,743.072,183.075,746.772,179.775
C748.472,178.375,765.571,149.773,773.871,134.373 L776.471,129.773
L787.471,137.373 C834.969,170.075,877.067,212.377,910.266,260.98
C912.866,264.78,914.866,268.28,914.766,268.78
C914.566,269.28,903.866,275.78,890.967,283.181
C878.068,290.581,866.668,297.582,865.768,298.782
C862.268,302.782,863.268,305.182,878.268,330.084
C884.168,339.785,886.468,339.885,900.967,331.484
C906.767,328.084,914.366,323.584,917.966,321.583
C921.566,319.483,927.566,315.983,931.365,313.683
C935.265,311.383,938.565,309.583,938.865,309.583
C939.565,309.583,946.665,324.184,952.164,337.084
C972.463,383.986,986.363,440.49,989.663,489.792 L990.263,499.592
L960.664,499.592 C932.265,499.592,930.865,499.692,928.965,501.592
C927.065,503.492,926.965,504.892,926.965,522.593
C926.965,540.294,927.065,541.694,928.965,543.594
C930.965,545.594,932.265,545.594,986.463,545.594
C1041.86,545.594,1041.96,545.594,1044.06,543.494
C1046.26,541.294,1046.26,540.994,1045.66,513.192
C1044.76,470.69,1040.36,436.088,1031.36,398.586
C1027.46,382.685,1026.86,380.485,1020.26,360.084
C1009.06,325.382,990.461,284.58,971.762,253.578
C923.864,174.276,855.866,108.873,775.07,64.3706
C712.572,29.8688,645.075,8.96764,574.477,2.06727
C555.278,0.16716,507.68,-0.63288,488.981,0.56719 Z" />
</vector>

虽然不是答案,但我设法找到了解决方法。此解决方法依赖于将图像绘制到链接到位图的 canvas 上,然后在 onDraw 方法中将其绘制到最终旋转的 canvas 上。

似乎这个问题真的只出现在 nodpi-drawables 上,换句话说,导入的 svg。然而,它非常一致。无论形状是多路径矢量还是简单的正方形都没有关系,问题总是采用完全相同的形状,当 canvas 旋转 90° 时图像完全消失。

我用来绕过这个问题的完整代码如下:

package nl.dvandenberg.energymonitor.customViews;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;

import nl.dvandenberg.energymonitor.R;

public class GaugeView extends View {
    private static final int ORIGINAL_ROTATE_Y = 510;
    private static final int ORIGINAL_IMAGE_HEIGHT = 613;
    private static final int ORIGINAL_IMAGE_WIDTH = 1046;
    private final Drawable outerGauge, innerGauge;
    private float rotateX;
    private float rotateY;
    private int rotation = 0;

    private Bitmap innerGaugeBitmap;

    private final Canvas innerGaugeCanvas;

    {
        outerGauge = getContext().getDrawable(R.drawable.gauge_outer);
        innerGauge = getContext().getDrawable(R.drawable.gauge_inner);
        innerGaugeCanvas = new Canvas();
    }

    public GaugeView(Context context) {
        super(context);
    }

    public GaugeView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public GaugeView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setProgress(double percentage) {
        this.rotation = (int) (180 * Math.min(100, Math.max(0, percentage)) / 100);
        invalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        double width = MeasureSpec.getSize(widthMeasureSpec);
        double idealHeight = ORIGINAL_IMAGE_HEIGHT * width / ORIGINAL_IMAGE_WIDTH;
        double height = Math.min(idealHeight, MeasureSpec.getSize(heightMeasureSpec));
        width = width * height / idealHeight;
        heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) height, MeasureSpec.getMode(heightMeasureSpec));

        rotateX = (float) (width / 2f);
        rotateY = (float) (height / ORIGINAL_IMAGE_HEIGHT * ORIGINAL_ROTATE_Y);

        outerGauge.setBounds(0, 0, (int) width, (int) height);
        innerGauge.setBounds(0, 0, (int) width, (int) height);

        if (innerGaugeBitmap != null){
            innerGaugeBitmap.recycle();
        }
        innerGaugeBitmap = Bitmap.createBitmap((int) width, (int) height, Bitmap.Config.ARGB_8888); // Gives LINT-warning draw-allocation, but no other way to upscale bitmaps exists.
        innerGaugeCanvas.setBitmap(innerGaugeBitmap);
        innerGaugeBitmap.eraseColor(Color.TRANSPARENT);
        innerGauge.draw(innerGaugeCanvas);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        outerGauge.draw(canvas);
        canvas.rotate(rotation, rotateX, rotateY);
        canvas.drawBitmap(innerGaugeBitmap,0,0,null);
    }
}

重要的部分出现在 onMeasure 方法中:

        if (innerGaugeBitmap != null){
            innerGaugeBitmap.recycle();
        }
        innerGaugeBitmap = Bitmap.createBitmap((int) width, (int) height, Bitmap.Config.ARGB_8888); // Gives LINT-warning draw-allocation, but no other way to upscale bitmaps exists.
        innerGaugeCanvas.setBitmap(innerGaugeBitmap);
        innerGaugeBitmap.eraseColor(Color.TRANSPARENT);
        innerGauge.draw(innerGaugeCanvas);

我已在 https://code.google.com/p/android/issues/detail?id=208453

提交错误报告