MyTetra Share
Делитесь знаниями!
Урок 106. Android 3. Fragments. Взаимодействие с Activity
22.04.2017
11:19
Текстовые метки: Fragment
Раздел: Android - Ресурсы - StartAndroid


Урок 106. Android 3. Fragments. Взаимодействие с Activity






В этом уроке:

- рассмотрим взаимодействие между Activity и ее фрагментами

 

После размещения фрагмента, хотелось бы начать с ним взаимодействовать. Т.е. размещать View-компоненты и работать с ними, обращаться к фрагментам из Activity и наоборот. Попробуем это реализовать.

Для чистоты эксперимента будем работать с двумя фрагментами: статическим и динамическим.

 

Создадим проект:

Project name: P1061_FragmentActivity
Build Target: Android 4.1 
Application name: FragmentActivity
Package name: ru.startandroid.develop.p1061fragmentactivity
Create Activity: MainActivity

В strings.xml добавим строки:

<string name="frag1_text">Fragment 1</string>

<string name="frag2_text">Fragment 2</string>

<string name="log">Log</string>

<string name="find">Find</string>

 

Создаем layout и классы для двух фрагментов.

fragment1.xml:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="#77ff0000"

android:orientation="vertical">

<TextView

android:id="@+id/textView"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="@string/frag1_text">

</TextView>

<Button

android:id="@+id/button"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="@string/log">

</Button>

</LinearLayout>

 

fragment2.xml:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="#7700ff00"

android:orientation="vertical">

<TextView

android:id="@+id/textView"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="@string/frag2_text">

</TextView>

<Button

android:id="@+id/button"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="@string/log">

</Button>

</LinearLayout>

 

Fragment1.java:

package ru.startandroid.develop.p1061fragmentactivity;


import android.app.Fragment;

import android.os.Bundle;

import android.util.Log;

import android.view.LayoutInflater;

import android.view.View;

import android.view.View.OnClickListener;

import android.view.ViewGroup;

import android.widget.Button;


public class Fragment1 extends Fragment {


final String LOG_TAG = "myLogs";


public View onCreateView(LayoutInflater inflater, ViewGroup container,

Bundle savedInstanceState) {

View v = inflater.inflate(R.layout.fragment1, null);


Button button = (Button) v.findViewById(R.id.button);

button.setOnClickListener(new OnClickListener() {

public void onClick(View v) {

Log.d(LOG_TAG, "Button click in Fragment1");

}

});

return v;

}

}

У фрагмента нет привычного для нас метода findViewById для поиска компонентов с экрана. Поэтому вызываем этот метод для View, которое будет содержимым фрагмента. В методе onCreateView мы создаем View и сразу же находим в нем кнопку и ставим ей обработчик. Затем отдаем View системе.

 

Fragment2.java:

package ru.startandroid.develop.p1061fragmentactivity;


import android.app.Fragment;

import android.os.Bundle;

import android.util.Log;

import android.view.LayoutInflater;

import android.view.View;

import android.view.View.OnClickListener;

import android.view.ViewGroup;

import android.widget.Button;


public class Fragment2 extends Fragment {


final String LOG_TAG = "myLogs";


public View onCreateView(LayoutInflater inflater, ViewGroup container,

Bundle savedInstanceState) {

View v = inflater.inflate(R.layout.fragment2, null);

Button button = (Button) v.findViewById(R.id.button);

button.setOnClickListener(new OnClickListener() {

public void onClick(View v) {

Log.d(LOG_TAG, "Button click in Fragment2");

}

});

return v;

}

}

Все аналогично Fragment1.

 

Настраиваем основное Activity.

main.xml:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/LinearLayout1"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

<Button

android:id="@+id/btnFind"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:onClick="onClick"

android:text="@string/find">

</Button>

<fragment

android:id="@+id/fragment1"

android:name="ru.startandroid.develop.p1061fragmentactivity.Fragment1"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_weight="1"

tools:layout="@layout/fragment1">

</fragment>

<FrameLayout

android:id="@+id/fragment2"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_weight="1">

</FrameLayout>

</LinearLayout>

Кнопка, компонент fragment, в который помещен Fragment1, и контейнер FrameLayout, в который потом поместим Fragment2.

Обратите внимание на атрибут tools:layout. В нем указан layout-файл, и мы можем на этапе разработки видеть, как будет выглядеть статический фрагмент, когда приложение будет запущено.

Для этого надо нажать правой кнопкой на компоненте fragment, и через пункт Fragment Layout указать нужный layout.

 

