Pythonに足を生やす

趣味や勉強のアウトプット等

【Kotlin】xmlを使わずにViewを表みたいに配置したい!【Android Studio】

f:id:will_optd:20180814005000p:plain

今回やりたいこと

 唐突に大学生のための時間割アプリを作りたくなったので、まずは上の画像みたいにView(今回はボタン)を等間隔かつ画面一杯にn×m個配置することを試みてみました。2×2の4個くらいであればLayout Editorから手で4つ配置してあげてxmlファイルをじいくってもいいのですが今回みたいに数が多くなると(約50個)少し面倒なので、Kotlinで繰り返し処理をなんだかんだやってみたのが上の画像の結果です。ただ、実は後述の諸事情のせいで縦の大きさをうまい具合に調整できなかったので今回は妥協して高さのほうにも画面の横幅を利用しました。

手順

 今回は以下の手順で実装しました。

①いくつViewを配置するのか決める(nとmを決める)
②View1つ1つに対応する座標を生成して配列にする(2×2なら[(1,1), (1,2), (2,1), (2,2)]みたいな感じ)
③座標に対応する空の配列を作成(n×mの多次元配列)
④②の座標の配列で繰り返し処理を実行して③の配列1つ1つにView生成して(位置とか大きさも決めて)代入していく

コード

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val constraintLayout = findViewById(R.id.constraintLayout) as ConstraintLayout
        
        // 画面の大きさを取得
        val wm = getWindowManager()
        val disp = wm.getDefaultDisplay()
        val p = Point()
        disp.getSize(p)
        val width = p.x
        val height = p.x
        // いくつViewを配置するかを決める
        val colnum = 7
        val rownum = 7
        // 座標を入れるための空のListとViewを入れるための空の多次元配列を作成
        val grids = mutableListOf<Pair<Int, Int>>()
        val buttons = Array(rownum, { arrayOfNulls<Button>(colnum)})

        // ボタンひとつひとつの幅と高さを決める
        val buttonWidth = width/colnum
        val buttonHeight = height/rownum
        val buttonLayoutParams = LinearLayout.LayoutParams(
                buttonWidth,  buttonHeight
        )
        // 座標を生成して上で作った配列に代入
        for (i in 1..rownum) {
            for (j in 1..colnum){
                grids.add(Pair<Int, Int>(i, j))
            }
        }
        // ボタンを生成して配置
        for (i in grids){
            // 座標に対応する部分にボタンを生成して代入(下二行)
            var target = buttons[i.first - 1][i.second - 1]
            target = Button(this)
            target.text = "BUTTON"
            target.layoutParams = buttonLayoutParams
            // ボタンの位置を決める
            var posx = (buttonWidth * (i.second - 1)).toFloat()
            var posy = (buttonHeight * (i.first - 1)).toFloat()
            // ボタンの位置を設定
            target.setX(posx)
            target.setY(posy)
            // ボタンを配置する
            constraintLayout.addView(target)
        }
    }

反省点

 まず、Viewを画面いっぱいに表示したかったのにOnCreateの中では親Viewの大きさが取得できない都合上、うまく大きさを調整できなかったのが悔やまれます。それというのも、上の青いバー部分を含んだ画面の大きさを取得してそれを下にボタンの大きさを決めているため、画面サイズを単純に割って配置するViewのサイズを決めてしまうと下にはみ出てしまうのです。調べてみるとonCreateのタイミングじゃなくonWindowFocusChange?のタイミングであればボタンを配置した親ビューの大きさも取得できるみたいなのでまずはそこを改善していきたいと思います。

今後やりたいこととか

 xmlじゃなくてコードのほうでViewを生成するとクリック時の挙動をどこで記述したらいいか今のとこさっぱりわからないのでそこを調べてクリック時に何らかの処理を行うプログラムを書いてみようと思います。それと、そもそもLayout Editorかxlmで似たようなことができればもっと楽になるのでそういった方法があれば是非ご教授いただけると幸いです。

2018/11/8 追記

大きさの調整はこんな感じで上手くいきました。setContentView(R.layout.activity_main)をonCreate内に書いて、前回の中身の部分はonWindowFocusChanged内へ。そしてコンテンツ領域のサイズはconstraintLayoutの横幅と縦幅からcl.getWidth()とcl.getHeight()で。

f:id:will_optd:20181109001033p:plain

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.constraint.ConstraintLayout
import android.widget.Button
import android.widget.LinearLayout

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)
        val cl = findViewById(R.id.constraintLayout) as ConstraintLayout
        val width = cl.getWidth()
        val height = cl.getHeight()

        val colnum = 5
        val rownum = 5
        val grids = mutableListOf<Pair<Int, Int>>()
        val buttons = Array(rownum, { arrayOfNulls<Button>(colnum)})

        val buttonWidth = width/colnum
        val buttonHeight = height/rownum
        val buttonLayoutParams = LinearLayout.LayoutParams(
                buttonWidth,  buttonHeight
        )

        for (i in 1..rownum) {
            for (j in 1..colnum){
                grids.add(Pair<Int, Int>(i, j))
            }
        }

        for (i in grids) {
            var target = Button(this)
            buttons[i.first - 1][i.second - 1] = target
            target.text = "BUTTON"
            target.layoutParams = buttonLayoutParams
            var posx = (buttonWidth * (i.second - 1)).toFloat()
            var posy = (buttonHeight * (i.first - 1)).toFloat()
            target.setX(posx)
            target.setY(posy)
            cl.addView(target)
        }
        buttons[0][0]?.text = "ボタン"
    }
}

後はボタン同士の余白をなくしたい…