3

为了显示带有移动对象(来自位图)和触摸事件的视图,我一直SurfaceView在 Android 中使用以下代码。它在我的开发设备上一直运行良好,但事实证明,很多用户只是看到一个黑盒子代替了View. 经过很长时间(不成功)的调试,我得出的结论是它必须是 Android 4.1 导致SurfaceView停止正常工作。

我的开发设备是Android 4.0,但抱怨黑人的用户只有SurfaceViewAndroid 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);
4

1 回答 1

-1

将您的绘图方法重命名为 onDraw2()。更改线程代码以调用 onDraw2。这样,您就不会覆盖基类的 ondraw。我认为您可能会在 onDraw 中获得 2 次点击。一个来自基类覆盖,一个来自线程。

这可以解释为什么设置 z 顺序会有所帮助。您将颠倒 2 个窗口的绘制顺序,从而避免该问题。至于问题的“为什么现在”部分。由于您有 2 条通往 onDraw 的途径,我怀疑这是不受支持的 android 行为,所以不知道会发生什么。

我还看到您启用了 setDrawingCache。我不认为这对你有帮助。通常你会在某个时候调用 getDrawingCache。如果它不重要,请尝试删除它。

我看到的唯一另一件事是您创建了线程并在创建的表面中传递了支架。您可能希望在 surfaceChanged 发生时采取措施,或者至少验证没有任何重要的变化。

于 2013-02-02T16:44:14.143 回答