为了显示带有移动对象(来自位图)和触摸事件的视图,我一直SurfaceView
在 Android 中使用以下代码。它在我的开发设备上一直运行良好,但事实证明,很多用户只是看到一个黑盒子代替了View
. 经过很长时间(不成功)的调试,我得出的结论是它必须是 Android 4.1 导致SurfaceView
停止正常工作。
我的开发设备是Android 4.0,但抱怨黑人的用户只有SurfaceView
Android 4.1。用 Android 4.1 模拟器检查过 - 它也不能在那里工作。
你能看出代码有什么问题吗?可能是由 Android 4.1 中的“Project Butter”引起的吗?
当然,我已经检查过这些Bitmap
对象是否有效(以适当的行将它们保存到 SD 卡中),并且所有绘图方法也会定期调用 - 那里一切正常。
package com.my.package.util;
import java.util.ArrayList;
import java.util.List;
import com.my.package.Card;
import com.my.package.MyApp;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MySurface extends SurfaceView implements SurfaceHolder.Callback {
private MyRenderThread mRenderThread;
private volatile List<Card> mGameObjects;
private volatile int mGameObjectsCount;
private int mScreenWidth;
private int mScreenHeight;
private int mGameObjectWidth;
private int mGameObjectHeight;
private int mHighlightedObject = -1;
private Paint mGraphicsPaint;
private Paint mShadowPaint;
private Rect mDrawingRect;
private int mTouchEventAction;
private Bitmap bitmapToDraw;
private int mOnDrawX1;
private BitmapFactory.Options bitmapOptions;
// ...
public MySurface(Context activityContext, AttributeSet attributeSet) {
super(activityContext, attributeSet);
getHolder().addCallback(this);
setFocusable(true); // touch events should be processed by this class
mGameObjects = new ArrayList<Card>();
mGraphicsPaint = new Paint();
mGraphicsPaint.setAntiAlias(true);
mGraphicsPaint.setFilterBitmap(true);
mShadowPaint = new Paint();
mShadowPaint.setARGB(160, 20, 20, 20);
mShadowPaint.setAntiAlias(true);
bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inInputShareable = true;
bitmapOptions.inPurgeable = true;
mDrawingRect = new Rect();
}
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { }
public void surfaceCreated(SurfaceHolder arg0) {
mScreenWidth = getWidth();
mScreenHeight = getHeight();
mGameObjectHeight = mScreenHeight;
mGameObjectWidth = mGameObjectHeight*99/150;
mCurrentSpacing = mGameObjectWidth;
setDrawingCacheEnabled(true);
mRenderThread = new MyRenderThread(getHolder(), this);
mRenderThread.setRunning(true);
mRenderThread.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
mRenderThread.setRunning(false); // stop thread
while (retry) { // wait for thread to close
try {
mRenderThread.join();
retry = false;
}
catch (InterruptedException e) { }
}
}
public void stopThread() {
if (mRenderThread != null) {
mRenderThread.setRunning(false);
}
}
@Override
public void onDraw(Canvas canvas) {
if (canvas != null) {
synchronized (mGameObjects) {
mGameObjectsCount = mGameObjects.size();
canvas.drawColor(Color.BLACK);
if (mGameObjectsCount > 0) {
mCurrentSpacing = Math.min(mScreenWidth/mGameObjectsCount, mGameObjectWidth);
for (int c = 0; c < mGameObjectsCount; c++) {
if (c != mHighlightedObject) {
try {
drawGameObject(canvas, mGameObjects.get(c).getDrawableID(), false, c*mCurrentSpacing, c*mCurrentSpacing+mGameObjectWidth);
}
catch (Exception e) { }
}
}
if (mHighlightedObject > -1) {
mOnDrawX1 = Math.min(mHighlightedObject*mCurrentSpacing, mScreenWidth-mGameObjectWidth);
try {
drawGameObject(canvas, mGameObjects.get(mHighlightedObject).getDrawableID(), true, mOnDrawX1, mOnDrawX1+mGameObjectWidth);
}
catch (Exception e) { }
}
}
}
}
}
private void drawGameObject(Canvas canvas, int resourceID, boolean highlighted, int xLeft, int xRight) {
if (canvas != null && resourceID != 0) {
try {
if (highlighted) {
canvas.drawRect(0, 0, mScreenWidth, mScreenHeight, mShadowPaint);
}
bitmapToDraw = MyApp.gameObjectCacheGet(resourceID);
if (bitmapToDraw == null) {
bitmapToDraw = BitmapFactory.decodeResource(getResources(), resourceID, bitmapOptions);
MyApp.gameObjectCachePut(resourceID, bitmapToDraw);
}
mDrawingRect.set(xLeft, 0, xRight, mGameObjectHeight);
canvas.drawBitmap(bitmapToDraw, null, mDrawingRect, mGraphicsPaint);
}
catch (Exception e) { }
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
synchronized (mRenderThread.getSurfaceHolder()) { // synchronized so that there are no concurrent accesses
mTouchEventAction = event.getAction();
if (mTouchEventAction == MotionEvent.ACTION_DOWN || mTouchEventAction == MotionEvent.ACTION_MOVE) {
if (event.getY() >= 0 && event.getY() < mScreenHeight) {
mTouchEventObject = (int) event.getX()/mCurrentSpacing;
if (mTouchEventObject > -1 && mTouchEventObject < mGameObjectsCount) {
mHighlightedObject = mTouchEventObject;
}
else {
mHighlightedObject = -1;
}
}
else {
mHighlightedObject = -1;
}
}
else if (mTouchEventAction == MotionEvent.ACTION_UP) {
if (mActivityCallback != null && mHighlightedObject > -1 && mHighlightedObject < mGameObjectsCount) {
try {
mActivityCallback.placeObject(mGameObjects.get(mHighlightedObject));
}
catch (Exception e) { }
}
mHighlightedObject = -1;
}
}
return true;
}
// ...
}
这是定期调用SurfaceView
's的线程的代码onDraw()
:
package com.my.package.util;
import android.graphics.Canvas;
import android.view.SurfaceHolder;
public class MyRenderThread extends Thread {
private SurfaceHolder mSurfaceHolder;
private MySurface mSurface;
private boolean mRunning = false;
public MyRenderThread(SurfaceHolder surfaceHolder, MySurface surface) {
mSurfaceHolder = surfaceHolder;
mSurface = surface;
}
public SurfaceHolder getSurfaceHolder() {
return mSurfaceHolder;
}
public void setRunning(boolean run) {
mRunning = run;
}
@Override
public void run() {
Canvas c;
while (mRunning) {
c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
if (c != null) {
mSurface.onDraw(c);
}
}
}
finally { // when exception is thrown above we may not leave the surface in an inconsistent state
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
SurfaceView
包含在我的Activity
布局 XML 中:
<com.my.package.util.MySurface
android:id="@+id/my_surface"
android:layout_width="fill_parent"
android:layout_height="@dimen/my_surface_height" />
然后在代码中访问它是这样的:
MySurface mySurface = (MySurface) findViewById(R.id.my_surface);