Красота из фрагментов. Как улучшить UI в Android c помощью класса Fragment


В этом уроке:

Рассмотрим взаимодействие между 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 добавим строки:

Fragment 1
Fragment 2
Log
Find

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

fragment1.xml :


xmlns:android =


android:background ="#77ff0000"
android:orientation ="vertical" >
android:id ="@+id/textView"


android:text ="@string/frag1_text" >


fragment2.xml :


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" >
android:id ="@+id/textView"
android:layout_width ="wrap_content"
android:layout_height ="wrap_content"
android:text ="@string/frag2_text" >


Fragment1.java :

package

Import android.app.Fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import
import android.widget.Button;

Public View onCreateView (
Bundle savedInstanceState
) {

;
button.setOnClickListener (new OnClickListener () {

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;

Final String LOG_TAG = "myLogs" ;

Public View onCreateView (LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState
) {

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 :


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

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

android:id ="@+id/fragment2"
android:layout_width ="match_parent"
android:layout_height ="wrap_content"
android:layout_weight ="1" >

Кнопка, компонент 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 import android.os.Bundle;

Public class MainActivity extends Activity {

@Override


;
() .beginTransaction () ;
ft.commit () ;
}
}

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

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

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

Button click in Fragment1

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

Button click in Fragment2

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

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

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

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

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

public void onClick (View v ) {

((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.

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

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

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

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

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

Давайте перепишем обработчик кнопки в первом фрагменте. Будем менять текст кнопки 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.

Теперь меняем 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

В этом уроке, я хочу объяснить, что такое Fragment и почему его нужно использовать. Мы рассмотрим немного теории, а также практический пример применения Fragments.

Представьте себе пазл, который вы можете сложить и получить общий вид картинки, именно так можно представить Fragment . Или же если вы работали разработкой сайтов, а именно PHP то вам знакома возможность подключать общие части сайта например меню как отдельного блока с помощью include .

Шаг 0. Теория

Так как определение не совсем понятно, я решил сказать своими словами его.

Fragment (Фрагмент) – по сути это подобие Activity, которое мы можем подключать в разные части приложения. Но одно Activity может содержать несколько fragment.

Фрагменты появились в API 11 (Android 3.0) для поддержки на более старых версиях был доработан Android Support library.

Также важно понимать, что фрагменты – это не замена активности, они не могут существовать сами по себе, а только вместе с Activity. Поэтому в AndroidManifest регистрировать Fragments не нужно.

Шаг 1. Создаем проект

Давайте создадим простой проект, пока без использования Fragment-ов. Создаем Android Gradle Project:

Теперь создадим Activity в пакете com.myfragmentexam.app назовем его MainActivity :

Package com.myfragmentexam.app; import android.app.Activity; import android.os.Bundle; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_layout); } }

Для этого activity нам нужно создать layout, создаем в res/layout новый layout и называем его main_layout :