【问题标题】:Cannot Display 2 Instances of my custom SurfaceView无法显示我的自定义 SurfaceView 的 2 个实例
【发布时间】:2012-05-08 21:32:51
【问题描述】:

我已经创建了我自己的自定义 SurfaceView,它自己可以正常工作,但是当我尝试将两个放在 TabWidget 中的单独选项卡上时,无论选择哪个选项卡,都只会显示一个,而且它始终是 SurfaceView在应用启动时首次绘制。

为了说明问题,我创建了可以编译以显示问题的示例代码。

下面的SurfaceView,叫做SurfaceViewCircle,只是简单的创建一个位图,默认画一个蓝色的圆圈然后显示出来。有一个公共方法changeColour(),它会改变位图中的圆圈颜色。

其次,我创建了一个 XML 布局,它只包含一个 SurfaceViewCircle 实例。

在 Activity 类中,我创建了一个 TabWidget 和宿主等。然后我将上述 XML 膨胀两次,但在一个实例中,我将 SurfaceViewCircle 的颜色更改为红色。应用程序运行后,无论我选择哪个选项卡,红色圆圈始终显示,除了当应用程序退出并显示蓝色圆圈时的简短实例。

谁能指出我在使用 SurfaceView 时是否遗漏了一个步骤?

这是活动代码:

public class TestActivity extends Activity  {
/** Called when the activity is first created. */

private TabHost mTabHost;
private Context mTabHostContext;
private View surfaceView1, surfaceView2;
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    /*
     * Setup tabs
     */
    setContentView(R.layout.maintabs);
        setupTabHost(); //Prepares the TabHost from code rather than XML;
    mTabHost.getTabWidget().setDividerDrawable(R.drawable.tab_divider); //Sets a thin dividing line
    mTabHostContext = mTabHost.getContext();
    surfaceView1 = LayoutInflater.from(mTabHostContext).inflate(R.layout.surfaceviewindependent, null);
    SurfaceViewCircle s = (SurfaceViewCircle)surfaceView1.findViewById(R.id.circle1);
    /*
     * Change the colour to red
     */
    s.changeColour(getResources().getColor(R.color.red_square));

    /*
     * Create a second layout containing SurfaceViewCircle but leave circle as default blue.
     */
    surfaceView2 = LayoutInflater.from(mTabHostContext).inflate(R.layout.surfaceviewindependent, null);
    setupTab(surfaceView1,"SurfaceView1");
    setupTab(surfaceView2,"SurfaceView2");


}

private void setupTabHost() {
    mTabHost = (TabHost) findViewById(android.R.id.tabhost);
    mTabHost.setup();
}

private void setupTab(final View view, final String tag) {
    View tabview = createTabView(mTabHost.getContext(), tag); // This creates a view to be used in the TAB only

    /* this creates the tab content AND applies the TAB created in the previous step in one go */
    TabSpec setContent = mTabHost.newTabSpec(tag).setIndicator(tabview).setContent(new TabContentFactory() {
        public View createTabContent(String tag) {return view;}
    });
    mTabHost.addTab(setContent);

}

private static View createTabView(final Context context, final String text) {
    View view = LayoutInflater.from(context).inflate(R.layout.tabs_bg, null);
    TextView tv = (TextView) view.findViewById(R.id.tabsText);
    tv.setText(text);

    return view;
}   
}

这是我的自定义 SurfaceView:

public class SurfaceViewCircle extends SurfaceView implements SurfaceHolder.Callback{

private Paint paint, circlePaint;
private Bitmap bitmap = null;
private int w;
private int h;
private int colour = 0;
private Resources r = null;
private _Thread t = null;
private boolean surfaceIsCreated;

public SurfaceViewCircle(Context context) {
    super(context);
    initialise();
}

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

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

private void initialise(){
    r = getResources();
    getHolder().addCallback(this);
    paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setFilterBitmap(true);
    colour = R.color.blue_square;
    circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    circlePaint.setColor(r.getColor(colour));
    circlePaint.setStyle(Style.FILL);
    circlePaint.setStrokeWidth(0.02f);
    t = new _Thread(getHolder());


}

public void changeColour(int colour){
    circlePaint.setColor(colour);
    if (surfaceIsCreated){
        createBitmap();
    }
    synchronized (t){
        t.notify();
    }
}

private Bitmap createBitmap(){
    Bitmap b = null;
    b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(b);
    c.scale((float)w, (float)w);        //Scales the background for whatever pixel size
    c.drawCircle(0.5f, 0.5f, 0.5f, circlePaint);
    //c.drawColor(r.getColor(colour));
    return b;
}

public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
    int width = measure(widthMeasureSpec);
    int height = measure(heightMeasureSpec);

