LoginSignup
1
0

More than 3 years have passed since last update.

自作SurfaceViewをxmlレイアウトで使う

Posted at

自作したSurfaceViewをxmlで使うためにどのような変更をすればいいかをまとめておく。
理解して書いているわけではないので、試行錯誤の一つとして読んでいただければと思います。

SurfaceViewをそのまま使うとき

今回はSurfaceViewの表示だけでなく、タッチの検出などもしたい状況を想定し、画面の幅と高さに加えてタッチした回数や場所を表示するSurfaceView自作した場合を考えます。
起動時と何回かタッチした後の画面は以下のようになります。
  Screenshot_1575687627.png    Screenshot_1575687634.png

SurfaceViewのコードは以下のようにしました。

MySurfaceView.kt
class MySurfaceView (context: Context): SurfaceView(context), SurfaceHolder.Callback, Runnable {

    private val holder1: SurfaceHolder
    private var thread: Thread? = null
    private var canvas: Canvas? = null

    private var count: Int = 0
    private var canvasWidth : Int = 0
    private var canvasHeight : Int = 0

    private var xTouched : Int? = null
    private var yTouched : Int? = null

    init{
        holder1 = getHolder()
        holder1.addCallback(this)
        thread = Thread(this)
        count = 0

    }

    override fun surfaceCreated(holder2: SurfaceHolder) {
        thread?.start()
        canvasWidth = getWidth()
        canvasHeight = getHeight()
    }

    override fun surfaceChanged(holder3: SurfaceHolder, format: Int, width: Int, height: Int) {

    }

    override fun surfaceDestroyed(holder4: SurfaceHolder) {
        thread = null
    }

    //タッチした時の処理
    override fun onTouchEvent(event: MotionEvent): Boolean {
        count++
        xTouched = event.x.toInt()
        yTouched = event.y.toInt()

        Log.v("CHECK", "Touched")
        return false
    }

    //キャンバスに描画
    override fun run() {
        //文字の詳細を設定
        val paint = Paint()
        paint.setTextSize(80.0F)
        paint.color = Color.BLACK
        paint.isAntiAlias = true

        while(thread != null) {
            //描画の準備
            canvas = holder1.lockCanvas()

            canvas?.drawColor(Color.WHITE)

            canvas?.drawText("幅:$canvasWidth 高さ:$canvasHeight", 10.0F, 80.0F * 1, paint)
            canvas?.drawText("タッチ回数:$count", 10.0F, 80.0F * 2, paint)

            if (xTouched != null) {
                canvas?.drawText("タッチ位置:($xTouched, $yTouched)", 10.0F, 80.0F * 3, paint)
            }

            //描画する
            holder1.unlockCanvasAndPost(canvas)
        }
    }
}

setContentViewに自作SurfaceViewを直接設定するとちゃんと表示できます。

MainActivity.kt
class MainActivity : AppCompatActivity() {
    private var mMySfV : MySurfaceView? = n![Screenshot_1575688981.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/313474/58816fb6-6d3c-4ed9-7819-a34e0d99fab6.png)
ull

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        mMySfV = MySurfaceView(this)
        setContentView(mMySfV)

    }
}

xmlレイアウトで表示

以下のページを参考にまずはSurfaceViewを表示する。
XMLレイアウトでSurfaceViewを用いる

とりあえず、xmlを以下のようにし、Activityを変更すると画面が変わる。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <SurfaceView
        android:id="@+id/SurfaceViewMain"
        android:layout_width="fill_parent"
        android:layout_height="300dp">
    </SurfaceView>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World" />

</TableLayout>
MainActivity.kt
class MainActivity : AppCompatActivity() {
    private var mMySfV : MySurfaceView? = null


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        mMySfV = MySurfaceView(this)
        setContentView(R.layout.activity_main)

    }
}

Screenshot_1575688981.png

このSurfaceViewが配置されている画面に自作SurfaceViewを表示するためには自作SurfaceViewはSurfaceViewを受け渡して生成するようにする。そしてHolderは受け取ったSurfaceViewのHolderを使う。
onCreate()でこれをやるとうまくいかないので、onStartで行う。

MainActivity.kt
class MainActivity : AppCompatActivity() {

    private var mMySfV : MySurfaceView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)
    }

    override fun onStart(){
        super.onStart()

        val mSfV = findViewById<SurfaceView>(R.id.SurfaceViewMain)
        mMySfV = MySurfaceView(this, mSfV)
    }
}

MySurfaceViewは最初の部分を変更するだけでよい。

MySurfaceView.kt
class MySurfaceView (context: Context, surfaceview : SurfaceView):
 SurfaceView(context), SurfaceHolder.Callback, Runnable { //コンストラクタも変更


    private val sv : SurfaceView     //追加
    private val holder1: SurfaceHolder
    private var thread: Thread? = null
    private var canvas: Canvas? = null

    private var count: Int = 0
    private var canvasWidth : Int = 0
    private var canvasHeight : Int = 0

    private var xTouched : Int? = null
    private var yTouched : Int? = null

    init{
        sv = surfaceview            //追加
        holder1 = sv.holder         //変更
        holder1.addCallback(this)
        thread = Thread(this)
        count = 0

    }

    //以下は同じ

すると以下のように表示される。残念ながら、widthやheightが正しく取得できておらず、タッチにも反応しない。
Screenshot_1575689889.png

widthの取得とタッチの検出

widthやheightはsurfaceChangedの中で取得できる。実験してみるとgetWidth()やgetHeight()では取得できない。


class MySurfaceView (context: Context, surfaceview : SurfaceView): SurfaceView(context), SurfaceHolder.Callback, Runnable {

    //同じ

    override fun surfaceChanged(holder3: SurfaceHolder, format: Int, width: Int, height: Int) {
        canvasWidth = width
        canvasHeight = height
    }

    //同じ
}

MySurfaceViewは画面に配置されてるわけではないから、タッチで反応しないのは当然である。自然な方法が何かはわからないが、xmlに配置しているSurfaceViewでタッチを感知して、そのなかでMySurfaceViewでやりたかった処理を行えばよい。
MySurfaceViewのonTouchEventを変更しておく。

MySurfaceView.kt
class MySurfaceView (context: Context, surfaceview : SurfaceView): SurfaceView(context), SurfaceHolder.Callback, Runnable {

    //タッチした時の処理
    //override fun onTouchEvent(event: MotionEvent): Boolean {
    fun touched(event: MotionEvent) : Boolean {
        count++
        xTouched = event.x.toInt()
        yTouched = event.y.toInt()

        return false
    }
}

Activityは以下のように変更する。

MainActivity.kt
class MainActivity : AppCompatActivity() {

    private var mMySfV : MySurfaceView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)
    }

    //以下を追加する
    override fun onStart(){
        super.onStart()

        val mSfV = findViewById<SurfaceView>(R.id.SurfaceViewMain)
        mMySfV = MySurfaceView(this, mSfV)

       mSfV.setOnTouchListener(object : View.OnTouchListener{
           override fun onTouch(v: View?, event:MotionEvent?):Boolean{
               return mMySfV?.touched(event!!)!!
           }
       })
    }
}

以上の変更でうまくいく。
   Screenshot_1575716538.png    Screenshot_1575716525.png

最初の動機

もともとは、『炎のAndroid開発道場』の演習にあった「15パズル」をxmlに配置する方法を調べたのが動機だった。オリジナルなものだと以下のような画面になる。

Screenshot_1575041838.png

上で説明したことをのこの場合に適用すると、xmlでこの自作SurfaceViewが使える。
Screenshot_1575634762.png    Screenshot_1575638134.png

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0