web-dev-qa-db-ja.com

JetpackのAndroidナビゲーションコンポーネントを使用してフラグメントを破棄/再作成

既存のアプリに Jetpackのアーキテクチャコンポーネントを使用したナビゲーション を実装しようとしています。

メインフラグメント(ListFragment)がアイテムのリストである単一のアクティビティアプリがあります。現在、ユーザーがリスト項目をタップすると、2番目のフラグメントがfragmentTransaction.add(R.id.main, detailFragment)によってスタックに追加されます。したがって、戻るボタンを押すと、DetailFragmentが切り離され、ListFragmentが再び表示されます。

ナビゲーションアーキテクチャでは、これは自動的に処理されます。新しいフラグメントを追加する代わりに、それは replaced なので、フラグメントビューが破棄され、onDestroyView()が呼び出され、ビューを再作成するために戻るボタンが押されたときにonCreateView()が呼び出されます。

これは LiveDataViewModel で使用して、必要以上のメモリの使用を回避するのに適したパターンであることを理解していますが、私の場合、リストのレイアウトが複雑で、また、リストのスクロール位置を保存して、ユーザーがフラグメントを残したのと同じ位置まで再度スクロールする必要があるためです。それは可能ですが、より良い方法が存在するはずです。

フラグメントのプライベートフィールドにビューを「保存」し、onCreateView()で既に使用している場合は再利用しようとしましたが、アンチパターンのようです。

private View view = null;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

    if (view == null) {
        view = inflater.inflate(R.layout.fragment_list, container, false);
        //...
    }

    return view;
}

レイアウトの再膨張を回避するための、他のよりエレガントな方法はありますか?

39
pauminku

Googleのイアンレイクは、ビューを変数に保存できると返信しました代わりに新しいレイアウトを拡張するを返すだけのインスタンスonCreateView()の事前保存されたビュー

出典: https://Twitter.com/ianhlake/status/1103522856535638016

Leakcanaryはこれをリークとして示す可能性がありますが、false positive..

27
erluxman

以下の実装を通じてフラグメントの永続的なビューを持つことができます

BaseFragment

open class BaseFragment : Fragment(){

        var hasInitializedRootView = false
        private var rootView: View? = null

        fun getPersistentView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?, layout: Int): View? {
            if (rootView == null) {
                // Inflate the layout for this fragment
                rootView = inflater?.inflate(layout,container,false)
            } else {
                // Do not inflate the layout again.
                // The returned View of onCreateView will be added into the fragment.
                // However it is not allowed to be added twice even if the parent is same.
                // So we must remove rootView from the existing parent view group
                // (it will be added back).
                (rootView?.getParent() as? ViewGroup)?.removeView(rootView)
            }

            return rootView
        }
    }

MainFragment

class MainFragment : BaseFragment() {


    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return getPersistentView(inflater, container, savedInstanceState, R.layout.content_main)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        if (!hasInitializedRootView) {
            hasInitializedRootView = true
            setListeners()
            loadViews()
        }
    }
}

ソース

5
Shahab Rauf

私はこのようにしてみましたが、うまくいきました。

  • Init ViewModel by navGraphViewModels(Live on Navigationスコープ)
  • 復元する状態をViewModelに保存します
// fragment.kt
private val vm by navGraphViewModels<VM>(R.id.nav_graph) { vmFactory }

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    // Restore state
    vm.state?.let {
        (recycler.layoutManager as GridLayoutManager).onRestoreInstanceState(it)
    }
}

override fun onPause() {
    super.onPause()
    // Store state
    vm.state = (recycler.layoutManager as GridLayoutManager).onSaveInstanceState()
}

// vm.kt
var state:Parcelable? = null
4
Samnang CHEA

自分の目的地を置くだけ。

<action
   Android:id="@+id/action_piecesReferenceCount_self"
   app:destination="@id/piecesReferenceCount" />
Navigation.findNavController(myview).navigate(R.id.action_piecesReferenceCount_self);