    int d = Math.min(width, height);
    setMeasuredDimension(d,d);
}

private int measure(int measureSpec) {
    int result = 0;
    // Decode the measurement specifications
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    return specSize;
}

@Override
protected void onSizeChanged(int w, int h, int oldW, int oldH){
    super.onSizeChanged(w, h, oldW, oldH);
    //synchronized (this){
        this.w = Math.min(w, h);
        this.h = w;
    //}
    Bitmap b = createBitmap();

        bitmap = b;

    Log.i("Square", "onSizeChanged() called.");


}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
        int height) {
    // TODO Auto-generated method stub

}

@Override
public void surfaceCreated(SurfaceHolder holder) {
    Log.i("Panel", "surfaceCreated() called.");
    t.setRunning(true);
    t.start();
    surfaceIsCreated = true;

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    Log.i("Square", "surfaceDestroyed() called.");

    surfaceIsCreated = false;
    boolean retry = true;
    synchronized (t){
        t.setRunning(false);
        t.notify();
    }
    while (retry) {
        try {
            t.join();
            retry = false;
        } catch (InterruptedException e) {
            // we will try it again and again...
        }
    }

}

private class _Thread extends Thread {
    private SurfaceHolder _surfaceHolder;
    private boolean _run = false;

    public _Thread(SurfaceHolder surfaceHolder) {
        _surfaceHolder = surfaceHolder;
    }

    public void setRunning(boolean run) {
        _run = run;
    }

    @Override
    public void run() {
        Canvas c = null;
        while (_run){
            try {
                c = _surfaceHolder.lockCanvas(null);
                synchronized (_surfaceHolder) {
                    synchronized(bitmap){
                        c.drawBitmap(bitmap, 0, 0, paint);
                    }
                }
            } finally {
                // do this in a finally so that if an exception is thrown
                // during the above, we don't leave the Surface in an
                // inconsistent state
                if (c != null) {
                    _surfaceHolder.unlockCanvasAndPost(c);
                }
            }
            synchronized(this){
                try {
                    wait();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block

                }
            }
        }
    }
}
}

maintabs.xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost" android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <LinearLayout android:orientation="vertical"
        android:layout_width="fill_parent" android:layout_height="fill_parent">
        <TabWidget android:id="@android:id/tabs"
            android:layout_width="fill_parent" android:layout_height="wrap_content"
            android:layout_marginLeft="0dip" android:layout_marginRight="0dip" />
            <FrameLayout android:id="@android:id/tabcontent"
            android:layout_width="fill_parent" android:layout_height="fill_parent" />
    </LinearLayout>
    </TabHost>
</LinearLayout>

还有surfaceviewindependent.xml:

    <?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
<uk.co.androidcontrols.gauges.SurfaceViewCircle
android:id="@+id/circle1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_weight="0.5"
    android:layout_margin="1dip">
</uk.co.androidcontrols.gauges.SurfaceViewCircle>
</LinearLayout>

我还注意到其他人也遇到过类似的问题here.

抱歉格式不佳,但代码编辑器几乎无法用于大代码引号!

其他信息

我尝试在onvisibilityChanged() 中使用setVisibility()',但最终导致异常:

protected void onVisibilityChanged(View changedView, int visibility){
    super.onVisibilityChanged(changedView, visibility);
    changedView.setVisibility(visibility);
    Log.i("SurfaceViewCircle", "onVisibilityChanged() called.");
}

java.lang.IllegalThreadStateException: Thread already started.

似乎调用changedView.setvisibility() 每次都会破坏表面。

【问题讨论】:

标签: android tabs surfaceview


【解决方案1】:

我根据您的代码构建了一个测试项目,并且令人惊讶地花了将近几个小时来摆弄它。我现在要赶紧脱口而出我的发现,因为我应该上阵了!

