2012年5月15日火曜日

SurfaceViewと、それ以外のViewとの、Z軸上での混在

SurfaceViewと、それ以外のViewとの、Z軸上での混在

問題点
SurfaceViewと、それ以外のViewとを、各々半透明にして、Z軸上で重ねた場合、それらの色は混じり合わず、どちらか一方の半透明色しか表示されない。
「それ以外のView」には、別のインスタンスとしてのSurfaceViewも含まれると思われる。
参考:
Androidのカメラの表示優先で質問です
GLSurfaceViewや、SurfaceViewを複数使った場合に順番がおかしくなる。
GLSurfaceViewの背景を透過させる
セカイカメラ開発から見たAndroidアプリケーション開発の現状とAndroid内部構造

再現方法
SurfaceView以外のViewとして、この記事ではRelativeLayoutを用いた。
このRelativeLayoutを半透明にして、背景にある壁紙を表示する。具体的方法は次のとおりである。

AndroidManifest.xmlに次の1行を加える。これにより、これが設定されたActivityは透明や半透明に設定できるようになる。
android:theme="@android:style/Theme.Translucent"

レイアウト用xmlは次のとおりとする。名前はmain.xmlである。このxmlでは、半透明の値として"#880000ff"を設定する。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/MainParent"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#880000ff"
    >
    
    <!-- このTextViewはSurfaceViewが重なると表示されない。 -->
    <TextView
        android:text="@string/app_name"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textColor="#ff00ff00"
        >
    </TextView>
</RelativeLayout>

上記の設定で、ActivityのonCreateメソッド内でsetContentView(R.layout.main)すると、当該アプリの背景にある壁紙が青色の半透明になって透けて見えるようになる。ここまでは期待どおりである。

このアプリで半透明のSurfaceViewを表示するようにすると、青色の半透明は無くなり、SurfaceViewだけを基にした半透明になる。
Javaのcodeは次のとおりである。

public class StartingActivity extends Activity
    implements
    Callback
    {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        RelativeLayout rl = (RelativeLayout)findViewById(R.id.MainParent);
        SurfaceView sv = new SurfaceView(this);
        sv.getHolder().setFormat(PixelFormat.TRANSLUCENT);
        sv.getHolder().addCallback(this);
        rl.addView(sv);
        
        TextView tv = new TextView(this);
        tv.setText(R.string.app_name);
        tv.setTextColor(0xff000000);
        rl.addView(tv);//このTextViewは表示される。
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }

    @Override
    public void surfaceCreated(SurfaceHolder h) {
        Canvas c = h.lockCanvas();
        Paint p = new Paint();
        p.setARGB(150, 255, 0, 0);
        c.drawCircle(200, 200, 200, p);
        h.unlockCanvasAndPost(c);
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    }
}

解決方法
次のようにsetZOrderOnTop(true)を実行すれば良い。
        SurfaceView sv = new SurfaceView(this);
        sv.getHolder().setFormat(PixelFormat.TRANSLUCENT);
        sv.setZOrderOnTop(true);//これを追加する。
        sv.getHolder().addCallback(this);
        rl.addView(sv);

考察
public void setZOrderOnTop (boolean onTop)の説明に次のとおり書かれてある。
Control whether the surface view's surface is placed on top of its window. Normally it is placed behind the window, to allow it to (for the most part) appear to composite with the views in the hierarchy. By setting this, you cause it to be placed above the window.
この説明を読むと、上記の問題解決にsetZOrderOnTop(true)は効果が無いように解釈できる。behind the windowから、above the windowに変えます、というだけだ。私が接した事例ではSurfaceViewが表示されてしまっているのに、その状態が何故behind the windowなんだ。そもそも、私はthe windowが何たるか分かっていないのだが。

振る舞いから考えると、setZOrderOnTop(true)は、同一のZ軸上に存在する他のViewも表示できるようにした上で、それらのViewの中からSurfaceViewをtopに位置付ける、ということかもしれない。

0 件のコメント:

コメントを投稿