2. Добавление фрагментов в Activity. Менеджер фрагментов

Рассмотрим процесс создания фрагментов и добавления их в Activity.

Как правило, фрагмент добавляет часть пользовательского интерфейса в Activity, и этот интерфейс встраивается в общую иерархию представлений Activity. Разработчик может добавить фрагмент в макет Activity двумя способами:

  • объявив фрагмент в файле макета Activity;

  • программно, добавив фрагмент в существующий объект ViewGroup.

Рассмотрим оба этих способа и попутно разберемся, как же создавать и использовать фрагменты.

Объявление фрагмента в макете Activity

Создадим тестовое приложение с одним Activity.

Создадим два пустых класса - MenuFragment и DetailsFragment. Оба класса наследуются от базового класса android.support.v4.app.Fragment.

Перейдем в макет Activity, найдем компонент под названием <fragment> и добавим их в макет, указав для каждого фрагмента соответствующий класс фрагмента, который мы только что создали. В нашем тестовом примере макет будет выглядеть следующим образом

Теперь перейдем непосредственно к процессу создания фрагментов.

Создание фрагмента

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

Чтобы создать макет для фрагмента, разработчик должен реализовать метод обратного вызова onCreateView(), который система Android вызывает, когда для фрагмента наступает время отобразить свой макет. Реализация этого метода должна возвращать объект View, который является корневым в макете фрагмента.

Чтобы возвратить макет из метода onCreateView(), можно выполнить его раздувание из ресурса макета, определенного в XML-файле. Для этой цели метод onCreateView() предоставляет объект LayoutInflater.

Создадим макеты для двух наших фрагментов, fragment_menu.xml и fragment_details.xml.

Теперь перейдем в классы фрагментов и переопределим метод обратного вызова onCreateView().

MenuFragment.java
public class MenuFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_menu, container, false);
    }
}

Запустим эмулятор и посмотрим на результат.

Чтобы показать преимущество фрагментов перед использованием Activity напрямую, создадим макет Activity для ландшафтной ориентации мобильного устройства.

Создадим макет activity_main, указав квалификатор Orientation как Landscape (такой ресурс называется альтернативным, мы будем говорить о таких ресурсах позже).

Этот макет будет добавлен в папку layout-land. Создадим макет с двумя фрагментами. Мы не меняем макеты и код класса фрагментов, а просто в альтернативном макете Activity изменяем компоновку фрагментов в окне.

Запустим приложение и повернем эмулятор, чтобы перевести устройство в ландшафтный режим.

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

Добавление макета через объявление его в макете Activity реализуется достаточно просто, но имеет один важный недостаток - мы не можем динамически менять фрагменты во время работы приложения. Чтобы добиться такого поведения от фрагментов, необходимо добавлять их программно, что мы и рассмотрим далее.

Программное добавление фрагментов

Вместе с программным добавлением фрагментов рассмотрим вопрос передачи данных из фрагмента в фрагмент и вопрос взаимодействия фрагмента с Activity, к которому он прикреплен.

Модифицируем фрагменты следующим образом - добавим в фрагмент MenuFragment поле ввода и кнопку. По нажатию на кнопку, данные из поля ввода будут переданы в DetailsFragment и выведены на экран.

Программирование MenuFragment

Модифицируем фрагмент MenuFragment. Добавим в макет фрагмента поле ввода и кнопку. По нажатию на кнопку мы должны вызывать метод Activity, в котором будет осуществлено динамическое добавление

Добавим переопределение для методов жизненного цикла. Метод onCreateView() вызывается в момент создания дерева элементов графического интерфейса. В теле метода мы должны осуществить inflate макета фрагмента, после чего передать дерево элементов как результат работы метода. Метод onViewCreated() вызывается уже после успешного создания графического интерфейса макета. В этом методе можно получить доступ и манипулировать элементами UI. Добавим обработчик нажатия на кнопку

MenuFragment.java
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment_menu, container, false);
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    Button button = view.findViewById(R.id.button);
    button.setOnClickListener(v -> {
        EditText et = view.findViewById(R.id.editText);
        String text = et.getText().toString();
        
        // Вызов метода Activity и отправка данных
    });
}

