Статьи

Android SDK: использование фрагментов

Понятие фрагментов было введено в Android 3.0, API Level 11. С тех пор сообщество Android увидело шесть основных выпусков платформы. Тем не менее, более половины активных Android-устройств изначально не имеют этой функции. Вместо этого разработчики должны использовать библиотеку поддержки Android, если они хотят использовать фрагменты с этими устройствами. Узнайте, как создать базовое приложение на основе фрагментов с помощью библиотеки поддержки Android в этом руководстве.

Это руководство предназначено для разработчиков Android, которым необходимо ориентироваться на подавляющее большинство устройств Android, которые в настоящее время находятся в руках пользователей, и хочет использовать API Fragment, который упрощает поддержку различных размеров экрана. Вы должны быть знакомы с Java, Eclipse и знать основы Android.

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

Чтобы в полной мере увидеть эффекты и отличия отзывчивого, хорошо спроектированного фрагмента экрана, вам понадобятся два устройства (или настроенные эмуляторы): одно с обычным экраном размера телефона, а другое с экраном размером с планшет.

Обратите внимание, что этот код основан на передовом коде, созданном мастером New Android Application в Eclipse. Проект можно скачать и просмотреть на хостинге с кодами Google, а также установить бинарный файл .

Примером здесь будет классический пример фрагмента, также известный как поток «master-detail». Мы начинаем со списка «вещей» (предметов, они могут быть песнями, фильмами, домашними животными и т. Д.) И, когда нажимается «вещь», мы отображаем детали для этой конкретной «вещи». Если размер экрана устройства может соответствовать как списку, так и деталям, будут использованы два фрагмента. В противном случае используется только один, и пользователь должен щелкнуть, чтобы перейти к деталям.

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

Фрагменты одной панели

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

Фрагменты двойной панели

Фрагменты не снимают необходимость действий. И, на самом деле, вам все равно понадобятся два действия: одно действие будет обрабатывать случай, когда отображаются оба фрагмента (режим двух панелей), и другое, чтобы обрабатывать подробный вид, когда на первом экране отображался только список, поскольку экран устройства размер был маленький. Благодаря использованию надлежащим образом организованных ресурсов, первое действие будет динамически корректироваться в зависимости от ширины экрана. Внутренне это действие должно знать, находится ли оно в двухпанельном режиме или нет, поэтому оно может правильно разместить фрагмент детали или порождать действие детали, если это необходимо, который будет содержать только фрагмент детали.

Для упрощения: Вам нужно реализовать два класса Activity и два класса Fragment для обработки этих случаев. Классы Activity довольно просты, поскольку они просто отображают правильный фрагмент или фрагменты и управляют потоком приложения. Для нашего примера здесь мы используем следующие классы Activity: основной класс, CircleListActivity, иногда используемый класс сведений, CircleDetailActivity. У нас также есть два класса Fragment, которые фактически выполняют макет и рисование: CircleListFragment и CircleDetailFragment. Да, мы говорим о кругах. Круги — это просто вещи. Не нужно беспокоиться о «вещи», которую мы выбрали.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
 
public class CircleListActivity extends FragmentActivity {
    private boolean mTwoPane;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_circle_list);
 
        if (findViewById(R.id.circle_detail_container) != null) {
 
            mTwoPane = true;
 
            ((CircleListFragment) getSupportFragmentManager().findFragmentById(
                    R.id.circle_list)).setActivateOnItemClick(true);
        }
 
    }
 
}

Обратите внимание, что мы используем FragmentManager поддержки Android, а не тот, который представлен в Android 3.0.

Тем не менее, класс Activity делает очень мало. Вся основная логика происходит автоматически, потому что операционная система Android будет загружать правильную версию ресурса макета для устройства при вызове метода setContentView (). Это означает, что вам просто нужно сосредоточить свое внимание на рассматриваемых ресурсах. Как ресурс правильно выбирает использование одного фрагмента или двух?

Pop Quiz: Сколько макетов нам нужно, чтобы отобразить оба фрагмента?

Ответ: 3, но это не включает макет каждого фрагмента.

