问题
I am having an issue with drawing to a SurfaceView
, when the bitmap images on the SurfaceView
are moved, they flicker (or tear). I did not have this problem in previous iterations of my code. But now that I finally got the bitmaps to scale properly by using a separate Canvas
for each bitmap, this problem started occurring. These are the important parts of my custom 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);
}
}
This is my custom 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);
}
}
}
}
And the sticker class used to hold each separate Canvas
and Bitmap
:
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);
}
}
I have found various discussions on the same topic on StackOverflow, as well as other sites. All of them conclude that the flickering is due to how the SurfaceView
handles buffering, using two surfaces to swap between. The solution consists of using a separate bitmap to draw all of the images to first, then drawing that bitmap to the SurfaceView
. As you can see, I have done this in my createBitmap
method, yet the flickering persists.
回答1:
I think the flickering was due to the Canvas
drawing to the bitmap not being finished before it gets used in createBitmap()
. I altered the method getBitmap()
to return mSticker
instead of the much larger mSurface
(which is used to detect where the user is touching), so that the bitmap being drawn was not so large. And I added the method getMatrix()
to return the mMatrix
from each Sticker
, and used that for the canvas.drawBitmap()
method in createBitmap()
. This stopped the flickering, even though the png files I use for my stickers are fairly large (most are 1024 x 1024).
来源:https://stackoverflow.com/questions/32896471/android-surfaceview-flickering