Для взаимодействия с Activity, к которому прикреплен фрагмент, можно воспользоваться методом getActivity() фрагмента, после чего предусмотреть в Activity публичный метод для вызова из фрагмента.

Однако, корректный и безопасный способ состоит в создании интерфейса, который будет реализовывать Activity и получение ссылки на Activity во время вызова метода жизненного цикла onAttach() (этот метод вызывается в момент присоединения Activity) и обнулении ссылки на Activity во время вызова метода жизненного цикла onDetach() (метод вызывается в момент отсоединения фрагмента).

MenuFragment.java
public class MenuFragment extends Fragment {
    
    ...
    
    private OnFragmentInteractionListener mListener;

    ...

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    public interface OnFragmentInteractionListener {
        void onFragmentInteraction(String result);
    }
}

Полностью код класса MenuFragment будет выглядеть следующим образом

MenuFragment.java
public class MenuFragment extends Fragment {

    private OnFragmentInteractionListener mListener;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    public interface OnFragmentInteractionListener {
        void onFragmentInteraction(String result);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_menu, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        Button button = view.findViewById(R.id.button);
        button.setOnClickListener(v -> {
            EditText et = view.findViewById(R.id.editText);
            String text = et.getText().toString();

            // Вызов метода Activity и отправка данных
            mListener.onFragmentInteraction(text);
        });
    }
}

Программирование MainActivity

Изменим макет Activity. Вместо элементов <fragment> добавим контейнеры (FrameLayout), к которым будут в дальнейшем прикреплены фрагменты.

Отредактируем класс MainActivity. Добавим фрагмент MenuFragment в методе onCreate(). Для управления фрагментами в Activity используется объект FragmentManager. Чтобы его получить, следует вызвать метод getSupportFragmentManager().

Получим объект менеджера фрагментов и добавим фрагмент в макет Activity.

MainActivity.java
public class MainActivity extends AppCompatActivity {

    private FragmentManager manager;

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

        manager = getSupportFragmentManager();
        manager.beginTransaction()
                .add(R.id.frame1,new MenuFragment(),"frag1")
                .commit();
    }
}

Чтобы обработать нажатие кнопки в MenuFragment в MainActivity, необходимо реализовать интерфейс MenuFragment.OnFragmentInteractionListener.

MainActivity.java
public class MainActivity extends AppCompatActivity implements MenuFragment.OnFragmentInteractionListener {

    private FragmentManager manager;

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

        manager = getSupportFragmentManager();
        manager.beginTransaction()
                .add(R.id.frame1,new MenuFragment(),"frag1")
                .commit();
    }

    @Override
    public void onFragmentInteraction(String result) {

    }
}

В методе onFragmentInteraction() необходимо создать объект DetailsFragment, передать строку с текстом и добавить его в Activity. Для того чтобы это сделать, необходимо запрограммировать класс DetailsFragment.

Программирование DetailsFragment

Модифицируем фрагмент DetailsFragment. Для класса фрагмента необходимо добавить конструктор без параметров (для того чтобы фрагмент могла восстановить ОС), создание фрагмента лучше всего реализовать с помощью статического фабричного метода. Передача данных в фрагмент осуществляется с помощью механизма Arguments.

В методе OnViewCreated() установим значение текстового поля - строку, которая была передана с помощью механизма Arguments.

DetailsFragment.java
public class DetailsFragment extends Fragment {

    private static final String ARG_PARAM1 = "param1";

    private String mParam1;

    public DetailsFragment() {}

    public static DetailsFragment newInstance(String param1) {
        DetailsFragment fragment = new DetailsFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
        }
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_details, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        TextView textView = view.findViewById(R.id.textView);
        textView.setText(mParam1);
    }
}

Теперь вернемся в MainActivity и в методе onFragmentInteraction() добавим фрагмент DetailsFragment.

MainActivity.java
@Override
public void onFragmentInteraction(String result) {
    manager.beginTransaction()
            .add(R.id.frame2,DetailsFragment.newInstance(result),"frag2")
            .commit();
}

Запустим приложение и посмотрим на результат

Last updated