首先,您肯定会创建两个选项卡,每个选项卡都有一个自定义SurfaceView 的单独实例。没关系。

现在,当Activity 第一次启动并显示第一个选项卡时,只有第一个SurfaceView 被初始化并调用了surfaceCreated(),此时它的Thread 运行。

当第二个选项卡被选中时,createTabContent() 回调为其提供的第二个SurfaceView 然后像第一个一样被初始化。从那时起,直到 Activity 被拆解,both SurfaceViews 仍保持其有效的表面状态。在选项卡之间切换永远不会在SurfaceView 上调用surfaceDestroyed(),因此也永远不会再次调用SurfaceCreated()。首次创建后也不会再次调用“onMeasure()”。因此,这告诉我SurfaceViews 都保留在整个View 层次结构中。两个SurfaceViews'Threads 都在运行,如果你没有wait(),两者都会不断地尝试渲染到视频内存。

如您所知,SurfaceViewView 层次结构中的位置(或者更确切地说,不是)非常独特。这里似乎发生的情况是,要创建的第一个 SurfaceView 是其输出在视频内存上可见的那个,无论选项卡选择如何。

我首先尝试的一件事是让第二个SurfaceView 比第一个小得多,其中的圆圈按比例缩小。当从第一个选项卡(较大的SurfaceView,带有大红色圆圈)切换到第二个选项卡(较小的 SurfaceView,带有较小的蓝色圆圈)时,我可以看到可见 SurfaceView 的大小正确减小,就好像第二个 @ 987654345@ 变得可见,但不是可见其较小的蓝色圆圈,而是第一个 SurfaceView 的大红色圆圈的大部分穿过,但被第二个 SurfaceView 的较小尺寸裁剪。

我最终使用的是以下两个方法调用:

((SurfaceView)surfaceView1.findViewById(R.id.circle1)).setVisibility(View.GONE);

((SurfaceView)view.findViewById(R.id.circle1)).bringToFront();

后者bringToFront() 似乎没有取得任何成果。但是在 first SurfaceView 上使用 setVisibility(View.GONE) 调用就像选择了 second SurfaceView 的选项卡然后让它很好地从红色圆圈切换到一个蓝色圆圈。

我认为您因此需要尝试做的是寻找合适的TabHost API 回调方法来覆盖,在选择选项卡时将调用every(可能使用TabHost.OnTabChangeListener)并将其用作位置在所有SurfaceViews 上酌情调用setVisibility() 以控制哪一个出现在顶部。

【讨论】:

  • 谢谢特雷弗!发布我的问题后,我也必须睡一觉,现在我必须开始工作了!稍后我会仔细查看您的答案。
  • Trevor:有几点需要注意。如果您覆盖 onVisibilityChanged() 方法,您将看到它在选项卡选择更改时被调用。然而,当我写这篇文章时,我意识到,在我的实际“生产”代码(此处未显示)中,我不会调用 super 方法......尽管在上面的示例中我没有覆盖这个方法。此外,如果您在上面的示例中添加了第三个选项卡,其中包含一个标准的 Android 按钮,那么在 Button 和 surfaceview 之间切换是可以的,但在 Surfaceview 之间切换就不行了。我可能会查看 View 类的源代码。
  • 我也曾尝试调用 setVisibility() 方法,但遇到了一些问题,但我可能会再次重新调查此方法。
  • 当我复制项目时,我还添加了另外两个选项卡来制作四个;后两个包含简单的 TextViews。正如您所说,它可以在这些普通组件之间切换就好了,但问题仍然存在于 SurfaceView 之间。 setVisibility() 似乎在我所做的一个简短的简单测试中起到了作用,尽管我没有时间测试完整的修复。
  • 谢谢特雷弗。稍后我会给你的建议。尽管 SurfaceView 的工作方式并不完全正确,或者文档没有明确说明 SurfaceView 的子类应该处理它们自己的可见性设置?我的意思是标准的 Android View 类,例如按钮句柄可见性更改正常,为什么 SurfaceView 不能?很高兴与 Android 开发团队的人澄清这一点。
【解决方案2】:

看来我想用 SurfaceView 做的不推荐:link

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多