Memo

メモ > 技術 > IDE: AndroidStudio > アプリの作成(XMLレイアウト / フラグメント)

アプリの作成(XMLレイアウト / フラグメント)
■フラグメント Fragmentの基本 - Qiita https://qiita.com/naoi/items/3e1125d1e1418d09f77a Android はじめてのFragment - Qiita https://qiita.com/Reyurnible/items/dffd70144da213e1208b ■フラグメントでページの切り替え 以下で新規にプロジェクトを作成 プロジェクトの選択: Empty Activity プロジェクトの名前: fragment ビューバインディングを使えるようにする build.gradle を変更したら「Sync Now」をクリック アクティビティを追加で作成する プロジェクトウインドウのツリーで「app」を右クリックし、 「New → Activity → Empty Activity」を選択する 表示されたダイアログで「Activity Name」で「SubActivity」と入力して、あとはデフォルトのまま「Finish」ボタンを押す プロジェクトウインドウのツリーに「SubActivity」と「activity_sub.xml」が追加される タイトル表示用のフラグメントを作成する プロジェクトウインドウのツリーで「app」を右クリックし、 「New → Fragment → Fragment(Blank)」を選択する 表示されたダイアログで「Fragment Name」で「TitleFragment」と入力して、あとはデフォルトのまま「Finish」ボタンを押す プロジェクトウインドウのツリーに「TitleFragment.kt」と「fragment_title.xml」が追加される fragment_title.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout 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" tools:context=".TitleFragment"> <!-- TODO: Update blank fragment layout --> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="@string/hello_blank_fragment" /> </FrameLayout>
このファイルを、以下のように変更する
<?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:id="@+id/constraintLayout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".TitleFragment" > <TextView android:id="@+id/titleText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="タイトル" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
TitleFragment
package org.refirio.fragment import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment // TODO: Rename parameter arguments, choose names that match // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER private const val ARG_PARAM1 = "param1" private const val ARG_PARAM2 = "param2" /** * A simple [Fragment] subclass. * Use the [TitleFragment.newInstance] factory method to * create an instance of this fragment. */ class TitleFragment : Fragment() { // TODO: Rename and change types of parameters private var param1: String? = null private var param2: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { param1 = it.getString(ARG_PARAM1) param2 = it.getString(ARG_PARAM2) } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_title, container, false) } companion object { /** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @param param1 Parameter 1. * @param param2 Parameter 2. * @return A new instance of fragment TitleFragment. */ // TODO: Rename and change types and number of parameters @JvmStatic fun newInstance(param1: String, param2: String) = TitleFragment().apply { arguments = Bundle().apply { putString(ARG_PARAM1, param1) putString(ARG_PARAM2, param2) } } } }
このファイルを、以下のように変更する
package org.refirio.fragment import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import org.refirio.fragment.databinding.FragmentTitleBinding class TitleFragment : Fragment() { private var _binding: FragmentTitleBinding? = null private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = FragmentTitleBinding.inflate(inflater, container, false) return binding.root } override fun onDestroyView() { super.onDestroyView() _binding = null } fun setTitle(title: String) { binding.titleText.text = title } }
タイトル表示用のフラグメントをアクティビティへ配置する activity_main.xml を開き、「Hello World」のテキストビューを削除してから、 「common」内の「<fragment>」をドラッグ&ドロップで中央に配置する 「Fragments」ダイアログに、存在するフラグメントが一覧表示されるので、先程作成した「TitleFragment」を作成して「OK」をクリックする 成約は上端と左右を画面の端に接続し、「Attribute」でマージンを0に設定する(もともと0になっているはず) また、以下の設定を行う id: titleFragment layout_width: 0dp layout_height: wrap_content フラグメントのプレビューを有効にする プログラムの動作には影響しないが、レイアウトがイメージしやすくなるので設定しておくといい 配置したフラグメントを選択し、「All Attrivute」内の「layout」の「[]」をクリックする 「Pick a Resource」ダイアログが開くので、先ほど作成した「fragment_title」を選択して「OK」をクリックする ここまで作業できたらいったん実行してみる 画面上部にフラグメント内の文字列が表示される サブ画面を作成する 「activity_sub.xml」を開き、初期配置されているテキストビューがあれば削除する 「layout」から「FrameLayout」を画面中央にドラッグ&ドロップで配置 成約は上端と左右を画面の端に接続し、「Attribute」でマージンを0に設定する また、以下の設定を行う id: titleFrame layout_width: 0dp layout_height: wrap_content 「activity_sub.xml」に「Common」内の「Button」を3つ配置する 位置はtitleFrameの下なら適当でいい また、それぞれに以下の設定を行う id: firstButton text: ボタン1 id: secondButton text: ボタン2 id: thirdButton text: ボタン3 ボタンに制約を追加しておく 一例だが、3つのボタンを選択して「Align」ボタンから「Top Edges」を選択する これで各ボタンが水平に並ぶ さらに3つのボタンを選択して、いずれかのボタンを右クリックして「Chains → Create Horizontal Chain」を選択する これで画面サイズやボタンサイズが変更されても水平方向に均等に配置される フラグメント表示用のビューを配置する 「Layout」内の「FrameLayout」をドラッグ&ドロップでボタンの下に配置する 以下の設定を行う id: container layout_width: 0dp layout_height: 0dp 制約は、上端を真ん中のボタンの下に、下端と左右は画面の端に接続する マージンはそれぞれ8を設定しておく 各画面のフラグメントを用意する プロジェクトウインドウのツリーで「app」を右クリックし、 「New → Fragment → Fragment(Blank)」を選択する 表示されたダイアログで以下を入力し、あとはデフォルトのまま「Finish」ボタンを押す FirstFragment SecondFragment ThirdFragment プロジェクトウインドウのツリーに以下が追加される FirstFragment.kt SecondFragment.kt ThirdFragment.kt fragment_pfirst.xml fragment_second.xml fragment_third.xml 画面の切り替えがわかるように、それぞれに配置されているテキストを変更しておく SubAcrivity.kt
package org.refirio.fragment import android.os.Bundle import androidx.appcompat.app.AppCompatActivity class SubActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_sub) } }
以下のように変更する
package org.refirio.fragment import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import org.refirio.fragment.databinding.ActivitySubBinding class SubActivity : AppCompatActivity() { private lateinit var binding: ActivitySubBinding private lateinit var title: TitleFragment override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivitySubBinding.inflate(layoutInflater) setContentView(binding.root) binding.firstButton.setOnClickListener { supportFragmentManager.beginTransaction().apply { replace(R.id.container, FirstFragment()) addToBackStack(null) commit() } } binding.secondButton.setOnClickListener { supportFragmentManager.beginTransaction().apply { replace(R.id.container, SecondFragment()) addToBackStack(null) commit() } } binding.thirdButton.setOnClickListener { supportFragmentManager.beginTransaction().apply { replace(R.id.container, ThirdFragment()) addToBackStack(null) commit() } } title = TitleFragment() supportFragmentManager.beginTransaction().apply { replace(R.id.titleFrame, title) //addToBackStack(null) commit() } } override fun onResume() { super.onResume() title.setTitle("サブ画面") } }
タイトル画面を完成させる activity_main.xml にボタンを配置し、上下左右に制約を追加し、以下の設定を行う id: startButton layout_width: wrap_content layout_height: wrap_content MainActivity.kt
package org.refirio.fragment import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import org.refirio.fragment.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.startButton.setOnClickListener { val intent = Intent(this, SubActivity::class.java) startActivity(intent) } } }
何故か「android Unresolved reference」のエラーになったが、 メニューから「Build → Clean Project」を実行するとビルドできるようになった ■フラグメントと値のやりとり 以下で新規にプロジェクトを作成 プロジェクトの選択: Empty Activity プロジェクトの名前: fragment ビューバインディングを使えるようにする build.gradle を変更したら「Sync Now」をクリック ボタン表示用のフラグメントを作成する プロジェクトウインドウのツリーで「app」を右クリックし、 「New → Fragment → Fragment(Blank)」を選択する 表示されたダイアログで「Fragment Name」で「ButtonFragment」と入力して、あとはデフォルトのまま「Finish」ボタンを押す プロジェクトウインドウのツリーに「ButtonFragment.kt」と「fragment_button.xml」が追加される fragment_button.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" tools:context=".ButtonFragment"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
ラベル表示用のフラグメントを作成する プロジェクトウインドウのツリーで「app」を右クリックし、 「New → Fragment → Fragment(Blank)」を選択する 表示されたダイアログで「Fragment Name」で「LabelFragment」と入力して、あとはデフォルトのまま「Finish」ボタンを押す プロジェクトウインドウのツリーに「LabelFragment.kt」と「fragment_label.xml」が追加される fragment_label.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" tools:context=".LabelFragment" > <TextView android:id="@+id/counterView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
フラグメントのプログラムを実装する ButtonFragment.kt
package org.refirio.fragment import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import org.refirio.fragment.databinding.FragmentButtonBinding class ButtonFragment : Fragment() { private var _binding: FragmentButtonBinding? = null private val binding get() = _binding!! /* * アクティビティがボタンクリック時のコールバックインターフェイスを実装していることを確認 */ override fun onAttach(context: Context) { super.onAttach(context) if (context !is OnButtonClickListener) { throw RuntimeException("リスナーを実装してください") } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = FragmentButtonBinding.inflate(inflater, container, false) binding.button.setOnClickListener { /* * ボタンクリック時のリスナーをセット */ val listener = context as? OnButtonClickListener listener?.onButtonClicked() } return binding.root } override fun onDestroyView() { super.onDestroyView() _binding = null } /* * ボタンクリック時のコールバックインターフェイスを定義 */ interface OnButtonClickListener { fun onButtonClicked() } }
LabelFragment.kt
package org.refirio.fragment import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import org.refirio.fragment.databinding.FragmentLabelBinding class LabelFragment : Fragment() { private var _binding: FragmentLabelBinding? = null private val binding get() = _binding!! private var counter = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) counter = // フラグメントが再生成されたら値を取り出す savedInstanceState?.getInt("counter") // フラグメントのargumentsプロパティから値を取り出す ?: arguments?.getInt("counter") // 値がなければ0をセット ?: 0 } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = FragmentLabelBinding.inflate(inflater, container, false) binding.counterView.text = counter.toString() return binding.root } /* * フラグメントの再生性に対応するため、カウントの値を保存する */ override fun onSaveInstanceState(outState: Bundle) { //super.onSaveInstanceState(outState) outState.putInt("counter", counter) } override fun onDestroyView() { super.onDestroyView() _binding = null } /* * カウントアップして表示を更新する */ fun update() { counter++ binding.counterView.text = counter.toString() } } /* * フラグメントのコンストラクタに引数を渡せるようにする */ fun newLabelFragment(value : Int) : LabelFragment { val fragment = LabelFragment() val args = Bundle() args.putInt("counter", value) // フラグメントのargumentsプロパティは、フラグメントが再生性されても引き継がれる fragment.arguments = args return fragment }
メインアクティビティとそのレイアウトを実装する activity_main.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" tools:context=".MainActivity"> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.5" /> <fragment android:id="@+id/buttonFragment" android:name="org.refirio.fragment.ButtonFragment" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toTopOf="@+id/guideline" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:layout="@layout/fragment_button" /> <FrameLayout android:id="@+id/container" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline"> </FrameLayout> </androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
package org.refirio.fragment import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import org.refirio.fragment.databinding.ActivityMainBinding class MainActivity : AppCompatActivity(), ButtonFragment.OnButtonClickListener { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) val view = binding.root setContentView(view) /* * フラグメントを動的に追加する */ if (supportFragmentManager.findFragmentByTag("LabelFragment") == null) { supportFragmentManager.beginTransaction().apply { add(R.id.container, newLabelFragment(0), "LabelFragment") commit() } } } /* * ButtonFragmentのコールバックインターフェイスを実装する */ override fun onButtonClicked() { val fragment = supportFragmentManager.findFragmentByTag("LabelFragment") as LabelFragment fragment.update() } }
■スライドショー 以下で新規にプロジェクトを作成 プロジェクトの選択: Empty Activity プロジェクトの名前: slideshow ビューバインディングを使えるようにする build.gradle を変更したら「Sync Now」をクリック 使用する画像を用意する 画像を選択してコピーし、app/res/drawable へコピーする 画像表示用のフラグメントを作成する プロジェクトウインドウのツリーで「app」を右クリックし、 「New → Fragment → Fragment(Blank)」を選択する 表示されたダイアログで「Fragment Name」で「ImageFragment」と入力して、あとはデフォルトのまま「Finish」ボタンを押す プロジェクトウインドウのツリーに「ImageFragment.kt」と「fragment_image.xml」が追加される フラグメントに画像を配置する fragment_image.xml を開き、最初から表示されているTextViewを削除し、 「common」内の「ImageView」をドラッグ&ドロップで中央に配置する 画像選択のダイアログが表示されるので、「backgrounds/scenic」を選択して「OK」をクリックする これ背景用の風景写真が表示されるサンプルデータ。プレビュー用なので、実際にプログラム実行時には表示されない (レイアウトエディタで画像は未設定にしておき、プログラムから表示する画像を指定するような場合に使用することができる) また、以下の設定を行う id: imageView layout_width: match_parent layout_height: match_parent scaleType: centerCrop ImageFragment.kt を以下のように変更する
package org.refirio.slideshow import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import org.refirio.slideshow.databinding.FragmentImageBinding private const val IMG_RES_ID = "IMG_RES_ID" class ImageFragment : Fragment() { private var imageResId: Int? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { imageResId = it.getInt(IMG_RES_ID) } } private var _binding: FragmentImageBinding? = null private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = FragmentImageBinding.inflate(inflater, container, false) return binding.root } override fun onDestroyView() { super.onDestroyView() _binding = null } companion object { @JvmStatic fun newInstance(imageResId: Int) = ImageFragment().apply { arguments = Bundle().apply { putInt(IMG_RES_ID, imageResId) } } } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) imageResId?.let { binding.imageView.setImageResource(it) } } }
ViewPager2を使う activity_main.xml を開き、最初から表示されているTextViewを削除し、 「Containers」内の「ViewPager2」をドラッグ&ドロップで中央に配置する 上下左右を画面の端に接続し、マージンは0にする また、以下の設定を行う id: pager layout_width: 0dp layout_height: 0dp MainActivity を以下のように変更する
package org.refirio.slideshow import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.viewpager2.adapter.FragmentStateAdapter import org.refirio.slideshow.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { class MyAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) { private val resources = listOf( R.drawable.brownie, R.drawable.caramel, R.drawable.donut, R.drawable.lemon ) override fun getItemCount(): Int = resources.size override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(resources[position]) } private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.pager.adapter = MyAdapter(this) } }
実行すると、画像がスワイプで順に表示される なお、上記コードの onCreate を以下のように変更すると、5秒ごとに自動で表示が切り替わる (「val handler」以降のみを追加)
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.pager.adapter = MyAdapter(this) val handler = Handler(Looper.getMainLooper()) timer(period = 5000) { handler.post { binding.apply { pager.currentItem = (pager.currentItem + 1) % 4 } } } }
また、activity_main.xml の「Attrivutes」で「All Atrrivutes → orientation」プロパティを「vertical」に設定すると、 左右ではなく上下で画面が切り替わるようになる

Advertisement