Android:SurfaceView 闪烁

Android: SurfaceView Flickering

我在绘制 SurfaceView 时遇到问题,当移动 SurfaceView 上的位图图像时,它们会闪烁(或撕裂)。我在之前的代码迭代中没有遇到过这个问题。但是现在我终于通过为每个位图使用单独的 Canvas 使位图正确缩放,这个问题开始出现。这些是我自定义的重要部分 SurfaceView class:

public class DrawingSurface extends SurfaceView implements SurfaceHolder.Callback {

    public DrawingSurface(Context context, AttributeSet attributeSet, int defStyle) {
        super(context, attributeSet, defStyle);
        initialize(context);
    }

    private void initialize(Context context) {
        mContext = context;
        getHolder().addCallback(this);
        setFocusable(true);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mHost = (EditorActivity) mContext;
        Log.d("DrawingSurface", "SURFACE CREATED");
        mDrawingThread = new DrawingThread(this, REFRESH_RATE);
        mDrawingThread.setRunning(true);
        mDrawingThread.start();
        onConfigurationChanged(getResources().getConfiguration());
    }

    public Bitmap createBitmap() {
        Bitmap bitmap = Bitmap.createBitmap(mBitmap);
        Canvas canvas = new Canvas(bitmap);
        Sticker[] stickers = mStickers.toArray(new Sticker[mStickers.size()]);
        for (Sticker sticker : stickers) {
            canvas.drawBitmap(sticker.getBitmap(), new Matrix(), mPaint);
        } return bitmap;
    }

    public void drawSurface(Canvas canvas) {
        Bitmap bitmap = createBitmap();
        mMatrix.setScale(1.0f / mScaleFactor, 1.0f / mScaleFactor);
        canvas.drawBitmap(bitmap, mMatrix, mPaint);
    }

    private void setScaleFactor() {
         mScaleFactor = ((float) mBitmap.getWidth()) / getWidth();
    }

    public void addSticker(int drawableId) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inScaled = false;
        Sticker sticker = new Sticker(
                BitmapFactory.decodeResource(getResources(), drawableId, options),
                new PointF(mBitmap.getWidth(), mBitmap.getHeight()),
                mScaleFactor);
        mActiveSticker = sticker;
        mStickers.add(sticker);
    }
}

这是我的习惯Thread:

public class DrawingThread extends Thread {

    private volatile boolean mRunning = false;

    private long mRefreshRate;
    private DrawingSurface mSurface;

    public DrawingThread (DrawingSurface surface, long time) {
        super();
        mSurface = surface;
        mRefreshRate = time;
    }

    public void setRunning (boolean run) {
        mRunning = run;
        Log.d("DrawingThread", "Running: " + mRunning);
    }

    @Override
    public void run() {
        while (mRunning) {
            try {
                sleep(mRefreshRate);
                onSurfaceUpdate();
            } catch (InterruptedException exception) {
                exception.printStackTrace();
            }
        }
    }

    public void onSurfaceChanged(Configuration config, Point fit, float ratio) {
        float width, height;
        if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            width = fit.y * ratio;
            height = fit.y;
        } else if (config.orientation == Configuration.ORIENTATION_PORTRAIT) {
            width = fit.x;
            height = fit.x / ratio;
        } else {
            width = fit.x;
            height = fit.x / ratio;
        } mSurface.getHolder().setFixedSize((int) width, (int) height);
    }

    private void onSurfaceUpdate() {
        Canvas canvas = null;
        try {
            canvas = mSurface.getHolder().lockCanvas();
            synchronized (mSurface.getHolder()) {
                if (canvas != null) {
                    mSurface.drawSurface(canvas);
                }
            }
        } finally {
            if (canvas != null) {
                mSurface.getHolder().unlockCanvasAndPost(canvas);
            }
        }
    }
}

和贴纸 class 用来分别固定 CanvasBitmap:

public class Sticker {
    private static final float START_SCALE = 0.5f;
    private static final float MIN_SCALE = 0.3f;
    private static final float MAX_SCALE = 7f;

    private float mScale = 1f;
    private float mScaleFactor;

    private Canvas mCanvas = new Canvas();
    private Bitmap mSticker;
    private Bitmap mSurface;

    private PointF mCenter = new PointF();

    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Matrix mMatrix = new Matrix();

    public Sticker(Bitmap sticker, PointF size, float scaleFactor) {
        mSticker = sticker;

        mSurface = Bitmap.createBitmap((int) size.x, (int) size.y, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mSurface);
        mCanvas.drawColor(0, PorterDuff.Mode.CLEAR);

        setScaleFactor(scaleFactor);
        postSticker();
    }

    private void postSticker() {
        mMatrix.postScale(START_SCALE, START_SCALE);
        setCenter();
        setTranslate(mCenter.x, mCenter.y);
    }

    public boolean collider(PointF point) {
        int x = (int) (point.x * mScaleFactor);
        int y = (int) (point.y * mScaleFactor);
        int color = mSurface.getPixel(x, y);

        if(color != Color.TRANSPARENT) {
            return true;
        } else {
            return false;
        }
    }

    public Bitmap getBitmap() {
        return mSurface;
    }

    public void flipSticker() {
        Matrix matrix = new Matrix();
        matrix.preScale(-1, 1);
        mSticker = Bitmap.createBitmap(mSticker, 0, 0,
                mSticker.getWidth(), mSticker.getHeight(), matrix, false);
    }

    public void setScaleFactor(float scaleFactor) {
        mScaleFactor = scaleFactor;
    }

    public void setTranslate(float deltaX, float deltaY) {
        mMatrix.postTranslate(deltaX * mScaleFactor, deltaY * mScaleFactor);
        draw();
    }

    public void setScale(float deltaScale, PointF midpoint) {
        mScale *= deltaScale;
        if(MIN_SCALE < mScale && mScale < MAX_SCALE) {
            mMatrix.postScale(deltaScale, deltaScale,
                    midpoint.x * mScaleFactor, midpoint.y * mScaleFactor);
        } draw();
    }

    public void setRotate(float deltaRotate, PointF midpoint) {
        mMatrix.postRotate(deltaRotate, midpoint.x * mScaleFactor, midpoint.y * mScaleFactor);
        draw();
    }

    private void setCenter() {
        float width = (mSurface.getWidth() / 2) - ((mSticker.getWidth() / 2) * START_SCALE);
        float height = (mSurface.getHeight() / 2) - ((mSticker.getHeight() / 2) * START_SCALE);
         mCenter.set(width, height);
    }

    private void draw() {
        mCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
        mCanvas.drawBitmap(mSticker, mMatrix, mPaint);
    }
}

我在 Whosebug 以及其他网站上发现了关于同一主题的各种讨论。他们都得出结论,闪烁是由于 SurfaceView 如何处理缓冲,使用两个表面交换。解决方案包括使用单独的位图首先绘制所有图像,然后将该位图绘制到 SurfaceView。如您所见,我已在 createBitmap 方法中完成此操作,但闪烁仍然存在。

我认为闪烁是由于 CanvascreateBitmap() 中使用位图之前未完成位图绘制。我将方法 getBitmap() 更改为 return mSticker 而不是更大的 mSurface (用于检测用户触摸的位置),因此绘制的位图是没那么大。我将方法 getMatrix() 添加到每个 Sticker 的 return 和 mMatrix,并将其用于 createBitmap() 中的 canvas.drawBitmap() 方法。这阻止了闪烁,即使我用于贴纸的 png 文件相当大(大多数是 1024 x 1024)。