Почему onCreateOptionsMenu() вызывается в каждом фрагменте с использованием метода FragmentTransaction add()?

#android #android-fragments

#Android #android-фрагменты

Вопрос:

Я пытался разобраться в этом и, должно быть, неправильно понимаю, как здесь работают фрагменты, несмотря на много чтения, или я сталкиваюсь с ошибкой.

У меня есть одно действие, которое содержит FrameLayout .

При создании действия я добавляю FragmentA .

     if (savedInstanceState == null) {            
        Fragment newFragment = FragmentA.newInstance();
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.add(R.id.fragment_container, newFragment, FRAGMENT_A_TAG).commit();
    }
  

FragmentA onCreateOptionsMenu метод вызывается, как я и ожидал (хотя, как ни странно onPrepareOptionsMenu , вызывается дважды в первый раз?).

FragmentA создает собственное меню, в данном случае просто элемент меню. При нажатии на пункт меню событие возвращается к действию для создания FragmentB .

     Fragment newFragment = FragmentB.newInstance();
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.add(R.id.fragment_container, newFragment, FRAGMENT_B_TAG);
    ft.addToBackStack(null);
    ft.commit();
  

Теперь вот в чем проблема. На этом этапе я бы ожидал FragmentB , что будут вызываться только ‘ы onCreateOptionsMenu . Но это не так. FragmentA вызывается ‘s onCreateOptionsMenu , за которым следует FragmentB ‘s .

Основное действие не имеет никакого связанного меню, только фрагменты.

Почему это?

Если я использую ft.replace(...) , у меня нет этой проблемы. Но это означает воссоздание представления каждый раз, когда появляется FragmentB, и я пытаюсь этого избежать.

Я надеюсь, что этого достаточно, чтобы продолжить, но вот код для действия и оба фрагмента для ясности.

 public class MainActivity extends AppCompatActivity {

    private static String FRAGMENT_A_TAG = "FRAGMENT_A_TAG";
    private static String FRAGMENT_B_TAG = "FRAGMENT_B_TAG";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        // Watch for button clicks.
        Button button = (Button) findViewById(R.id.btn_switch_fragment);

        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                addFragmentToStack();
            }
        });

        if (savedInstanceState == null) {
            // Do first time initialization -- add initial fragment.
            Fragment newFragment = FragmentA.newInstance();
            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            ft.add(R.id.fragment_container, newFragment, FRAGMENT_A_TAG).commit();
        }
    }

    private void addFragmentToStack() {

        Fragment newFragment = FragmentB.newInstance();
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.add(R.id.fragment_container, newFragment, FRAGMENT_B_TAG);
        ft.addToBackStack(null);
        ft.commit();
    }


}

public class FragmentA extends Fragment {

    public FragmentA() {
        // Required empty public constructor
    }

    public static FragmentA newInstance() {
        FragmentA fragment = new FragmentA();
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setHasOptionsMenu(true);

        System.out.println("onCreate called");
    }

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

        System.out.println("onCreateView called");

        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_a, container, false);
    }



    @Override
    public void onPrepareOptionsMenu(Menu menu) {

        System.out.println("onPrepareOptionsMenu called");

       super.onPrepareOptionsMenu(menu);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.fragment_example_a, menu);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

    }

    @Override
    public void onDetach() {
        super.onDetach();

    }

    @Override
    public void onResume() {
        super.onResume();

        System.out.println("onResume called");
    }
}

public class FragmentB extends Fragment {

    public FragmentB() {
        // Required empty public constructor
    }

    public static FragmentB newInstance() {
        FragmentB fragment = new FragmentB();
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setHasOptionsMenu(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_b, container, false);
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.fragment_example_b, menu);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

    }

    @Override
    public void onDetach() {
        super.onDetach();

    }

}
  

Ответ №1:

Если вы вызываете:

 ft.add(R.id.container, fragmentA, "tag").addToBackStack(null).commit();
  

FragmentA останется «возобновленной», а ее меню будет «раздуто». Затем, если вы вызываете:

 ft.add(R.id.container, fragmentB, "tag").addToBackStack(null).commit();
  

FragmentA по-прежнему «возобновлена», и теперь FragmentB также находится в состоянии «возобновлено», и его меню также будет «раздуто».

Когда вам нужно обновить представление, вы должны использовать view.invalidate() , и этот метод «перерисует» представление.

Так и сказал…

Если у вашего MainActivity есть меню, он вызовет invalidateOptionsMenu(), чтобы перерисовать меню MainActivity, а также нарисовать меню FragmentA. После добавления FragmentB панель инструментов должна добавить свое меню, поэтому она вызовет invalidateOptionsMenu(), чтобы перерисовать меню MainActivity и FragmentA, а также нарисовать меню FragmentB. Вот почему он вызывается каждый раз, когда вы меняете фрагмент, потому что вид меню нужно перерисовывать.

Этого не произойдет, если вы используете

 ft.replace(...)
  

потому что первый фрагмент будет уничтожен.

Надеюсь, это поможет понять.


Регистрируйте onCreateOptionsMenu вашего MainActivity, а также будет вызываться каждый раз, когда вы добавляете фрагмент с меню.

Комментарии:

1. Привет, спасибо за ответ. Это своего рода процесс, о котором я думал. Дело в том, что в моей основной деятельности нет меню, но я зарегистрировал onCreateOptionsMenu и заметил, что оно было вызвано. Чтобы уточнить, когда добавляется фрагмент B (после фрагмента A), поскольку меню фрагмента B нуждается в заполнении, invalidateOptionsMenu() вызывается в activity, который, в свою очередь, вызывает onCreateOptionsMenu любые связанные фрагменты, это правильно?

2. Во фрагментах вам нужно указать, есть ли у него меню, иначе он не будет запускать onCreateOptionsMenu. В Activities [onCreateOptionsMenu] выполняется по умолчанию до завершения выполнения метода onCreate.

3. Я понимаю это, и вы увидите, что мои фрагменты указывают свое собственное menu ( setHasOptionsMenu(true); ) , но мой вопрос в том, когда добавляется новый фрагмент, автоматически ли вызывается действие invalidateOptionsMenu() и, в свою очередь, затем запускается (если фрагмент имеет набор меню) onCreateOptionsMenu() в каждом фрагменте, который был добавлен в действие? Я сам проведу быстрый тест, вручную вызвав действия invalidateOptionsMenu() .

4. Похоже, это происходит. Если я добавляю два фрагмента в действие, даже если действие не указывает меню, и я добавляю два фрагмента, onPrepareOptionsMenu() вызывается для обоих фрагментов (как я и ожидал). Если я затем вызываю getActivity().invalidateOptionsMenu() каждый фрагмент onCreateOptionsMenu() , вызывается.

5. Да, и если в прикрепленном фрагменте нет меню, onCreateOptionsMenu вызываться не будет. Это потому, что нет необходимости перерисовывать представление. Привет из Мексики.