С этим файлом связано 55 файл(ов). Среди них: Khant_K_TCP_IP_Setevoe_administrirovanie_3-e_izdanie_1988.pdf, vk_gettoken, Adams_Rob_-_Khoroshiy_uvesisty_pinok_pod_zad.pdf, vk_gettoken и ещё 45 файл(а). Показать все связанные файлы Глава 16. Панель действий Запуск CriminalListActivity Рис. 16.10. FLAG_ACTIVITY_CLEAR_TOP в действии Однако существует другой, более правильный способ реализации иерархической навигации, основанный на использовании вспомогательного класса NavUtils и включении метаданных в манифест. Начнем с метаданных. Откройте файл AndroidManifest.xml . Добавьте в объявление CrimePagerActivity следующий атрибут, назначающий CrimeListActivity его родителем. Листинг 16.11. Добавление метаданных родительской активности (AndroidManifest.xml) android:label="@string/app_name"> android:value=".CrimeListActivity"/>
Тег метаданных — своего рода «листок для заметок», приклеенный к активности. Такие заметки хранятся в системном объекте PackageManager , и любой желающий может получить значение из «заметки», если он знает ее имя. Вы можете создавать собственные пары «имя-значение» и читать их данные по мере необходимости. Эту конкретную пару определяет класс NavUtils , чтобы он мог узнать родителя заданной активности. Она особенно полезна в сочетании со следующим методом класса NavUtils : public static void navigateUpFromSameTask(Activity sourceActivity) В методе CrimeFragment.onOptionsItemSelected(…) сначала проверьте, существует ли родительская активность, обозначенная в метаданных, при помощи вызова Na- vUtils.getParentActivityName(Activity) . Если она существует, вызовите navigat eUpFromSameTask(Activity) для перехода к родительской активности. Листинг 16.12. Использование NavUtils (CrimeFragment.java) @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: if (NavUtils.getParentActivityName(getActivity()) != null) { Альтернативная команда меню 279 NavUtils.navigateUpFromSameTask(getActivity()); } return true; default: return super.onOptionsItemSelected(item); } } Если информация о родителе в метаданных отсутствует, отображать стрелку не нужно. Вернитесь к onCreateView(…) и проверьте наличие родителя перед вызовом setDisplayHomeAsUpEnabled(true) Листинг 16.13. Нет родителя — нет стрелки (CrimeFragment.java) @TargetApi(11)@Override public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_crime, parent, false); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (NavUtils.getParentActivityName(getActivity()) != null) { getActivity().getActionBar().setDisplayHomeAsUpEnabled(true); } } } Почему использование NavUtils лучше самостоятельного запуска активности? Во- первых, код NavUtils компактен и понятен. Кроме того, использование NavUtils обеспечивает централизацию отношений между активностями в манифесте. Если эти отношения изменятся, достаточно изменить строку в манифесте вместо того, чтобы возиться с кодом Java. Другое преимущество заключается в том, что иерархические отношения отделяются от кода фрагмента. CrimeFragment может использоваться в разных активностях, которые могут иметь разных родителей; CrimeFragment все равно будет работать правильно. Запустите приложение CriminalIntent. Создайте преступление и нажмите на значке приложения, чтобы вернуться к списку преступлений. Может, из жалкой двух- уровневой иерархии CriminalIntent это и не очевидно, но метод navigateUpFromS ameTask(Activity) реализует функциональность «перехода наверх» и поднимает пользователя на один уровень к родителю CrimePagerActivity Альтернативная команда менюВ этом разделе все, что мы узнали о меню, совместимости и альтернативных ресур- сах, будет использовано для добавления команды меню, скрывающей и отобража- ющей подзаголовок в панели действий CrimeListActivity 280 Глава 16. Панель действий Создание альтернативного файла меню Команда меню, применяемая к панели действий, не должна быть видимой пользо- вателям без панели действий. Следовательно, первым шагом должно быть создание альтернативного ресурса меню. Создайте в каталоге res проекта папку menu-v11 Скопируйте и вставьте файл fragment_crime_list.xml в эту папку. В файле res/menu-v11/fragment_crime_list.xml добавьте команду меню Show Subtitle , которая будет отображаться на панели действий при наличии свободного места. Листинг 16.14. Добавление команды меню Show Subtitle (res/menu-v11/fragment_crime_list.xml)
Добавьте в метод onOptionsItemSelected(…) обработку команды меню — включение подзаголовка на панели действия. Листинг 16.15. Обработка команды меню Show Subtitle (CrimeListFragment.java) @TargetApi(11) @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_item_new_crime: return true; case R.id.menu_item_show_subtitle: getActivity().getActionBar().setSubtitle(R.string.subtitle); return true; default: return super.onOptionsItemSelected(item); } } Обратите внимание: мы только отменяем предупреждения Android Lint, а не заклю- чаем код панели действий в проверочную конструкцию. Проверка не нужна — этот код не может вызываться на старых устройствах, потому что команда меню R.id. menu_item_show_subtitle на них отображаться не будет. Запустите приложение CriminalIntent на новом устройстве и включите подзаго- ловок. Затем запустите его на устройстве Froyo или Gingerbread (физическом или виртуальном). Нажмите кнопку меню и убедитесь в том, что команда Show Subtitle не отображается. Добавьте новое преступление и убедитесь в том, что приложение работает так же, как прежде. «Да, и еще одно…» 281 Переключение текста команды Теперь подзаголовок отображается, но текст команды меню остался неизменным: Show Subtitle . Было бы лучше, если бы текст команды и функциональность команды меню изменялись в зависимости от текущего состояния подзаголовка. В методе onOptionsItemSelected(…) проверьте наличие подзаголовка при выборе команды меню и выполните соответствующие действия. Листинг 16.16. Обработка в зависимости от наличия подзаголовка (CrimeListFragment.java) @TargetApi(11) @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_item_new_crime: return true; case R.id.menu_item_show_subtitle: if (getActivity().getActionBar().getSubtitle() == null) { getActivity().getActionBar().setSubtitle(R.string.subtitle); item.setTitle(R.string.hide_subtitle); } else { getActivity().getActionBar().setSubtitle(null); item.setTitle(R.string.show_subtitle); } return true; default: return super.onOptionsItemSelected(item); } } Если панель действий не содержит подзаголовка, мы включаем подзаголовок и за- меняем текст команды меню на Hide Subtitle . Если подзаголовок уже отображается, то он отключается, а команде меню возвращается текст Show Subtitle Запустите приложение CriminalIntent и убедитесь в том, что подзаголовок успешно скрывается и отображается. «Да, и еще одно…» Программирование Android часто напоминает беседы с детективом Коломбо из сериала. Вы уже думаете, что все прошло как по маслу и у следствия нет претензий. Но Android всегда поворачивается у двери и говорит: «Да, и еще одно…» В нашем случае это повороты. Если отобразить подзаголовок, а потом повернуть устройство, подзаголовок исчезнет при создании интерфейса «с нуля». Для реше- ния этой проблемы нужно определить поле для признака видимости подзаголовка, а также сохранить CrimeListFragment , чтобы значение переменной не терялось при поворотах. В файле CrimeListFragment.java добавьте логическую переменную, затем в методе on- Create(…) сохраните CrimeListFragment и инициализируйте переменную.
282 Глава 16. Панель действий Листинг 16.17. Инициализация переменных и сохранение CrimeListFragment (CrimeListFragment.java) public class CrimeListFragment extends ListFragment { private ArrayList mCrimes; private boolean mSubtitleVisible; private final String TAG = "CrimeListFragment"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); mSubtitleVisible = false; } Затем в методе onOptionsItemSelected(…) задайте эту переменную при обработке выбора команды меню. Листинг 16.18. Присваивание переменной subtitleVisible при обработке команды меню (CrimeListFragment.java) @TargetApi(11) @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_item_new_crime: return true; case R.id.menu_item_show_subtitle: if (getActivity().getActionBar().getSubtitle() == null) { getActivity().getActionBar().setSubtitle(R.string.subtitle); mSubtitleVisible = true; item.setTitle(R.string.hide_subtitle); } else { getActivity().getActionBar().setSubtitle(null); mSubtitleVisible = false; item.setTitle(R.string.show_subtitle); } return true; default: return super.onOptionsItemSelected(item); } } Осталось проверить, может ли отображаться подзаголовок. В файле CrimeListFrag- ment.java переопределите метод onCreateView(…) и назначьте подзаголовок, если переменная mSubtitleVisible содержит true Листинг 16.19. Подзаголовок назначается, если поле mSubtitleVisible истинно (CrimeListFragment.java) @TargetApi(11) @Override public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Упражнение. Пустое представление для списка 283 Bundle savedInstanceState) { View v = super.onCreateView(inflater, parent, savedInstanceState); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (mSubtitleVisible) { getActivity().getActionBar().setSubtitle(R.string.subtitle); } } return v; } Также необходимо проверить состояние подзаголовка в onCreateOptionsMenu(…) и убедиться в том, что отображается правильный текст команды меню. Листинг 16.20. Назначение текста команды меню с истинным значением mSubtitleVisible (CrimeListFragment.java) @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.fragment_crime_list, menu); MenuItem showSubtitle = menu.findItem(R.id.menu_item_show_subtitle); if (mSubtitleVisible && showSubtitle != null) { showSubtitle.setTitle(R.string.hide_subtitle); } } Запустите приложение CriminalIntent. Отобразите подзаголовок, поверните устройство. Подзаголовок должен появиться в воссозданном представлении, как и ожидалось. Упражнение. Пустое представление для списка В настоящее время при запуске CriminalIntent отображает пустой список — большую черную пустоту. Мы должны предоставить пользователям что-то для взаимодей- ствия при отсутствии элементов в списке. Так как класс ListView является субклассом AdapterView , он поддерживает специ- альный вариант View , называемый «пустым представлением» и существующий именно для таких ситуаций. Если задать пустое представление, ListView будет ав- томатически переключаться между этим представлением при отсутствии элементов списка и отображением списка, если в нем есть хотя бы один элемент. Для изменения пустого представления в коде используется следующий метод AdapterView : public void setEmptyView(View emptyView) Также можно создать в разметке XML макет, в котором задается как ListView , так и пустое представление. Если назначить им идентификаторы ресурсов @android:id/ list и @android:id/empty соответственно, будет использоваться функция автома- тического переключения.
284 Глава 16. Панель действий Текущая реализация CrimeListFragment не заполняет свой макет в onCreateView(…) , но для реализации пустого представления в макете это необходимо. Создайте для CrimeListFragment XML-ресурс макета, использующий FrameLayout в качестве корневого контейнера, с представлением ListView и другим представлением View , которое будет использоваться для пустого списка. Пусть в пустом представлении выводится сообщение (например, «Список пуст»). Добавьте в представление кнопку, которая будет инициировать создание нового преступления, чтобы пользователю не пришлось обращаться к командному меню или панели действий.
Сохранение и загрузка локальных файловПочти каждому приложению необходимо место для хранения данных. В этой главе мы научим приложение CriminalIntent сохранять и загружать данные из файла JSON, хранящегося в файловой системе устройства. Каждое приложение на устройстве Android имеет каталог в своей песочнице (sand- box). Хранение файлов в песочнице защищает их от других приложений и даже любопытных глаз пользователей (если только устройство не было «взломано» — в этом случае пользователь сможет делать все, что ему заблагорассудится). Песочница каждого приложения представляет собой подкаталог каталога /data/data , имя которого соответствует имени пакета приложения. Для CriminalIntent полный путь к каталогу песочницы имеет вид /data/data/com.bignerdranch.android.criminalintent Знать путь к песочнице полезно, но запоминать его не нужно; если понадобится, вы всегда сможете получить его при помощи вспомогательных методов API. Кроме песочницы, ваше приложение может хранить файлы во внешнем хранилище. Как правило, это SD-карта, которая может быть доступна (или не доступна) на устройство. На SD-карте могут храниться файлы и даже целые приложения, однако при этом приходится учитывать ряд факторов из области безопасности и програм- мирования. Самый важный момент заключается в том, что доступ к файлам на внешнем хранилище не ограничивается вашим приложением — любой желающий может читать, записывать и удалять их. В этой главе основное внимание будет уделяться внутреннему (закрытому) хранилищу, но тот же API при необходимости может использоваться для работы с файлами внешнего хранилища. (Собственно, упражнение в конце этой главы посвящено именно этой теме.) Сохранение и загрузка данных в CriminalIntentПоддержка долгосрочного хранения данных в приложении включает два процесса: сохранение данных в файловой системе и их загрузка при запуске приложения. 17 286Глава 17. Сохранение и загрузка локальных файлов Каждый процесс состоит из двух фаз. При сохранении данные сначала преобразу- ются в формат хранения, после чего результат записывается в файл. При загрузке все происходит наоборот: отформатированные данные сначала читаются из файла, а затем разбираются в формат, с которым работает приложение. Для CriminalIntent форматом хранения будет формат JSON, а операции чтения и записи файлов осуществляются методами ввода-вывода класса Android Context На рис. 17.1 представлена общая схема реализации сохранения и загрузки данных в CriminalIntent. crimes.json (в «песочнице») Загрузка… Сохранение… Массив объектов Crime в CrimeLabРазбор данных JSON и создание данных Crime Чтение из файла (методы ввода-вывода) Форматирование данных Crime в JSON Запись в файл (методы ввода-вывода) Рис. 17.1. Сохранение и загрузка в CriminalIntent Формат JSON (JavaScript Object Notation) стал популярным в последнее время, особенно в области веб-служб. Android включает стандартный пакет org.json , классы которого предоставляют средства для создания и разбора файлов в формате JSON. В документации разработчика Android приведено описание org.json , а более подробную информацию о формате JSON можно получить по адресу https://json.org. (XML — другой способ форматирования данных для записи в файл. Android также предоставляет классы и методы для работы с XML. Примеры их использования при разборе XML приведены в главе 26.) Для работы с файлами, доступными для вашего приложения, удобнее всего ис- пользовать методы ввода-вывода, предоставляемые классом Context (суперклассом всех ключевых компонентов приложений: Application , Activity и Service , а также Сохранение и загрузка данных в CriminalIntent 287 нескольких других). Эти методы возвращают экземпляры стандартных классов Java — таких, как java.io.File или java.io.FileInputStream Сохранение преступлений в файле JSON В приложении CriminalIntent класс CrimeLab будет отвечать за инициирование сохранения и загрузки данных, а механика создания и разбора объектов модели в формате JSON будет делегирована новому классу CriminalIntentJSONSerializer и существующему классу Crime Создание класса CriminalIntentJSONSerializer Обязанности по записи существующего списка ArrayList объектов Crime в формат JSON делегированы классу CriminalIntentJSONSerializer Создайте этот класс в пакете com.bignerdranch.android.criminalintent . Оставьте его суперклассом java.lang.Object Затем добавьте код, приведенный в листинге 17.1. Не забудьте добавить команды import для классов, необходимых для работы кода, командой Eclipse Organize Imports Листинг 17.1. Реализация CriminalIntentJSONSerializer public class CriminalIntentJSONSerializer { private Context mContext; private String mFilename; public CriminalIntentJSONSerializer(Context c, String f) { mContext = c; mFilename = f; } public void saveCrimes(ArrayList crimes) throws JSONException, IOException { // Построение массива в JSON JSONArray array = new JSONArray(); for (Crime c : crimes) array.put(c.toJSON()); // Запись файла на диск Writer writer = null; try { OutputStream out = mContext .openFileOutput(mFilename, Context.MODE_PRIVATE); writer = new OutputStreamWriter(out); writer.write(array.toString()); } finally { if (writer != null) writer.close(); } } } Хотя код сериализации можно включить непосредственно в класс CrimeLab , вы- деление логики сериализации данных в формат JSON в автономный модуль имеет
|