Нам нужен один макет, который будет отображать как фрагмент списка, так и фрагмент детализации. Нам нужен другой макет, который просто покажет фрагмент списка. Наконец, нам нужен третий макет, который покажет только фрагмент детали.

Макеты могут использовать тег фрагмента, чтобы указать конкретный фрагмент для загрузки. Однако это работает только для фиксированных фрагментов. Для динамических фрагментов, таких как фрагмент детали, который будет отображать различную информацию в зависимости от того, что было выбрано, мы используем представление контейнера, чтобы указать, куда фрагмент должен идти в макете. Наиболее распространенным контейнером для использования является FrameLayout, так как он содержит только один дочерний элемент и может быть затем позиционирован как блок в любом месте содержащего макета.

Для начала мы создадим два отдельных макета фрагментов.

Во-первых, макет, который будет отображать только фрагмент списка:

1
2
3
4
5
6
7
8
<fragment xmlns:android=»http://schemas.android.com/apk/res/android»
    android:id=»@+id/circle_list»
    android:name=»com.mamlambo.circlesfragmentexample.CircleListFragment»
    android:layout_width=»match_parent»
    android:layout_height=»match_parent»
    android:layout_marginLeft=»16dp»
    android:layout_marginRight=»16dp»
/>

Теперь макет, который отображает только фрагмент детали:

1
2
3
4
5
<FrameLayout xmlns:android=»http://schemas.android.com/apk/res/android»
    android:id=»@+id/circle_detail_container»
    android:layout_width=»match_parent»
    android:layout_height=»match_parent»
 />

Если они выглядят довольно просто, это потому, что они есть. Повторим еще раз: это макеты, которые определяют, где будут находиться фрагменты, а не то, как они выглядят.

Наконец, давайте объединим их в макет, который будет отображать оба фрагмента:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<LinearLayout xmlns:android=»http://schemas.android.com/apk/res/android»
 
    android:layout_width=»match_parent»
    android:layout_height=»match_parent»
    android:layout_marginLeft=»16dp»
    android:layout_marginRight=»16dp»
    android:orientation=»horizontal»
    android:showDividers=»middle»
    >
 
    <fragment
        android:id=»@+id/circle_list»
        android:name=»com.mamlambo.circlesfragmentexample.CircleListFragment»
        android:layout_width=»0dp»
        android:layout_height=»match_parent»
        android:layout_weight=»1″/>
 
    <FrameLayout
        android:id=»@+id/circle_detail_container»
        android:layout_width=»0dp»
        android:layout_height=»match_parent»
        android:layout_weight=»3″ />
 
</LinearLayout>

Теперь, когда вы знаете, как выглядят макеты, вам все еще может быть интересно, как выбрать правильный программный вариант.

Вы знаете, что операционная система Android будет выбирать соответствующие ресурсы на основе текущих характеристик устройства. Итак, на первый взгляд, вы можете начать размещать макеты в таких папках, как layout-land, layout-large, layout-small и так далее. Вы называете макет одного фрагмента списка так же, как макет с двумя фрагментами, и помещаете их в различные папки. Это может даже работать, но этот метод будет трудно поддерживать, с несколькими различными файлами для нескольких идентичных макетов.

Вместо этого вы можете использовать ссылки для ссылки на определенный элемент в Android из определенной папки ресурсов. Лучше всего увидеть пример этого. Мы назовем наши три макета на основе действий (в отличие от отдельных фрагментов, содержащихся в них): activity_circle_detail, activity_circle_list и activity_circle_twopane. Затем мы поместим все три в стандартную папку макета. Вы уже видели в коде, начиная с шага 2, что мы загружаем только макет activity_circle_list. Или мы?

Теперь давайте добавим несколько ссылок. Хороший способ определить, подходит ли экран для двух соседних панелей, — по ширине в единицах dp. Допустим, наша двухпанельная компоновка требует ширины экрана не менее 850 dp. Давайте создадим папку с именем values-w850dp. Это должно охватывать 7-дюймовые планшеты высокой плотности и 10-дюймовые планшеты средней плотности в ландшафтном режиме, но оно не включает телефоны xhdpi и, как правило, исключает портретный режим на большинстве устройств, за исключением некоторых с экранами с очень высоким разрешением. В этой папке создайте файл с именем refs.xml и поместите в него следующее:

