#1.今回のテーマ
まずは画面デザイン編①として、トップ画面について書いていきます。
androidアプリの画面定義はxmlで書いていきます。
メインとなる記法は以下の2種類です。
①Linear Layout
LinearLayoutとは、ビューを並べて表示させるレイアウトです。ただし、この並べる方向は縦方向かのどちらかしか指定できません。
縦横を組み合わせた画面レイアウトにしたい場合は、Linear Layoutを入れ子にすることで実装します。
以下に簡単な例を示します。
//ここから縦方向のLinear Layoutを開始
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
//ここから横方向のLinear Layoutを開始
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:orientation="horizontal">
//2つのTextViewを横並びで配置
<TextView
android:id="@+id/tvLevel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="48dp"
android:text="右手初級"
android:textSize="36dp"/>
<TextView
android:id="@+id/tv_questionCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="48dp"
android:text="第1問"
android:textSize="36dp"/>
</LinearLayout>
//ここまでで横並びのLinear Layout終了
//以下のImageViewは先ほどの二つのTextViewの下(縦方向)に配置
<ImageView
android:id="@+id/iv_note"
android:layout_width="300dp"
android:layout_height="288dp"
android:layout_marginTop="8dp"
android:layout_marginLeft="48dp"
android:src="@drawable/right_c"/>
</LinearLayout>
並べる方向は、android:orientationで指定します。
縦並びの場合:
android:orientation="vertical"
横並びの場合:
android:orientation="horizontal"
です。
②Constraint Layout
画面部品を相対的に配置するレイアウトです。
タグの記述はレイアウト部品を入れ子にせず、並列に記述することが可能です。
部品同士の配置は各部品同士の位置を相対的に制約(Constraint)として記載することで実装します。
先ほどのandroid studioのスクリーンショットの右側のblueprintを見ると、各画面部品が矢印でつながれていることがわかると思います。
こんな感じで、TextViewやButtonに対して、位置関係や余白などを設定して画面レイアウトを作っていきます。
今回のアプリでは、両方を学習したかったので、トップ画面はConstraint Layout、ゲーム画面と結果画面はLinear Layoutで作成しました。
この記事ではまずConstraint Layoutで作ったトップ画面について解説していきます。
Constraint LayoutはGUIベースでコードの意味を理解していなくても直感的に画面が作れてしまうので、とっかかりやすいですが、xmlをしっかりと理解したい人はまずLinearLayoutから試してみることをお勧めします。
#2.ランキングラベル
まずは最も簡単なTextViewの配置からです。
activity.mainタブを選択して、デザインビューを表示します。
画面左のパレットから「Common」⇒「TextView」を選択して、中央の画面イメージ部分にドラッグ&ドロップします。場所はとりあえず任意の位置で問題ありません。
次に「ランキング」という文字を表示していきます。
text属性に直接書いても問題ありませんが、android開発では、strings.xmlに定義した変数を使うことが推奨されているようです。
strings.xmlを開き、resourcesタグに以下を追加します。
<resources>
<string name="label_ranking">ランキング</string>
</resources>
これで、「ランキング」という文字列は「label_ranking」という変数で扱うことができるようになりました。今後はこれを使っていきます。
再びactivity_main.xmlを開きます。
画面の右側「属性」の中に「text」という項目があります。
ここに先ほどstrings.xmlで定義した変数をセットすることで文字を表示していきます。
@string/label_ranking
と記述します。strings.xmlでlabel_rankingという変数で定義した文字列をこのTextViewでは表示する、という意味になります。
次は制約を設定していきます。
ランキングは画面の上部中央に表示することにします。
ランキングのアイテムを選択すると、上下左右中央に「〇」が表示されています。
この丸を選択して、他のアイテムと結びつけることで位置関係を定義していきます。
まずは上部の〇を選択して、画面の上端まで引っ張ります。
そうすることで、画面上端のすぐ下に配置する、という制約を付与したことになります。
ピッタリ上端にくっついていたのでは見づらいので、次に余白を設定します。
画面右側の「レイアウト」内にある「ConstraintWidget」から設定していきます。
先ほど画面上端との制約を定義したので、上部分の余白を設定することができる状態になっていると思います。
今回は余白を「16」に設定しました。
同じようにして左右の制約も設定していきます。
画面両端と結合することで、画面中央に配置されることになります。
また、文字のサイズですが、textSize属性で設定します。
赤枠で囲み忘れましたが、右上に「textSize」属性がありますね。
今回は24dpを設定しました。
文字サイズ指定の方法はspとdpがあります。それぞれの違いは以下の記事を参考にしてください。
https://tech.pjin.jp/blog/2013/06/07/android%E3%80%80dp%EF%BC%88dip%EF%BC%89%E3%81%A8sp%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6-%E3%80%90android-tips%E3%80%91/
#3.レベル選択プルダウン
続いてレベルを選択するアイテムを実装していきます。
複数のレベルから選択するので、プルダウンで選べるようにしたいと思います。
プルダウン選択のためには「spinner」という部品を使います。
パレットから「Containers」⇒「Spinner」を選択して画面の任意の場所に配置します。
選択肢とする文字列は先ほどの「ランキング」と同様に「strings.xml」に記述していきます。
以下のように、resoucesタグ内にstring-arrayとして定義していきます。
<resources>
<string name="label_ranking">ランキング</string>
<string-array name="levelList">
<item>へ音初級</item>
<item>へ音中級</item>
<item>へ音上級</item>
<item>ト音初級</item>
<item>ト音中級</item>
<item>ト音上級</item>
<item>両手初級</item>
<item>両手中級</item>
<item>両手上級</item>
</string-array>
</resources>
ここでは、レベル選択の選択肢一覧に「levelList」という名前を付けました。
そして具体的な選択肢をitemとして定義していきます。
activity.xmlに戻ります。
「entries」属性に先ほどのlevelListを設定していきます。
注意点として先ほどのランキングという単なる文字列を表示する時は「@string/」で指定しましたが、今回は「@array/」で指定する必要があります。
「@array/levelList」とします。
もう一点、これは機能実装編で解説しますが、このレベル選択プルダウンは、プルダウンで選択したレベルに応じて、そのレベルの1位から3位の記録を表示する機能を実装していきます。先ほどの「ランキング」はただ表示するだけでした。
このようにアイテムに紐づいて何らかの処理を実装する必要がある場合は、idを設定してあげる必要があります。
今回はidを「sp_selectLevel」としました。
今後、このアイテムに対する処理を実装する際は、
「@id/sp_selectLevel」と記載することで実現します。
続いて制約を設定していきます。
上端は「ランキング」の下部に接続、左は画面左端と接続して、余白を共に「8」と設定しました。
#4.ランク表示
続いてランクを表示していく部分を実装していきます。
表のタイトル部分は単なるラベルなので、「ランキング」表示と同様にstrings.xmlで表示させたい文字列を定義してTextViewの「text」属性に設定するだけです。
<string name="label_rank">No</string>
<string name="label_name">名前</string>
<string name="label_time">タイム</string>
<string name="label_correct_count">正解</string>
<string name="label_date">日付</string>
<string name="label_rank_first">1位</string>
<string name="label_rank_second">2位</string>
<string name="label_rank_third">3位</string>
実際の順位表示の部分は、機能編でデータベースから値を取得します。
処理を実装するので、idを設定してあげます。
また、デフォルト値として「-」を表示させておきます。
例として1位の「名前」欄の設定は以下の通りです。
id:name_ first
text:-
#5.レベル選択ボタン
続いてゲーム画面に遷移するレベル選択ボタンを実装していきます。
標準で用意されているボタンを使ってもよかったのですが、今回は自分で作成した画像をボタンにしたいと思います。
あらかじめボタンにする画像を準備してpng形式で保存しておきます。
私は以下のサイトを使って画像を作成しました。
https://text-img.cman.jp/
次に、作成した画像ファイルをandroid studioのフォルダに移動していきます。
プロジェクトエクスプローラーを開き、「app」⇒「res」⇒「drawable」フォルダにドラッグ&ドロップします。
するとこんな画面が出てきますので、「リファクタリング」ボタンを押します。
これで、作成した画像がandroidに認識されます。
自分で作成した画像をボタンにしたい場合は「ImageButton」を使います。
パレットから「Button」⇒「ImageButton」を選択して画面エリアにドロップします。
すると、画像を選択する画面が出てきます。この中に先ほどコピーした画像が出てくるはずなので、選択してOKボタンを押します。
すると、srcCompat属性に選択した画像のパスがセットされます。
今回は「へ音初級」ボタンなので、「left_biginer」という画像ファイルを設定しました。
また、このボタンも画面を遷移する際の処理に使いますので、idを設定します。
idはbt_left_biginerとしました。
※btはボタンアイテムを指します。こうして接頭語をつけておくと後々コードが見やすくなります。
同様にしてその他のレベル選択ボタンも実装していきます。
GUIで画面定義をしていきましたが、実際にはxmlが自動で生成されています。
最後に自動生成されたxml全量を掲載しておきます。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:background="@color/white"
tools:context=".MainActivity">
<TextView
android:id="@+id/label_ranking"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/label_ranking"
android:textSize="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/label_rank"
android:layout_width="36dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/label_rank"
android:textSize="18dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sp_selectLevel" />
<TextView
android:id="@+id/label_name"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/label_name"
android:textSize="18dp"
app:layout_constraintStart_toEndOf="@+id/label_rank"
app:layout_constraintTop_toBottomOf="@+id/sp_selectLevel" />
<TextView
android:id="@+id/label_time"
android:layout_width="64dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/label_time"
android:textSize="18dp"
app:layout_constraintStart_toEndOf="@+id/label_name"
app:layout_constraintTop_toBottomOf="@+id/sp_selectLevel" />
<TextView
android:id="@+id/label_correct_count"
android:layout_width="44dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/label_correct_count"
android:textSize="18dp"
app:layout_constraintStart_toEndOf="@+id/label_time"
app:layout_constraintTop_toBottomOf="@+id/sp_selectLevel" />
<TextView
android:id="@+id/label_date"
android:layout_width="108dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/label_date"
android:textSize="18dp"
app:layout_constraintStart_toEndOf="@+id/label_correct_count"
app:layout_constraintTop_toBottomOf="@+id/sp_selectLevel" />
<TextView
android:id="@+id/rank_first"
android:layout_width="36dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/label_rank_first"
android:textSize="18dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/label_rank" />
<TextView
android:id="@+id/name_first"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="-"
android:textSize="18dp"
app:layout_constraintStart_toEndOf="@+id/rank_first"
app:layout_constraintTop_toBottomOf="@+id/label_name" />
<TextView
android:id="@+id/time_first"
android:layout_width="64dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="-"
android:textSize="18dp"
app:layout_constraintStart_toEndOf="@+id/name_first"
app:layout_constraintTop_toBottomOf="@+id/label_time" />
<TextView
android:id="@+id/correct_count_first"
android:layout_width="44dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="-"
android:textSize="18dp"
app:layout_constraintStart_toEndOf="@+id/time_first"
app:layout_constraintTop_toBottomOf="@+id/label_correct_count" />
<TextView
android:id="@+id/date_first"
android:layout_width="108dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="-"
android:textSize="18dp"
app:layout_constraintStart_toEndOf="@+id/correct_count_first"
app:layout_constraintTop_toBottomOf="@+id/label_date" />
<TextView
android:id="@+id/rank_second"
android:layout_width="36dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/label_rank_second"
android:textSize="18dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rank_first" />
<TextView
android:id="@+id/name_second"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="-"
android:textSize="18dp"
app:layout_constraintStart_toEndOf="@+id/rank_second"
app:layout_constraintTop_toBottomOf="@+id/name_first" />
<TextView
android:id="@+id/time_second"
android:layout_width="64dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="-"
android:textSize="18dp"
app:layout_constraintStart_toEndOf="@+id/name_second"
app:layout_constraintTop_toBottomOf="@+id/time_first" />
<TextView
android:id="@+id/correct_count_second"
android:layout_width="44dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="-"
android:textSize="18dp"
app:layout_constraintStart_toEndOf="@+id/time_second"
app:layout_constraintTop_toBottomOf="@+id/correct_count_first" />
<TextView
android:id="@+id/date_second"
android:layout_width="108dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="-"
android:textSize="18dp"
app:layout_constraintStart_toEndOf="@+id/correct_count_second"
app:layout_constraintTop_toBottomOf="@+id/date_first" />
<TextView
android:id="@+id/rank_third"
android:layout_width="36dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/label_rank_third"
android:textSize="18dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rank_second" />
<TextView
android:id="@+id/name_third"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="-"
android:textSize="18dp"
app:layout_constraintStart_toEndOf="@+id/rank_third"
app:layout_constraintTop_toBottomOf="@+id/name_second" />
<TextView
android:id="@+id/time_third"
android:layout_width="64dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="-"
android:textSize="18dp"
app:layout_constraintStart_toEndOf="@+id/name_third"
app:layout_constraintTop_toBottomOf="@+id/time_second" />
<TextView
android:id="@+id/correct_count_third"
android:layout_width="44dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="-"
android:textSize="18dp"
app:layout_constraintStart_toEndOf="@+id/time_third"
app:layout_constraintTop_toBottomOf="@+id/correct_count_second" />
<TextView
android:id="@+id/date_third"
android:layout_width="108dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="-"
android:textSize="18dp"
app:layout_constraintStart_toEndOf="@+id/correct_count_third"
app:layout_constraintTop_toBottomOf="@+id/date_second" />
<Spinner
android:id="@+id/sp_selectLevel"
android:entries="@array/levelList"
android:layout_width="128dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/label_ranking" />
<ImageButton
android:id="@+id/bt_right_biginer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:tag="ト音初級"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/bt_left_biginer"
app:layout_constraintTop_toBottomOf="@+id/time_third"
app:srcCompat="@drawable/right_biginer" />
<ImageButton
android:id="@+id/bt_right_high"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="ト音上級"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/bt_left_high"
app:layout_constraintTop_toBottomOf="@+id/bt_right_middle"
app:srcCompat="@drawable/right_high" />
<ImageButton
android:id="@+id/bt_right_middle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="ト音中級"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/bt_left_middle"
app:layout_constraintTop_toBottomOf="@+id/bt_right_biginer"
app:srcCompat="@drawable/right_middle" />
<ImageButton
android:id="@+id/bt_left_biginer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:tag="へ音初級"
app:layout_constraintEnd_toStartOf="@+id/bt_right_biginer"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/time_third"
app:srcCompat="@drawable/left_biginer" />
<ImageButton
android:id="@+id/bt_left_middle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="へ音中級"
app:layout_constraintEnd_toStartOf="@+id/bt_right_middle"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bt_left_biginer"
app:srcCompat="@drawable/left_middle" />
<ImageButton
android:id="@+id/bt_left_high"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="へ音上級"
app:layout_constraintEnd_toStartOf="@+id/bt_right_high"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bt_left_middle"
app:srcCompat="@drawable/left_high" />
<ImageButton
android:id="@+id/bt_both_biginer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="両手初級"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bt_left_high"
app:srcCompat="@drawable/both_biginer" />
<ImageButton
android:id="@+id/bt_both_middle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="両手中級"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bt_both_biginer"
app:srcCompat="@drawable/both_middle" />
<ImageButton
android:id="@+id/bt_both_high"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="両手上級"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bt_both_middle"
app:srcCompat="@drawable/both_high" />
</androidx.constraintlayout.widget.ConstraintLayout>
コードについては次回のLinear Layoutの解説時に触れたいと思います。
今回はGUIで作ってもこのようにxmlが生成されるんだ、ということだけつかんでもらえればと思います。
今回はここまでです。
次回はゲーム画面の実装を通して、Lineay Layoutについて解説します。
長文になりましたが、最後まで読んでいただきありがとうございました。
①概要
②画面デザイン~トップ画面(Constraint Layout)~(本記事)
③画面デザイン~ゲーム画面(Linear Layout)~
④画面デザイン~結果画面(Linear Layoutその2)~
⑤トップ画面からの遷移(インテント(putExtra))
⑥トップ画面から引き継いだデータ表示(インテント(getExtra))
⑦問題出題(ロジック実装)
⑧回答ボタン押下(効果音再生(MediaPlayer、正誤判定、次の問題出題)
⑨タイムカウンターの実装(handler)
⑩ゲーム画面から引き継いだゲーム結果表示(インテント)
⑪当日日付データ取得
⑫DB保存(SQLite、Insert)
⑬もう一度、トップ画面へ戻るボタン(インテント)
⑭ランキング表示(SQLite、Select)
⑮実機でのテスト
⑯Google Playで公開