Статьи

Фрагмент Android-транзакции: FragmentManager и Backstack

Фрагменты полезны, когда мы хотим поддерживать несколько размеров экрана . Для управления фрагментами нам нужен FragmentManager, который помогает нам обрабатывать транзакции между фрагментами. Под транзакцией мы подразумеваем последовательность шагов для добавления, замены или удаления фрагментов . В последнем посте мы показали, как поддерживать несколько размеров экрана и ориентацию с использованием фрагментов . Теперь мы хотим пойти немного дальше.

В Android есть два разных способа создания фрагмента:

  • статический метод
  • динамический метод

Статический метод — это когда мы «записываем» непосредственно наш фрагмент в файл XML. Это пример:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity"
    android:orientation="horizontal" >
 
        <fragment android:id="@+id/listFragment"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              class="com.survivingwithandroid.fragment.LinkListFragment"
              android:layout_weight="2"/>
</LinearLayout>

В этом случае наш макет не является динамическим, потому что мы не можем управлять фрагментом во время выполнения. Поэтому, если мы хотим сделать наш макет динамичным (а это происходит очень часто), нам нужно сделать это по-другому. Мы должны использовать FrameLayout. С FrameLayout мы можем обрабатывать фрагменты так, как нам нужно во время выполнения, но для этого нам нужен менеджер, другими словами, компонент, который может обрабатывать фрагменты. Это FragmentManager . Этот компонент может добавлять , заменять и удалять фрагменты во время выполнения. Расположение выше становится:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >
 
    <FrameLayout
        android:id="@+id/listFragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
    />
 
</RelativeLayout>

Так что теперь у нас есть свобода « вставлять » в FrameLayout наши фрагменты.

FragmentManager

Как мы уже говорили, FragmentManager является ключевым компонентом. Используя FragmentManager, мы можем обнаружить ( найти ) фрагмент внутри нашего макета, используя findFragmentById или findFragmentByTag . В то время как первый метод очень прост, и мы используем общий идентификатор Android для обнаружения компонента, второй метод (который использует тег) является необычным. Тег во фрагменте — это просто «имя», которое мы даем фрагменту, чтобы мы могли найти его позже, используя это имя. Нас больше интересуют некоторые другие методы.

Вся операция, выполняемая FragmentManager, происходит внутри «транзакции», как в операции с базой данных. Во-первых, мы можем получить FragmentManger, используя метод Activity getFragmentManager () . Получив ссылку на этот компонент, мы должны начать транзакцию следующим образом:

1
2
3
4
5
6
7
8
9
FragmentManager fm = getFragmentManager();
 
// Transaction start
FragmentTransaction ft = fm.beginTransaction();
 
.......
 
// Transaction commint
ft.commit();

В конце, когда мы закончили, и мы готовы показать наш фрагмент, мы должны вызвать метод commit, который отмечает конец транзакции. Например, если мы помним пример, который мы показали в предыдущем посте, и используя FrameLayout, мы можем «вставить» список ссылок следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class MainActivity extends Activity implements ChangeLinkListener{
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        FragmentManager fm = getFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();
 
        LinkListFragment llf = new LinkListFragment();
        ft.replace(R.id.listFragment, llf);
        ft.commit();
    }
  ...
 
}

Но … Какую операцию мы можем выполнить внутри транзакции? … Ну, мы можем:

  • добавить новый фрагмент
  • заменить существующий фрагмент
  • удалить фрагмент

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
if (findViewById(R.id.fragPage) != null) {
    WebViewFragment wvf = (WebViewFragment) getFragmentManager().findFragmentById(R.id.fragPage);
 
// Part 1: Tablet and so on
    if (wvf == null) {
        System.out.println("Dual fragment - 1");
        wvf = new WebViewFragment();
        wvf.init(linkData.getLink());
        // We are in dual fragment (Tablet and so on)
        FragmentManager fm = getFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();
        //wvf.updateUrl(link);
        ft.add(R.id.fragPage, wvf);
        ft.commit();
 
    }
    else {
     Log.d("SwA", "Dual Fragment update");
     wvf = new WebViewFragment();
      wvf.init(linkData.getLink());
      FragmentManager fm = getFragmentManager();
     FragmentTransaction ft = fm.beginTransaction();           
     ft.replace(R.id.fragPage, wvf);
     ft.commit();
 
     //wvf.updateUrl(linkData.getLink());
    }
}
else {
    Log.d("SwA", "replace");
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    WebViewFragment wvf =  new WebViewFragment();
    wvf.init(linkData.getLink());
    ft.replace(R.id.listFragment, wvf);
    ft.commit();
}