1
2
3
<resources>
    <item name=»activity_circle_list» type=»layout»>@layout/activity_circle_twopane</item>
</resources>

Как это работает? Когда ширина составляет 850 dp или больше, эта ссылка будет использоваться для загрузки макета. Это означает, что двухпанельный макет будет использоваться вместо отдельных панелей, отображаемых отдельно. Поскольку этот квалификатор ресурса (соглашение об именовании папок) не был представлен до уровня API 13, мы также хотели бы включить эту же ссылку в другой подходящий квалификатор папки, который существует намного дольше, например values-xlarge-land. Экстремальный размер определяется как минимум 960dp на 720dp, поэтому мы также должны убедиться, что он будет отображаться только в ландшафтном режиме, включая квалификатор земли.

С организованными макетами пришло время вернуться к Java-коду. Хотя правильный макет будет отображаться на экране при первом запуске операции со списком, что произойдет, если коснуться одного из элементов списка?

Фрагмент списка обрабатывает наш массив вещей (в данном случае кружки). Он не отличается от ListActivity тем, что он обеспечивает некоторую обработку рутинных задач списка, чтобы упростить собственное кодирование. Список должен содержать все круги из источника данных (в данном случае фиктивного источника данных), а также обрабатывать нажатия на определенные элементы, чтобы показать детали. Однако, поскольку фрагмент не является действием, он должен пропускать события, которые потенциально могут изменить экран на управляющий класс Activity. Отвод, который происходит в режиме двойной панели, загрузит фрагмент в другой панели на том же экране, тогда как отвод в режиме одной панели должен загрузить новую операцию с фрагментом детализации. Однако фрагмент не знает и не должен знать об этих деталях конфигурации.

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

Вот весь код класса CircleListFragment, включая механизм безопасного обратного вызова и обработку ответвления, которая вызывает метод обратного вызова для передачи обработки рабочего процесса управляющей операции.

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
 
import com.mamlambo.circlesfragmentexample.dummy.DummyContent;
 
public class CircleListFragment extends ListFragment {
 
    private static final String STATE_ACTIVATED_POSITION = «activated_position»;
    private Callbacks mCallbacks = sDummyCallbacks;
    private int mActivatedPosition = ListView.INVALID_POSITION;
    public interface Callbacks {
        public void onItemSelected(String id);
    }
 
    private static Callbacks sDummyCallbacks = new Callbacks() {
        @Override
        public void onItemSelected(String id) {
        }
    };
 
    public CircleListFragment() {
    }
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        setListAdapter(new ArrayAdapter<DummyContent.DummyItem>(getActivity(),
                android.R.layout.simple_list_item_activated_1,
                android.R.id.text1, DummyContent.ITEMS));
    }
 
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
 
        if (savedInstanceState != null
                &amp;&amp;
            setActivatedPosition(savedInstanceState
                    .getInt(STATE_ACTIVATED_POSITION));
        }
    }
 
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
 
        if (!(activity instanceof Callbacks)) {
            throw new IllegalStateException(
                    «Activity must implement fragment’s callbacks.»);
        }
 
        mCallbacks = (Callbacks) activity;
    }
 
    @Override
    public void onDetach() {
        super.onDetach();
 
        mCallbacks = sDummyCallbacks;
    }
 
    @Override
    public void onListItemClick(ListView listView, View view, int position,
            long id) {
        super.onListItemClick(listView, view, position, id);
 
        mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
    }
 
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (mActivatedPosition != ListView.INVALID_POSITION) {
            outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
        }
    }
 
    public void setActivateOnItemClick(boolean activateOnItemClick) {
        getListView().setChoiceMode(
                activateOnItemClick ?
                        : ListView.CHOICE_MODE_NONE);
    }
 
    private void setActivatedPosition(int position) {
        if (position == ListView.INVALID_POSITION) {
            getListView().setItemChecked(mActivatedPosition, false);
        } else {
            getListView().setItemChecked(position, true);
        }
 
        mActivatedPosition = position;
    }
}

