Android Fragment の使用と分析
Fragment は Android 3.0 (API レベル 11) で導入されました。各 Fragment には独自のレイアウトとライフサイクルがあります。
Fragment は単独では存在できず、Activity に依存する必要があります。アクティビティには複数のフラグメントが存在する可能性があり、フラグメントは複数のアクティビティで再利用できます。
Fragmentの基本的な使い方
まず、フラグメントを作成する必要があります。コードは次のとおりです。
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
class BlankFragment : Fragment(R.layout.fragment_blank) {
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)
}
}
companion object {
fun newInstance(param1: String, param2: String) =
BlankFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
Androidx fragment 1.1.0
の後、layoutId
パラメーターとして使用される Fragment コンストラクターを使用できるため、onCreateView
メソッドを。次に例を示します。class BlankFragment : Fragment(R.layout.fragment_blank) { // ...... }
次に、Activity コードを記述し、onCreate() で Fragment を追加します。コードは次のようになります。
class TestActivity : AppCompatActivity() {
private val binding by lazy(LazyThreadSafetyMode.NONE) {
ActivityTestBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
// add Fragment;这是 fragment-ktx 为我们提供的扩展函数
supportFragmentManager.commit {
add(R.id.frag, BlankFragment.newInstance("p1", "p2"))
}
// add Fragment (传统的add Fragment方式)
/* supportFragmentManager.beginTransaction()
.add(R.id.fragmentContainerView, BlankFragment.newInstance("p1", "p2"))
.commit() */
}
}
アクティビティに対応する xml レイアウト コードは次のとおりです。FragmentContainerView が記述されています。
<?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=".TestActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
わかりました、上記のコードを使用して、フラグメントを通常どおりアクティビティに表示できます。
フラグメントコンテナ
これまで Fragment のコンテナとして FrameLayout を使用していましたが、AndroidX Fragment 1.2.0 以降ではFrameLayout の代わりに FragmentContainerViewを使用できるようになりました。
FragmentContainerView は Fragment 専用に設計された View で、FrameLayout を継承しています。
同時に、FragmentContainerView は Fragment をより静的なコードの方法で表示できるようにするいくつかのプロパティを提供します。
- android:name: コンテナに追加する必要がある Fragment を指定できます
- android:tag: Fragmentにタグを設定可能に
<androidx.fragment.app.FragmentContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/fragment_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.MyFragment"
android:tag="my_tag">
</androidx.fragment.app.FragmentContainerView>
FragmentManager
FragmentManager は、Activity または Fragment 内のすべての Fragment の管理を担当します。これは、追加、削除、置換などのFragment
トランザクションの( Transaction
)。
Fragment を動的に追加するには、まず FragmentManager を取得します。
では、FragmentManager を取得するにはどうすればよいでしょうか。
- FragmentActivity : returnの
getSupportFragmentManager()
メソッドを呼び出します。ネストが必要な場合に呼び出されます。Activity
FragmentManager
Activity
Fragment
- Call Fragmentの
getChildFragmentManager()
method :当前Fragment
Returned 、ネストが必要な場合に呼び出されます。FragmentManager
当前Fragment
子Fragment
- Fragmentの
getParentFragmentManager()
methodを呼び出します: 現在の Fragment が Activity にアタッチされている場合、メソッドは Activity を返しますFragmentManager
; 現在の Fragment が別の Fragment の子 Fragment である場合、その親 Fragment の ChildFragmentManager が返されます。
非推奨の API:
1.活動
getFragmentManager()
方法2. Fragmentの
requireFragmentManager()
方法3.フラグメントの
getFragmentManager()
方法
FragmentTranscation
FragmentManager では、Fragment に対するすべての操作は FragmentTransaction を通じて実行されます。
操作の追加/削除
add()
Fragmentの数ある操作の一つで、remove()
、replace()
、hide()
等と同レベルのメソッドがあり、
add()
最初のパラメーターは Fragment をホストするコンテナーの ID、2 番目のパラメーターは Fragment オブジェクト、3 番目のパラメーターは Fragment のTag
名前。
Tag を指定する利点は、次のコードを使用して FragmentManager から Fragment オブジェクトを取得できることです。
val mFragment = supportFragmentManager.findFragmentByTag("my_fragment_tag")
add()
1 つのトランザクションで、 //remove()
同時に実行するなど、複数の操作を実行できますreplace()
。
remove() メソッドにより、フラグメントのライフ サイクルが onDetach で実行され、フラグメント インスタンスが FragmentManager から削除されます。
追加と置換の違い
add()
以前に追加されたフラグメントを削除せずに、コンテナの上部にフラグメントFragment
を追加しreplace()
コンテナの上部にあるものを置き換えますFragment
現在 がありFragmentA
、add()
メソッドを介して が追加された場合FragmentB
、FragmentA
は引き続きアクティブであるため、[戻る] ボタンが押されてFragmentA
も は呼び出されませんonCreateView方法
。
一番上のものを作成しFragmentB
てFragmentA
、コンテナから削除FragmentA
され(この時点で FragmentA は onDestroy を実行します)、FragmentB
一番上になります。
また、アプリケーションにメモリの制約がある場合は、追加ではなく置換を使用することを検討する必要があります。
送信操作
トランザクションごとに、commit()
メソッドを介して送信できます。
commit()
操作は非同期であり、mManager.enqueueAction()
処理のために内部的にキューに入れられます。
commit()
対応する同期方法はcommitNow()
.
commit()
の中に操作がありますcheckStateLoss()
. 間違った使い方をすると, 例えば, メソッドの後にcommit()
操作がonSaveInstanceState()
例外を投げる.commitAllowingStateLoss()
メソッドはcommit()
例外を投げないバージョンのメソッドですが, 極力それを使うようにすべきです.commit()
開発中の問題点を事前に明らかにする。
バックスタックを追加
FragmentManager
これには、アクティビティのタスク スタックに似たロールバック スタック (つまり、BackStack) があります。
addToBackStack()
このメソッドを使用すると、現在のトランザクションをロールバック スタックに追加できます。ユーザーが [戻る] ボタンをクリックすると、トランザクションがロールバックされます。
ロールバックとは、トランザクションが add() の場合、ロールバック操作が remove() であることを意味します。
addToBackStack()
メソッドを追加しないと、ユーザーが戻るボタンをクリックしたときにメソッドが直接破棄されますActivity
。
フラグメントのライフサイクル
図に示すように、フラグメントには合計 11 のライフサイクル メソッドがあります。
それらがどのような状況に対応し、何をするかを見てみましょう。
1、onAttach(context: Context)
メソッドがコールバックされると、Fragment と Activity がバインドされたということになりますが、この時点で mContext = context の操作を実行でき、Activity のコンテキストを正常に取得できます。
2、onCreate(Bundle savedInstanceState)
Fragment の初期化に使用でき、パラメータ savedInstanceState を介して以前の状態を取得できます。
3、onCreateView()
Fragment のレイアウトの初期化、レイアウトの読み込み、FindViewById は通常、この関数で行われます。ここで時間のかかる操作を実行することはお勧めしません。
4、onActivityCreated()
このメソッドが実行されると、Fragment にバインドされた Activity の onCreate メソッドが実行されて返され、Activity を操作する UI 操作はこのメソッドで実行できます。
5、onStart()
このメソッドが実行されると、Activity と一致して Fragment が非表示から表示に変わります。
6、onResume()
このメソッドが実行されると、フラグメントがアクティブになり、ユーザーはそれを操作できます。
7、onPause()
このメソッドが実行されると、フラグメントは一時停止状態になりますが、まだ表示されており、ユーザーはそれを操作できません。
8、onStop()
このメソッドが実行されると、フラグメントは完全に見えなくなります。
9、onDestroyView()
Fragment に関連するビューを破棄します。この時点では Activity からバインドが解除されていないことに注意してください。また、onCreateView メソッドを使用してビューを再作成することもできます。
10、onDestroy()
Destroy the Fragment. このメソッドは通常、終了するために戻るキーが押されたとき、または Fragment がリサイクルされたときに呼び出されます。
11、onDetach()
アクティビティでアンバインドします。
では、運用シナリオによっては、そのライフ サイクルはどのようになるのでしょうか? どれどれ。
show() 和 hide()
メソッドを呼び出すと、
Fragment的正常生命周期方法并不会被执行,此时仅仅是 Fragment 的 View 被显示或者隐藏
呼び出しadd()
方法
onAttach -> onCreate -> onCreateView -> onViewCreated -> onActivityCreated -> onStart -> onResume
呼び出しremove()
方法
onPause -> onStop -> onDestroyView -> onDestroy -> onDetach
呼び出しreplace()
方法
其实就是执行上一个 Fragment 的 remove() 的生命周期 加上 下一个 Fragment 的 add() 的生命周期
detach()
メソッドが呼び出されると、Fragment
ライフ サイクルは次のようになります。
onPause() -> onStop() -> onDestroyView()
// 注意,此时并不会执行 `onDestroy()` 和 `onDetach()` ,也就是说 Fragment 的实例还是存在的。
attach()
メソッドを再度呼び出した後のライフサイクルは次のとおりです。
onCreateView() -> onViewCreated() -> onActivityCreated() -> onStart() -> onResume()
これから、追加と添付の違いもわかります。
add() メソッドを使用して Fragment を追加すると、onAttach() メソッドがトリガーされます。
attach() メソッドを使用して Fragment を追加しても、onAttach() メソッドはトリガーされません。
ホームボタンを押してデスクトップに戻ると、ライフサイクルは次のようになります。
onPause() —> onStop()
デスクトップから Fragment に戻ると、ライフ サイクルは次のようになります。
onStart() —> onResume()
フラグメント通信の問題
方法 1: setFragmentResultListener
2020 年に、Google は Android の新機能をリリースしました。これには、フラグメント間の新しい通信方法が含まれます。
- setFragmentResult を使用してデータを送信する
- setFragmentResultListener を使用してデータを受け入れる
この新しいアプローチの利点は何ですか? 次の点を含めます。
- フラグメント間でデータを渡します。互いに参照を保持しません
- データはライフサイクルがアクティブなときにのみ処理され、フラグメントが予測できない状態にあるときに発生する可能性のある未知の問題を回避します
- ライフサイクルが ON_DESTROY の場合、リスナーを自動的に削除します
- イベントはスティッキーです。つまり、メッセージが最初に送信され、次にリスナーが作成されます。最新のニュースは引き続きリスナーにコールバックできます。
この新しい通信方法がどのように使用されるかを見てみましょう。
// 在 ActivityA 中发送数据
supportFragmentManager.setFragmentResult("request_key",
Bundle().also {
it.putInt("bundle_key", 1)
}
)
// 在 Fragment 中接受数据 ()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
parentFragmentManager.setFragmentResultListener(
"request_key",
viewLifecycleOwner,
object : FragmentResultListener {
override fun onFragmentResult(requestKey: String, result: Bundle) {
// 数据将会在这里回调
}
})
}
Fragment のビューが作成された後にのみ viewLifecycleOwner が null にならないことに注意してください。そのため、Fragment の onViewCreated メソッドにデータを受け取るロジックを入れます。
では、setFragmentResultListener で渡された viewLifecycleOwner はどのように使用されるのでしょうか?
viewLifecycleOwner はコンポーネントのライフ サイクルを認識できることがわかっているので、受信データの監視はFragment がアクティブである場合にのみ更新されたデータを取得し、破棄することが容易に考えられます。
方法 2: ViewModel + LiveData
この方法は、フラグメント間またはホスト アクティビティとの通信についても、Google によって公式に推奨されています。
Activity が破壊されない限り、ViewModel は常に存在し、横画面と縦画面を切り替えて Activity が再構築されても、ViewModel は影響を受けないことがわかっています。
アクティビティには複数のフラグメントを含めることができ、これらのフラグメントは互いに独立しており、同じアクティビティに属しています。したがって、ViewModel と LiveData を使用して、同じ Activity 内の異なる Fragment 間の通信を実現できます。
同じアクティビティに限定されることに注意してください。異なるアクティビティの Fragment 間の通信では、このメソッドを使用できません。
使い方は実にシンプルで、MyFragmentのonViewCreatedメソッドで、ホストActivityのViewModelのLiveDataを取得し、このLiveDataのデータ更新の観測を開始します。次のように:
// MyFragment.kt:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val viewModel = ViewModelProvider(requireActivity())[ActivityViewModel::class.java]
viewModel.value.observe(viewLifecycleOwner) {
// 在这里处理更新后的数据
}
}
方法 3: インターフェイス コールバック
インターフェース コールバックのメソッドは少し面倒ですが、方法でもあります. 以下に例を示します (Fragment はホスト アクティビティにデータを送信します):
// 1、创建一个接口,并定一个回调方法
interface CallbackListener {
fun callback(msg: String)
}
// 2、Activity 代码:
class ParentControlLockActivity : AppCompatActivity() {
// 在 Activity 中持有一个接口监听器
val listener = object : CallbackListener {
override fun callback(msg: String) {
// 在这里处理接收到的数据
}
}
// ......
}
// 3、Fragment 代码:
class BlankFragment : DialogFragment(R.layout.fragment_blank) {
private var mListener: CallbackListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
// 获取宿主 Activity 的监听器
mListener = (activity as ParentControlLockActivity).listener
// 发送消息
mListener?.callback("Fragment发送消息")
}
}
その他の方法
- setArgments
- ハンドラ
- ブロードキャスト
- イベントバス
他にも比較的簡単なコミュニケーション方法がありますので、ここではいちいち例を挙げることはしませんが、興味のある方は他の情報も参考にしてください。
ダイアログフラグメント
Fragment に基づいて、Android は、Fragmet から継承したダイアログ、つまり DialogFragment を使用するためのより良い方法を提供します。DialogFragment は、Dialog のすべてのニーズを実現できます。
DialogFragment を使用すると、ダイアログのライフ サイクルをより適切に管理できます. AlertDialog と比較して、AlertDialog にはライフ サイクル管理がありません. また、横画面と縦画面の切り替えなどの特殊なケースでは、AlertDialog を使用すると多くのバグが発生する可能性があります.
ダイアログフラグメントの使用
最初に DialogFragment を作成します。コードは次のとおりです。
private const val ARG_PARAM1 = "mTitle"
private const val ARG_PARAM2 = "mMessage"
class BlankFragment : DialogFragment() {
private lateinit var mBinding: DialogFragmentBinding
private var mTitle: String? = null
private var mMessage: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.Theme_AppCompat_Dialog)
arguments?.let {
mTitle = it.getString(ARG_PARAM1)
mMessage = it.getString(ARG_PARAM2)
}
}
override fun onResume() {
super.onResume()
dialog?.window?.setLayout(
resources.getDimension(R.dimen.yd_cbc_gallery_dialog_w).toInt(),
resources.getDimension(R.dimen.yd_cbc_gallery_dialog_h).toInt()
)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState)
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
mBinding = DialogFragmentBinding.inflate(inflater)
return mBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
}
private fun initView() {
mBinding.tvTitle.text = mTitle
mBinding.tvSub.text = mMessage
}
companion object {
// 展示 dialog
@JvmStatic
fun showDialog(fm: FragmentManager, param1: String, param2: String) {
BlankFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
show(fm, "BlankFragment")
}
}
}
}
このダイアログをアクティビティに表示する必要がある場合は、次のコードを呼び出すだけです。
BlankFragment.showDialog(supportFragmentManager, "title", "msg")
上記の DialogFragment の使い方以外にも、別の使い方、つまり DialogFragment を書き換えるonCreateDialog
方法が
private var mTitle: String? = null
private var mMessage: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.Theme_AppCompat_Dialog)
arguments?.let {
mTitle = it.getString(ARG_PARAM1)
mMessage = it.getString(ARG_PARAM2)
}
}
// 重写 onCreateDialog 返回一个 dialog
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return AlertDialog.Builder(requireContext()).setTitle(mTitle).setMessage(mMessage).create()
}
companion object {
// 展示 dialog
@JvmStatic
fun showDialog(fm: FragmentManager, param1: String, param2: String) {
BlankFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
show(fm, "BlankFragment")
}
}
}
}
DialogFragment を表示するときは Fragment.show() メソッドを直接使用して Dialog を表示しますが、これは実際には内部でトランザクション送信メソッドを使用しており、これは通常の Fragment と同じです。
// DialogFragment.java:
public int show(@NonNull FragmentTransaction transaction, @Nullable String tag) {
mDismissed = false;
mShownByMe = true;
transaction.add(this, tag);
mViewDestroyed = false;
mBackStackId = transaction.commit();
return mBackStackId;
}