Если вы сравните этот фрагмент кода с последним примером в предыдущем посте, вы заметите некоторые различия. Сначала в зеленой части мы больше не запускаем действие, когда пользователь нажимает на ссылку, а просто заменяем FrameLayout фрагментом, показывающим веб-страницу. Более того, в желтой части мы не обновляем текущий фрагмент внутри FrameLayout, но создаем новый фрагмент и заменяем существующий фрагмент только что созданным. Поведение приложения всегда одинаково, но мы получили это поведение по-другому, используя динамические фрагменты внутри нашего макета.

Если вы запустите приложение и начнете его использовать, вы можете заметить «проводное» поведение при нажатии кнопки «Назад». Мы ожидаем, что кнопка «Назад» приведет нас к последней посещенной веб-странице, но это не так, как мы предполагали. Когда вы нажимаете кнопку «Назад», вы попадаете на домашнюю страницу.

android_fragment_tutorial2 android_fragment_tutorial2 android_fragment_tutorial3

Почему?

Фрагмент Backstack

Поведение, описанное выше, является нормальным, поскольку кнопка «Назад» действует на уровне активности, а не на уровне фрагмента. Таким образом, наша деятельность остается неизменной, пока мы заменяем фрагменты, когда пользователь взаимодействует с приложением. Таким образом, когда мы нажимаем кнопку «Назад», мы выбираем первое действие в стеке действий, в нашем случае — домашнее. Нам не нужно такое поведение, но мы хотим, чтобы при нажатии кнопки «Назад» мы возвращались в стек фрагментов. Мы можем добиться этого, добавив фрагмент в backstack . Мы делаем это следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@Override
public void onLinkChange(LinkData linkData) {       
    System.out.println("Listener");
    // Here we detect if there's dual fragment
    if (findViewById(R.id.fragPage) != null) {
        WebViewFragment wvf = (WebViewFragment) getFragmentManager().findFragmentById(R.id.fragPage);
 
        if (wvf == null) {
            System.out.println("Dual fragment - 1");
            wvf = new WebViewFragment();
            wvf.init(linkData.getLink());
            // We are in dual fragment (Tablet and so on)
            FragmentManager fm = getFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            //wvf.updateUrl(link);
            ft.add(R.id.fragPage, wvf);
            ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
            // Add to backstack
            ft.addToBackStack(linkData.getName());
            ft.commit();
 
        }
        else {
         Log.d("SwA", "Dual Fragment update");
         wvf = new WebViewFragment();
          wvf.init(linkData.getLink());
          FragmentManager fm = getFragmentManager();
         FragmentTransaction ft = fm.beginTransaction();           
         ft.replace(R.id.fragPage, wvf);
         ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
          // Add to backstack
          ft.addToBackStack(linkData.getName());
          ft.commit();
 
         //wvf.updateUrl(linkData.getLink());
        }
    }
    else {
        /*
        System.out.println("Start Activity");
        Intent i = new Intent(this, WebViewActivity.class);
        i.putExtra("link", link);
        startActivity(i);
        */
           Log.d("SwA", "replace");
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        WebViewFragment wvf =  new WebViewFragment();
        wvf.init(linkData.getLink());
 
        ft.addToBackStack(linkData.getName());
        ft.replace(R.id.listFragment, wvf);
        ft.commit();
    }
 
}

Мы используем метод addToBackStack FragmentTrasaction и добавляем каждый фрагмент в backstack. Таким образом, когда мы нажимаем кнопку «Назад», мы имеем правильное поведение.

Исходный код скоро появится

Ссылка: транзакция фрагмента Android: FragmentManager и Backstack от нашего партнера по JCG Франческо Аццолы в блоге Surviving с Android .