Помимо ранее описанных функциональных возможностей, существует некоторое управление ListView, так что элемент списка, к которому подключен, отображается как выбранный в режиме двойной панели и некотором базовом управлении жизненным циклом фрагмента. Как и прежде, мы используем версию фрагмента поддержки, чтобы обеспечить обратную совместимость со старыми устройствами. Обязательно импортируйте правильный пакет в библиотеку поддержки (import android.support.v4.app.ListFragment).

Теперь нам нужно вернуться к управляющему классу Activity, чтобы он обрабатывал обратный вызов.

Добавьте поддержку обратного вызова в основной класс Activity, CircleListActivity, реализуя ранее созданный интерфейс CircleListFragment.Callbacks. Затем реализуйте метод onItemSelected () для ветвления поведения приложения на основе того, может ли устройство обрабатывать режим двух панелей или нет. Вот полностью обновленный класс:

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
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
 
public class CircleListActivity extends FragmentActivity implements
        CircleListFragment.Callbacks {
 
    private boolean mTwoPane;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_circle_list);
 
        if (findViewById(R.id.circle_detail_container) != null) {
            mTwoPane = true;
 
            ((CircleListFragment) getSupportFragmentManager().findFragmentById(
                    R.id.circle_list)).setActivateOnItemClick(true);
        }
 
        // TODO: If exposing deep links into your app, handle intents here.
    }
 
    @Override
    public void onItemSelected(String id) {
        if (mTwoPane) {
            Bundle arguments = new Bundle();
            arguments.putString(CircleDetailFragment.ARG_ITEM_ID, id);
            CircleDetailFragment fragment = new CircleDetailFragment();
            fragment.setArguments(arguments);
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.circle_detail_container, fragment).commit();
 
        } else {
            Intent detailIntent = new Intent(this, CircleDetailActivity.class);
            detailIntent.putExtra(CircleDetailFragment.ARG_ITEM_ID, id);
            startActivity(detailIntent);
        }
    }
}

Используя флаг, чтобы определить, отображается ли двухпанельное представление, метод onItemSelected () либо запускает новое действие, либо заменяет любой текущий фрагмент, найденный в R.id.circle_detail_container, новым экземпляром класса CircleDetailFragment. Для такого дизайна требуется немного больше. Идентификатор круга, по которому пользователь нажал, передается либо новому экземпляру Fragment с помощью аргументов, либо новому действию, где он также превратит идентификатор в аргументы.

На данный момент нам нужно реализовать класс CircleDetailActivity для случая с одной панелью и реализовать класс CircleDetailFragment.

Вспомните простоту макета для этого класса Activity. Макет является просто заполнителем для класса фрагмента детали. Все, что нужно сделать для этого действия, — это передать идентификатор, превратить его в аргумент Fragment, а затем добавить Fragment в контейнер.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.mamlambo.circlesfragmentexample;
 
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
public class CircleDetailActivity extends FragmentActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_circle_detail);
 
                if (savedInstanceState == null) {
            Bundle arguments = new Bundle();
            arguments.putString(CircleDetailFragment.ARG_ITEM_ID, getIntent()
                    .getStringExtra(CircleDetailFragment.ARG_ITEM_ID));
            CircleDetailFragment fragment = new CircleDetailFragment();
            fragment.setArguments(arguments);
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.circle_detail_container, fragment).commit();
        }
    }
}

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

Класс Fragment детализации должен видеть, какие данные круга должны быть загружены и отображены. Затем он должен загрузить и отобразить эти данные. Класс загрузит макет. Дизайн макета может быть общим, но следует учитывать тот факт, что в качестве фрагмента он может быть или не быть полноэкранным. Другими словами, как обычно, не делайте предположений о размере экрана устройства. Конечно, как обычно, вы можете создавать несколько макетов на основе квалификаторов ресурсов. Имейте в виду, что они будут загружаться на основе общих характеристик экрана устройства, а не на основе характеристик области отображения (режимы панели).

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

Без лишних слов, вот весь код для класса CircleDetailFragment:

1
import android.os.Bundle;

Хотя весь код в основном совпадает с фрагментом, не относящимся к поддержке, обратите внимание на правильный импорт для android.support.v4.app.Fragment, чтобы убедиться, что используется правильный класс поддержки Android.