MainActivity.java:

package ru.startandroid.develop.p1061fragmentactivity;


import android.app.Activity;

import android.app.Fragment;

import android.app.FragmentTransaction;

import android.os.Bundle;


public class MainActivity extends Activity {


@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

Fragment frag2 = new Fragment2();

FragmentTransaction ft = getFragmentManager().beginTransaction();

ft.add(R.id.fragment2, frag2);

ft.commit();

}

}

Здесь мы просто добавляем Fragment2 в контейнер.

 

Все сохраняем, запускаем приложение.

 

Жмем кнопку Log в первом фрагменте и смотрим лог:

Button click in Fragment1

 

Жмем Log во втором фрагменте:

Button click in Fragment2

 

Все ок. Компоненты в фрагментах нашлись и обработчики среагировали на нажатия.

Атрибут onClick, который мы привыкли использовать для кнопки, здесь не прокатит. Указанный в этом атрибуте метод, будет вызван в Activity, а не в фрагменте.

 

Доступ к фрагменту из Activity

Разберемся, как получить доступ к фрагменту из Activity. Для этого у FragmentManager есть методfindFragmentById, который на вход принимает id компонента fragment (если фрагмент статический) или id контейнера (если динамический).

У нас в main.xml есть кнопка btnFind, вызывающая метод onClick при нажатии. Дорисуем в MainActivity.javaметод onClick:

public void onClick(View v) {

Fragment frag1 = getFragmentManager().findFragmentById(R.id.fragment1);

((TextView) frag1.getView().findViewById(R.id.textView))

.setText("Access to Fragment 1 from Activity");


Fragment frag2 = getFragmentManager().findFragmentById(R.id.fragment2);

((TextView) frag2.getView().findViewById(R.id.textView))

.setText("Access to Fragment 2 from Activity");

}

Используем метод findFragmentById. В первом случае на вход передаем id компонента fragment, т.к. Fragment1 у нас размещен именно так. При поиске Fragment2 указываем id контейнера, в который этот фрагмент был помещен. В результате метод findFragmentById возвращает нам объект Fragment.

Далее мы получаем доступ к его View с помощью метода getView, находим в нем TextView и меняем текст.

Все сохраняем, запускаем. Жмем кнопку Find

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

На всякий случай проговорю одну вещь из разряда «Спасибо кэп!». Если посмотреть на код MainActivity, то можно заметить, что работая с frag2 в методе onCreate и с frag2 в методе onClick мы работаем с текущим фрагментом Fragment2. Это так и есть. Оба frag2 в итоге будут ссылаться на один объект. Так что, если вы динамически добавили фрагмент, то у вас уже есть ссылка на него, и искать его через findFragmentById вам уже не надо.

 

Доступ к Activity из фрагмента 

Теперь попробуем из фрагмента поработать с Activity. Для этого фрагмент имеет метод getActivity.

Давайте перепишем обработчик кнопки в первом фрагменте. Будем менять текст кнопки btnFind.

Fragment1.java:

package ru.startandroid.develop.p1061fragmentactivity;


import android.app.Fragment;

import android.os.Bundle;

import android.view.LayoutInflater;

import android.view.View;

import android.view.View.OnClickListener;

import android.view.ViewGroup;

import android.widget.Button;


public class Fragment1 extends Fragment {


public View onCreateView(LayoutInflater inflater, ViewGroup container,

Bundle savedInstanceState) {

View v = inflater.inflate(R.layout.fragment1, null);

Button button = (Button) v.findViewById(R.id.button);

button.setOnClickListener(new OnClickListener() {

public void onClick(View v) {

((Button)getActivity().findViewById(R.id.btnFind)).setText("Access from Fragment1");

}

});

return v;

}

}

Получаем Activity методом getActivity, ищем в нем кнопку и меняем текст.

Сохраняем, запускаем. Жмем кнопку в первом фрагменте:

Работает. Из фрагмента мы поменяли компонент Activity.

 

Обработка в Activity события из фрагмента 

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

Например, в Activity есть два фрагмента. Первый – список заголовков статей. Второй – отображает содержимое статьи, выбранной в первом. Мы нажимаем на заголовок статьи в первом фрагменте и получаем содержимое во втором. В этом случае, цель первого фрагмента – передать в Activity информацию о том, что выбран заголовок. А Activity дальше уже сама решает, что делать с этой информацией. Если, например, приложение запущено на планшете в горизонтальной ориентации, то можно отобразить содержимое статьи во втором фрагменте. Если же приложение запущено на смартфоне, то экран маловат для двух фрагментов и надо запускать отдельное Activity со вторым фрагментом, чтобы отобразить статью.

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

