自作したSurfaceViewをxmlで使うためにどのような変更をすればいいかをまとめておく。
理解して書いているわけではないので、試行錯誤の一つとして読んでいただければと思います。
SurfaceViewをそのまま使うとき
今回はSurfaceViewの表示だけでなく、タッチの検出などもしたい状況を想定し、画面の幅と高さに加えてタッチした回数や場所を表示するSurfaceView自作した場合を考えます。
起動時と何回かタッチした後の画面は以下のようになります。
SurfaceViewのコードは以下のようにしました。
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を直接設定するとちゃんと表示できます。
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を変更すると画面が変わる。
<?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>
class MainActivity : AppCompatActivity() {
private var mMySfV : MySurfaceView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mMySfV = MySurfaceView(this)
setContentView(R.layout.activity_main)
}
}
このSurfaceViewが配置されている画面に自作SurfaceViewを表示するためには自作SurfaceViewはSurfaceViewを受け渡して生成するようにする。そしてHolderは受け取ったSurfaceViewのHolderを使う。
onCreate()でこれをやるとうまくいかないので、onStartで行う。
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は最初の部分を変更するだけでよい。
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が正しく取得できておらず、タッチにも反応しない。
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を変更しておく。
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は以下のように変更する。
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!!)!!
}
})
}
}
最初の動機
もともとは、『炎のAndroid開発道場』の演習にあった「15パズル」をxmlに配置する方法を調べたのが動機だった。オリジナルなものだと以下のような画面になる。