メモ > 技術 > 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」に設定すると、
左右ではなく上下で画面が切り替わるようになる