110

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

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

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

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

ビューをフラグメントのプライベート フィールドに「保存」し、既に存在する場合はそれを再利用しようとしました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;
}

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

4

9 に答える 9

33

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

ベースフラグメント

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
        }
    }

メインフラグメント

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()
        }
    }
}

ソース

于 2019-11-13T11:13:28.890 に答える
10

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

  • Init ViewModelby navGraphViewModels(ライブ オン ナビゲーション スコープ)
  • 復元する状態を保存します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
于 2019-06-04T03:07:03.543 に答える
1

これは、@ Shahab Rauf が示唆したのと同じ答えです。余分なことは、子フラグメントではなく BaseFragment にのみ Databinding を含めて onCreateView を実装することだけです。また、BaseFragment の onViewCreated() で navController を初期化します。

ベースフラグメント

abstract class BaseFragment<T : ViewDataBinding, VM : BaseViewModel<UiState>> : Fragment() {

protected lateinit var binding: T
var hasInitializedRootView = false
private var rootView: View? = null

protected abstract val mViewModel: ViewModel
protected lateinit var navController: NavController

fun getPersistentView(
    inflater: LayoutInflater?,
    container: ViewGroup?,
    savedInstanceState: Bundle?,
    layout: Int
): View? {
    if (rootView == null) {
        binding = DataBindingUtil.inflate(inflater!!, getFragmentView(), container, false)
        //setting the viewmodel
        binding.setVariable(BR.mViewModel, mViewModel)
        // Inflate the layout for this fragment
        rootView = binding.root
    } 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
}

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? = getPersistentView(inflater, container, savedInstanceState, getFragmentView())


//this method is used to get the fragment layout file
abstract fun getFragmentView(): Int

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    navController = Navigation.findNavController(view)
}
}

HomeFragment (BaseFragment を拡張している任意のフラグメント)

class HomeFragment : BaseFragment<HomeFragmentBinding, HomeViewModel>(),
RecycleViewClickListener {

override val mViewModel by viewModel<HomeViewModel>()

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

}
于 2021-05-07T04:39:56.310 に答える
0

Java開発者の場合、上記の回答から説明および組み合わせたように、

BaseFragment.java

public abstract class BaseFragment<T extends ViewDataBinding, V extends BaseViewModel> extends Fragment {

    private View mRootView;
    private T mViewDataBinding;
    private V mViewModel;
    public boolean hasInitializedRootView = false;
    private View rootView = null;

    public View getPersistentView(LayoutInflater layoutInflater, ViewGroup container, Bundle saveInstanceState, int layout) {

        if (rootView == null) {
            mViewDataBinding = DataBindingUtil.inflate(layoutInflater, layout, container, false);
            mViewDataBinding.setVariable(getBindingVariable(),mViewModel);
            rootView = mViewDataBinding.getRoot();
        }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).
            ViewGroup viewGroup = (ViewGroup) rootView.getParent();
            if (viewGroup != null){
                viewGroup.removeView(rootView);
            }
        }
        return rootView;
    }
}

フラグメントに次のように実装します。

@AndroidEntryPoint
public class YourFragment extends BaseFragment<YourFragmentBinding, YourViewModel> {


@Override
    public View onCreateView(@NonNull @NotNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return getPersistentView(inflater, container, savedInstanceState, getLayoutId());
    }


@Override
    public void onViewCreated(@NonNull @NotNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        if (!hasInitializedRootView){
            hasInitializedRootView = true;
            // do your work here

        }

    }


}
于 2021-10-03T12:34:10.490 に答える