Тут немного отвлекусь на небольшое лирическое отступление. Модульность, вообще, - очень важная и полезная штука. И ее надо использовать для универсальности, удобности и легкости в понимании работы своих приложений. Но уникальных рецептов, как правильно все организовать, конечно, нет. Каждый делает по-своему. Именно по этим причинам я в своих уроках даю чисто технические вещи про отдельные компонентыи не рассказываю, как организовывать и писать целое приложение. Иначе, форум бы уже ломился от сообщений, что я все делаю не так и надо по-другому, и каждый бы излагал свое видение. И была бы куча споров, где одна сторона говорит, что крокодил зеленый, а другая сторона говорит, что он нифига не зеленый, а длинный ))

Вернемся к уроку. Фрагмент должен сообщить в Activity, что выбрана статья. Для этого он будет вызывать некий метод в Activity. И как нам сообщает хелп, лучший способ тут – это использовать интерфейс, который мы опишем в фрагменте и который затем будет реализован в Activity. Схема известная и распространенная. Давайте реализуем. В нашем приложении никаких статей нет, поэтому будем просто передавать произвольную строку из второго фрагмента в Activity. А Activity уже будет отображать эту строку в первом фрагменте.

Перепишем Fragment2.java:

package ru.startandroid.develop.p1061fragmentactivity;


import android.app.Activity;

import android.app.Fragment;

import android.os.Bundle;

import android.view.LayoutInflater;

import android.view.View;

import android.view.View.OnClickListener;

import android.view.ViewGroup;

import android.widget.Button;


public class Fragment2 extends Fragment {

public interface onSomeEventListener {

public void someEvent(String s);

}

onSomeEventListener someEventListener;

@Override

public void onAttach(Activity activity) {

super.onAttach(activity);

try {

someEventListener = (onSomeEventListener) activity;

} catch (ClassCastException e) {

throw new ClassCastException(activity.toString() + " must implement onSomeEventListener");

}

}


final String LOG_TAG = "myLogs";


public View onCreateView(LayoutInflater inflater, ViewGroup container,

Bundle savedInstanceState) {

View v = inflater.inflate(R.layout.fragment2, null);

Button button = (Button) v.findViewById(R.id.button);

button.setOnClickListener(new OnClickListener() {

public void onClick(View v) {

someEventListener.someEvent("Test text to Fragment1");

}

});

return v;

}

}

Описываем интерфейс onSomeEventListener. В нем метод someEvent, который на вход получает строку. Этот интерфейс будет реализовывать Activity.

В методе onAttach мы на вход получаем Activity, к которому присоединен фрагмент. Мы пытаемся привести это Activity к типу интерфейса onSomeEventListener, чтобы можно было вызывать метод someEvent и передать туда строку.  Теперь someEventListener ссылается на Activity.

Далее, в onCreateView, в обработчике кнопки мы вызываем метод someEvent и передаем туда текст. Этот метод будет отработан в Activity.

 

Теперь меняем Activity.

MainActivity.java:

package ru.startandroid.develop.p1061fragmentactivity;


import ru.startandroid.develop.p1061fragmentactivity.Fragment2.onSomeEventListener;

import android.app.Activity;

import android.app.Fragment;

import android.app.FragmentTransaction;

import android.os.Bundle;

import android.widget.TextView;


public class MainActivity extends Activity implements onSomeEventListener{


@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

Fragment frag2 = new Fragment2();

FragmentTransaction ft = getFragmentManager().beginTransaction();

ft.add(R.id.fragment2, frag2);

ft.commit();

}


@Override

public void someEvent(String s) {

Fragment frag1 = getFragmentManager().findFragmentById(R.id.fragment1);

((TextView)frag1.getView().findViewById(R.id.textView)).setText("Text from Fragment 2:" + s);

}

}

Дописываем интерфейс onSomeEventListener к описанию класса.

onCreate без изменений.

Реализуем метод someEvent. Просто ищем первый фрагмент и вставляем туда текст.

 

Все сохраняем и запускаем. Жмем кнопку во втором фрагменте:

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

 

На следующем уроке:

- размещаем элементы в ActionBar

Так же в этом разделе:
 
MyTetra Share v.0.52
Яндекс индекс цитирования