■ はじめに
https://dk521123.hatenablog.com/entry/2020/07/20/232009
の続き。 単語帳だけでなく、TOEICの文法問題も扱えるようにするために 質問⇒回答・解説のような画面を作りたい。 そこで、画面を切り替えられる Fragment(フラグメント)について扱う。
■ サンプル
* 一つのActivityに対して、2つのFragmentを切り替えていく画面を考える * 以下のサイトがイメージ付きやすいかも。
https://qiita.com/daichi77/items/09119cdba435ead4a08c
画面レイアウト
[Activity] activity_grammar_exam.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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=".exam.grammars.GrammarExamActivity"> <FrameLayout android:id="@+id/grammarsExamFrameLayout" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:foregroundGravity="center" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"></FrameLayout> </LinearLayout>
[Fragment] fragment_grammar_exam_question.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".exam.grammars.GrammarExamQuestionFragment"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingStart="20dp" android:paddingTop="20dp" android:padding="30dp"> <TextView android:id="@+id/grammarQuestionTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="TextView" android:textSize="24sp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal" android:padding="20dp"> <TextView android:id="@+id/candidateTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="TextView" android:textSize="24sp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="bottom|center" android:orientation="horizontal" android:padding="5dp"> <Button android:id="@+id/selectAButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/select_a_button" android:layout_margin="5dp"/> <Button android:id="@+id/selectBButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/select_b_button" android:layout_margin="5dp"/> <Button android:id="@+id/selectCButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/select_c_button" android:layout_margin="5dp"/> <Button android:id="@+id/selectDButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/select_d_button" android:layout_margin="5dp"/> </LinearLayout> </LinearLayout>
[Fragment] fragment_grammar_exam_answer.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".exam.grammars.GrammarExamAnswerFragment"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="10dp"> <TextView android:id="@+id/correctOrNoTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="TextView" android:textSize="24sp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="10dp"> <TextView android:id="@+id/questionTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="TextView" android:textSize="24sp" /> <TextView android:id="@+id/questionTranslationTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="TextView" android:textSize="12sp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="10dp"> <TextView android:id="@+id/commentaryTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="TextView" android:textSize="24sp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="10dp"> <Button android:id="@+id/nextQuestionButton" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/next_question_button" /> </LinearLayout> </LinearLayout>
画面制御
GrammarExamActivity.kt
import android.os.Bundle import android.util.Log import android.widget.Toast import androidx.fragment.app.Fragment import com.dk.englishcards.R import com.dk.englishcards.commons.BaseSubPageActivity class GrammarExamActivity : BaseSubPageActivity(), GrammarExamQuestionFragment.OnSelectedAnswer, GrammarExamAnswerFragment.OnClickNextButton { private var questionNo: Int = 1 private lateinit var grammarExams: List<GrammarExam> private lateinit var grammarExamDbHandler: GrammarExamDbHandler override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_grammar_exam) this.grammarExamDbHandler = GrammarExamDbHandler() this.grammarExams = this.grammarExamDbHandler.readAll().toList().shuffled() this.showQuestion() } override fun onSelectedAnswer(isCorrectAnswer: Boolean) { Log.d("Grammar", "isCorrectAnswer = $isCorrectAnswer") val grammarExam = this.getTargetGrammarExam() val collectAnswer = when (grammarExam.answer) { "A" -> grammarExam.candidateA "B" -> grammarExam.candidateB "C" -> grammarExam.candidateC "D" -> grammarExam.candidateD else -> "" } val grammarExamAnswerFragment = GrammarExamAnswerFragment.newInstance( this.questionNo, isCorrectAnswer, collectAnswer, grammarExam.question, grammarExam.questionTranslation, grammarExam.commentary) grammarExamAnswerFragment.setClickNextButtonListener(this) this.replaceFragment(grammarExamAnswerFragment) } override fun onClickNextButton() { Log.d("Grammar", "Called onClickNextButton") if (this.grammarExams.size <= this.questionNo) { Toast.makeText(this, "Try again...", Toast.LENGTH_SHORT).show() this.questionNo = 1 this.grammarExams = this.grammarExams.shuffled() } else { this.questionNo++ } this.showQuestion() } private fun replaceFragment(fragment: Fragment) { val fragmentTransaction = supportFragmentManager.beginTransaction() fragmentTransaction.replace(R.id.grammarsExamFrameLayout, fragment) fragmentTransaction.commit() } private fun getTargetGrammarExam(): GrammarExam { val index = this.questionNo - 1 return this.grammarExams[index] } private fun showQuestion() { val grammarExam = this.getTargetGrammarExam() val grammarExamQuestionFragment = GrammarExamQuestionFragment.newInstance( this.questionNo, grammarExam.question, grammarExam.candidateA, grammarExam.candidateB, grammarExam.candidateC, grammarExam.candidateD, grammarExam.answer) grammarExamQuestionFragment.setSelectedAnswerListener(this) this.replaceFragment(grammarExamQuestionFragment) } }
GrammarExamQuestionFragment.kt
import android.os.Bundle import android.util.Log import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.dk.englishcards.R import kotlinx.android.synthetic.main.fragment_grammar_exam_question.* private const val ARG_NO = "no" private const val ARG_QUESTION = "question" private const val ARG_CANDIDATE_A = "candidateA" private const val ARG_CANDIDATE_B = "candidateB" private const val ARG_CANDIDATE_C = "candidateC" private const val ARG_CANDIDATE_D = "candidateD" private const val ARG_ANSWER = "answer" /** * A simple [Fragment] subclass. * Use the [GrammarExamQuestionFragment.newInstance] factory method to * create an instance of this fragment. */ class GrammarExamQuestionFragment : Fragment() { private var no: Int = 1 private var question: String? = "" private var candidateA: String? = "" private var candidateB: String? = "" private var candidateC: String? = "" private var candidateD: String? = "" private var answer: String? = "" private var listener: OnSelectedAnswer? = null interface OnSelectedAnswer { fun onSelectedAnswer(isCorrectAnswer :Boolean) } companion object { @JvmStatic fun newInstance( no: Int, question: String, candidateA: String, candidateB: String, candidateC: String, candidateD: String, answer: String) = GrammarExamQuestionFragment().apply { arguments = Bundle().apply { putInt(ARG_NO, no) putString(ARG_QUESTION, question) putString(ARG_CANDIDATE_A, candidateA) putString(ARG_CANDIDATE_B, candidateB) putString(ARG_CANDIDATE_C, candidateC) putString(ARG_CANDIDATE_D, candidateD) putString(ARG_ANSWER, answer) } } } fun setSelectedAnswerListener(listener: OnSelectedAnswer) { this.listener = listener } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments.let { no = it!!.getInt(ARG_NO) question = it.getString(ARG_QUESTION) candidateA = it.getString(ARG_CANDIDATE_A) candidateB = it.getString(ARG_CANDIDATE_B) candidateC = it.getString(ARG_CANDIDATE_C) candidateD = it.getString(ARG_CANDIDATE_D) answer = it.getString(ARG_ANSWER) } Log.d("Grammar", "${this.no}, ${this.question}") } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_grammar_exam_question, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) grammarQuestionTextView.text = "Q${this.no}. ${this.question}" candidateTextView.text = "(A) ${this.candidateA}\n" + "(B) ${this.candidateB}\n" + "(C) ${this.candidateC}\n" + "(D) ${this.candidateD}" selectAButton.setOnClickListener { val isCorrectAnswer = this.isCorrectAnswer(this.answer, "A") this.listener?.onSelectedAnswer(isCorrectAnswer) } selectBButton.setOnClickListener { val isCorrectAnswer = this.isCorrectAnswer(this.answer, "B") this.listener?.onSelectedAnswer(isCorrectAnswer) } selectCButton.setOnClickListener { val isCorrectAnswer = this.isCorrectAnswer(this.answer, "C") this.listener?.onSelectedAnswer(isCorrectAnswer) } selectDButton.setOnClickListener { val isCorrectAnswer = this.isCorrectAnswer(this.answer, "D") this.listener?.onSelectedAnswer(isCorrectAnswer) } } override fun onDetach() { super.onDetach() listener = null } private fun isCorrectAnswer(answer: String?, selectedAnswer: String): Boolean { return answer!!.toUpperCase() == selectedAnswer } }
GrammarExamAnswerFragment.kt
import android.os.Bundle import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.dk.englishcards.R import kotlinx.android.synthetic.main.fragment_grammar_exam_answer.* private const val ARG_NO = "no" private const val ARG_IS_COLLECT_ANSWER = "isCollectAnswer" private const val ARG_ANSWER = "answer" private const val ARG_QUESTION = "question" private const val ARG_QUESTION_TRANSLATIONS = "questionTranslation" private const val ARG_COMMENTARY = "commentary" /** * A simple [Fragment] subclass. * Use the [GrammarExamAnswerFragment.newInstance] factory method to * create an instance of this fragment. */ class GrammarExamAnswerFragment : Fragment() { private var no: Int = 1 private var isCollectAnswer: Boolean = false private var answer: String? = "" private var question: String? = "" private var questionTranslation: String? = "" private var commentary: String? = "" private var listener: OnClickNextButton? = null interface OnClickNextButton { fun onClickNextButton() } companion object { @JvmStatic fun newInstance( no: Int, isCollectAnswer: Boolean, answer: String, question: String, questionTranslation: String, commentary: String ) = GrammarExamAnswerFragment().apply { arguments = Bundle().apply { putInt(ARG_NO, no) putBoolean(ARG_IS_COLLECT_ANSWER, isCollectAnswer) putString(ARG_ANSWER, answer) putString(ARG_QUESTION, question) putString(ARG_QUESTION_TRANSLATIONS, questionTranslation) putString(ARG_COMMENTARY, commentary) } } } fun setClickNextButtonListener(listener: OnClickNextButton) { this.listener = listener } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { no = it.getInt(ARG_NO) isCollectAnswer = it.getBoolean(ARG_IS_COLLECT_ANSWER) answer = it.getString(ARG_ANSWER) question = it.getString(ARG_QUESTION) questionTranslation = it.getString(ARG_QUESTION_TRANSLATIONS) commentary = it.getString(ARG_COMMENTARY) } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_grammar_exam_answer, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) correctOrNoTextView.text = "Q${this.no} " + if (this.isCollectAnswer) "Collect!!!" else "Incorrect..." questionTextView.text = this.question.toString().replace( "-------", "( " + this.answer.toString() + " )") questionTranslationTextView.text = this.questionTranslation commentaryTextView.text = this.commentary nextQuestionButton.setOnClickListener { this.listener?.onClickNextButton() } } override fun onDetach() { super.onDetach() listener = null } }
データオブジェクト(あまり本質じゃないが)
GrammarExam.kt
import com.dk.englishcards.cards.EnglishCard import com.dk.englishcards.exam.words.EnglishWordsExam import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey import io.realm.annotations.Required import java.util.* open class GrammarExam : RealmObject() { @PrimaryKey var grammarExamId: String = UUID.randomUUID().toString() @Required var question: String = "" @Required var candidateA: String = "" @Required var candidateB: String = "" @Required var candidateC: String = "" @Required var candidateD: String = "" @Required var answer: String = "" var commentary: String = "" var questionTranslation: String = "" companion object { const val ID_FIELD = "grammarExamId" @JvmStatic fun newInstance( question: String, candidateA: String, candidateB: String, candidateC: String, candidateD: String, answer: String, commentary: String = "", questionTranslation: String = "" ) = GrammarExam().apply { this.question = question this.candidateA = candidateA this.candidateB = candidateB this.candidateC = candidateC this.candidateD = candidateD this.answer = answer this.commentary = commentary this.questionTranslation = questionTranslation } } }
関連記事
レイアウト ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2015/08/23/165632
Kotlin / Realm で英単語帳を作る
https://dk521123.hatenablog.com/entry/2020/07/20/232009
Android / Kotlin で画像検索を実装する
https://dk521123.hatenablog.com/entry/2020/09/21/224542