Теперь нам нужно добавить фрагменты в файл манифеста, верно? Вообще-то, нет. В отличие от классов Activity, фрагменты не регистрируются в файле манифеста. Итак, просто добавьте классы активности в файл манифеста, и все готово.

Вот пример файла манифеста для вашего удобства просмотра:

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
<?xml version=»1.0″ encoding=»utf-8″?>
<manifest xmlns:android=»http://schemas.android.com/apk/res/android»
    package=»com.mamlambo.circlesfragmentexample»
    android:versionCode=»1″
    android:versionName=»1.0″ >
 
    <uses-sdk
        android:minSdkVersion=»8″
        android:targetSdkVersion=»16″ />
 
    <application
        android:allowBackup=»true»
        android:icon=»@drawable/ic_launcher»
        android:label=»@string/app_name»
        android:theme=»@style/AppTheme» >
        <activity
            android:name=»com.mamlambo.circlesfragmentexample.CircleListActivity»
            android:label=»@string/app_name» >
            <intent-filter>
                <action android:name=»android.intent.action.MAIN» />
 
                <category android:name=»android.intent.category.LAUNCHER» />
            </intent-filter>
        </activity>
        <activity
            android:name=»com.mamlambo.circlesfragmentexample.CircleDetailActivity»
            android:label=»@string/title_circle_detail»
            android:parentActivityName=».CircleListActivity» >
            <meta-data
                android:name=»android.support.PARENT_ACTIVITY»
                android:value=».CircleListActivity» />
        </activity>
    </application>
 
</manifest>

Обратите внимание на широкий спектр версий Android SDK, которые могут поддерживаться с помощью этого подхода с использованием функциональности фрагмента библиотеки поддержки.

Хотя мы не предоставили приложению полный исходный код, вставленный в этом руководстве, а также ресурсы, вы можете заполнить эти детали самостоятельно. Или вы можете скачать код (см. Шаг 0), скомпилировать и запустить его. Или вы можете скачать скомпилированный двоичный файл и установить его.

Как бы то ни было, при работе в конфигурации устройства с шириной 850 dp или очень большим экраном (не менее 960×640 dp) вы увидите такой дизайн экрана (экран 1920×1200 пикселей при 10 «):

А когда ширина экрана составляет менее 850 точек на дюйм, макет возвращается в однопанельный режим, например, этот дисплей телефона шириной 1280 пикселей в горизонтальном режиме:

Или этот портретный телефон шириной 768 пикселей:

Или этот портретный дисплей шириной 1200 пикселей на планшете:

Из-за использования абстрактных измерений, таких как использование единиц измерения dp, вы можете видеть две разные компоновки на экране с одинаковым разрешением пикселей, но с разной плотностью пикселей. Это помогает сохранить плотность информации (больше пикселей не всегда означает, что ваши пользователи хотят больше данных) от слишком высокого уровня на физически маленьких экранах и является одной из замечательных особенностей системы ресурсов Android.

Вы изучили основы использования API-интерфейса Android Fragment, доступного в библиотеке поддержки Android. Несмотря на то, что фрагменты есть намного, гораздо больше, это руководство дает вам отправную точку для очень распространенного варианта использования, который может значительно улучшить внешний вид многих приложений или частей приложений, независимо от того, с какой версией Android они работают. или экраны, на которых они отображаются.

Разработчики мобильных приложений Лорен Дарси и Шейн Кондер являются соавторами нескольких книг по разработке Android: углубленная книга по программированию под названием « Разработка беспроводных приложений для Android» (в третьем выпуске в виде двухтомника), « Самс научи себя разработке приложений для Android за 24 часа» , и Изучение программирования приложений для Android для Kindle Fire: практическое руководство по созданию вашего первого приложения для Android . Когда они не пишут, они тратят свое время на разработку мобильного программного обеспечения в своей компании и оказание консультационных услуг. С ними можно связаться по электронной почте [email protected] , через их блог на androidbook.blogspot.com и в Twitter @androidwireless .

Купить разработку беспроводных приложений для Android, 3-е издание, том 1 Купить Sam's Teach Yourself для Android разработки приложений в течение 24 часов, 2-е издание Код Мамламбо в Код-Каньоне