Глава 36. Карты Используя этот метод в RunManager , можно создать удобный фасад (façade) для RunMapFragment Листинг 36.8. Запрос позиций серии, часть II (RunManager.java) public LocationCursor queryLocationsForRun(long runId) { return mHelper.queryLocationsForRun(runId); } RunMapFragment может использовать этот новый метод для загрузки позиций. Есте- ственно, запрос к базе данных следует вынести из главного потока при помощи Loader . Создайте новый класс LocationListCursorLoader для решения этой задачи. Листинг 36.9. Загрузчик позиций (LocationListCursorLoader.java) public class LocationListCursorLoader extends SQLiteCursorLoader { private long mRunId; public LocationListCursorLoader(Context c, long runId) { super(c); mRunId = runId; } @Override protected Cursor loadCursor() { return RunManager.get(getContext()).queryLocationsForRun(mRunId); } } Используйте новый загрузчик в RunMapFragment для загрузки позиций. Листинг 36.10. Загрузка позиций в RunMapFragment (RunMapFragment.java) public class RunMapFragment extends SupportMapFragment implements LoaderCallbacks { private static final String ARG_RUN_ID = "RUN_ID"; private static final int LOAD_LOCATIONS = 0; private GoogleMap mGoogleMap; private LocationCursor mLocationCursor; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Проверка идентификатора серии в аргументе и поиск серии Bundle args = getArguments(); if (args != null) { long runId = args.getLong(ARG_RUN_ID, -1); if (runId != -1) { LoaderManager lm = getLoaderManager(); lm.initLoader(LOAD_LOCATIONS, args, this); } } }
Вывод маршрута 587 ... @Override public Loader onCreateLoader(int id, Bundle args) { long runId = args.getLong(ARG_RUN_ID, -1); return new LocationListCursorLoader(getActivity(), runId); } @Override public void onLoadFinished(Loader loader, Cursor cursor) { mLocationCursor = (LocationCursor)cursor; } @Override public void onLoaderReset(Loader loader) { // Завершение работы с данными mLocationCursor.close(); mLocationCursor = null; }} Класс RunMapFragment сохраняет объект LocationCursor для списка позиций. Этот список будет использоваться при построении карты с маршрутом. Реализация onLoaderReset(Loader ) закрывает и обнуляет ссылку на курсор, когда тот становится недоступным. В обычных обстоятельствах этот метод будет вызываться при закрытии загрузчика объектом LoaderManager при выходе поль- зователя с фрагмента, содержащего карту. Вдобавок такое решение не закрывает курсор при выполнении поворота, а это именно то, что нам нужно. Мы подошли к самой интересной части этой главы: выводу информации на GoogleMap . Добавьте метод updateUI() с заполнением данных отображаемой серии, и вызовите этот метод в onLoadFinished(…) Листинг 36.11. Нанесение данных серии на карту (RunMapFragment.java) private void updateUI() { if (mGoogleMap == null || mLocationCursor == null) return; // Создание накладки с позициями серии. // Создаем ломаную со всеми точками. PolylineOptions line = new PolylineOptions(); // Также создается объект LatLngBounds для масштабирования по размерам. LatLngBounds.Builder latLngBuilder = new LatLngBounds.Builder(); // Перебор позиций mLocationCursor.moveToFirst(); while (!mLocationCursor.isAfterLast()) { Location loc = mLocationCursor.getLocation(); LatLng latLng = new LatLng(loc.getLatitude(), loc.getLongitude()); line.add(latLng); latLngBuilder.include(latLng); mLocationCursor.moveToNext(); } // Добавление маршрута на карту mGoogleMap.addPolyline(line); // Масштабирование карты по маршруту с дополнительными отступами продолжение
588Глава 36. Карты Листинг 36.11 (продолжение) // В качестве ограничивающего прямоугольника выбирается // размер текущего экрана. Display display = getActivity().getWindowManager().getDefaultDisplay(); // Построение инструкции перемещения для камеры карты. LatLngBounds latLngBounds = latLngBuilder.build(); CameraUpdate movement = CameraUpdateFactory.newLatLngBounds(latLngBounds, display.getWidth(), display.getHeight(), 15); mGoogleMap.moveCamera(movement);} @Override public Loader onCreateLoader(int id, Bundle args) { long runId = args.getLong(ARG_RUN_ID, -1); return new LocationListCursorLoader(getActivity(), runId); } @Override public void onLoadFinished(Loader loader, Cursor cursor) { mLocationCursor = (LocationCursor)cursor; updateUI(); } Новый метод использует Maps API в нескольких местах. Сначала он создает экзем- пляр PolylineOptions , который будет использоваться для построения маршрута, выводимого на экран, и экземпляр LatLngBounds.Builder для создания ограничи- вающего прямоугольника для масштабирования карты. Затем он перебирает данные LocationCursor и для каждого объекта Location создает объект LatLng с его координатами. Объект LatLng включается в PolylineOptions , а последний включается в LatLngBounds перед переходом к следующей строке в курсоре. После перебора всех позиций метод вызывает addPolyline(PolylineOptions) для GoogleMap , добавляя маршрут на карту. На следующем шаге следует масштабировать карту так, чтобы на ней помещалась вся линия. Перемещение по карте относится к ответственности «камеры», а для на- стройки камеры используются команды, упакованные в экземплярах CameraUpdate и передаваемые moveCamera(CameraUpdate) . Создание экземпляра для перемеще- ния камеры по границам только что добавленной линии выполняется методом newLatLngBounds(LatLngBounds, int, int, int) класса CameraUpdateFactory Мы передаем размеры текущего экрана как приближенное значение размера карты в пикселах, с добавлением нескольких пикселов на отступы, чтобы маршрут лучше смо- трелся. Существует упрощенная версия этого метода newLatLngBounds(LatLngBounds, int) , но она выдает исключение IllegalStateException , если метод будет вызван до того, как MapView завершит определение своих размеров в процессе построения макета. Так как мы не можем гарантировать, что это произойдет к моменту вызова updateUI() , приходится использовать приблизительную оценку. Внесите изменения и снова запустите RunTracker — на карте появляется черная линия, обозначающая маршрут. Пожалуй, сейчас стоит поближе познакомиться с классом PolylineOptions и попытаться немного украсить результат.
Добавление маркеров начала и конца маршрута 589Добавление маркеров начала и конца маршрутаТеперь, когда мы подготовили все необходимое, добавить маркеры, обозначающие начальную и конечную позиции маршрута, относительно несложно. Мы также добавим текст, который будет выводиться в информационном окне при прикос- новении к маркеру. Добавьте в метод updateUI() код, выделенный жирным шрифтом. Листинг 36.12. Маркеры начала и конца маршрута с информацией (RunMapFragment.java) private void updateUI() { if (mGoogleMap == null || mLocationCursor == null) return; // Создание накладки с позициями серии. // Создаем ломаную со всеми точками. PolylineOptions line = new PolylineOptions(); // Также создается объект LatLngBounds для масштабирования по размерам. LatLngBounds.Builder latLngBuilder = new LatLngBounds.Builder(); // Перебор позиций mLocationCursor.moveToFirst(); while (!mLocationCursor.isAfterLast()) { Location loc = mLocationCursor.getLocation(); LatLng latLng = new LatLng(loc.getLatitude(), loc.getLongitude()); Resources r = getResources(); // Если это первая позиция, добавить маркер if (mLocationCursor.isFirst()) { String startDate = new Date(loc.getTime()).toString(); MarkerOptions startMarkerOptions = new MarkerOptions() .position(latLng) .title(r.getString(R.string.run_start)) .snippet(r.getString(R.string.run_started_at_format, startDate)); mGoogleMap.addMarker(startMarkerOptions); } else if (mLocationCursor.isLast()) { // Если это последняя позиция, которая не является // также первой, добавить маркер String endDate = new Date(loc.getTime()).toString(); MarkerOptions finishMarkerOptions = new MarkerOptions() .position(latLng) .title(r.getString(R.string.run_finish)) .snippet(r.getString(R.string.run_finished_at_format, endDate)); mGoogleMap.addMarker(finishMarkerOptions); } line.add(latLng); latLngBuilder.include(latLng); mLocationCursor.moveToNext(); } // Добавление маршрута на карту mGoogleMap.addPolyline(line); Для первой и последней точки маршрута код создает экземпляр MarkerOp- tions для хранения позиции, заголовка и дополнительного текста. Заголовок 590 Глава 36. Карты и дополнительный текст выводятся в простом информационном окне, когда поль- зователь прикасается к маркеру. В этом коде используется маркер по умолчанию, но при желании можно создать маркер другого цвета (и даже содержащий заданное изображение) при помощи метода icon(BitmapDescriptor) и BitmapDescriptorFactory . Существует много стандартных цветов, из которых вы можете выбрать нужный. Запустите приложение RunTracker и протестируйте его во время своего следующего путешествия. Счастливого пути! Упражнение. Оперативное обновление В текущей версии RunMapFragment может отображать карту с местоположениями серии, «замороженными» по времени на момент прибытия пользователя. Каче- ственное геопозиционное приложение должно выводить обновления в режиме «живого» обновления. Используя субкласс LocationReceiver в RunMapFragment , организуйте обработку поступления новых позиций с перерисовкой маршрута. Не забудьте удалить предыдущую накладку (overlay) и маркеры, прежде чем до- бавлять новые.
Послесловие Поздравляем! Вы добрались до последней страницы этого учебника. Не каждому хватило бы терпения сделать то, что вы сделали; узнать то, что вы узнали. Ваша самоотверженная работа не пропала даром; теперь вы стали разработчиком Android. Последнее упражнение У нас осталось еще одно, последнее упражнение для вас: станьте хорошим разработ- чиком Android. Каждый хороший разработчик хорош по-своему, поэтому с этого момента вы должны найти собственный путь. С чего начать, спросите вы? Мы можем дать несколько советов. Пишите код . Начинайте прямо сейчас. Вы быстро забудете то, что узнали в книге, если не будете применять полученные знания. Примите участие в про- екте или напишите собственное простое приложение. Что бы вы ни выбрали, не тратьте времени: пишите код. Учитесь . В этой книге вы узнали много полезной информации. Что-то из этого пробудило ваше воображение? Поэкспериментируйте со своими любимыми возможностями. Найдите и почитайте дополнительную документацию по ним — или целую книгу, если она есть. Встречайтесь с людьми . Многие первоклассные разработчики Android посто- янно бывают на канале #android-dev сервера irc.freenode.net . Сайт Android Developer Office Hours (https://plus.google.com/+AndroidDevelopers/posts) поможет вам оставаться на связи с группой разработки Android и другими заинтересованными разработчиками. Локальные встречи также помогут вам найти единомышленников. Присоединяйтесь к сообществу разработки с открытым кодом . Разработка Android процветает на сайте https://www.github.com. Обнаружив полезную би- блиотеку, посмотрите, в каких еще проектах участвуют его авторы. Делитесь своим кодом — никогда не знаешь, кому он пригодится. 37
592Глава 37. Послесловие Бессовестная саморекламаЕсли вам понравилась книга, ознакомьтесь с другими учебниками Big Nerd Ranch по адресу https://www.bignerdranch.com/books. У нас также имеется широкий вы- бор недельных курсов для разработчиков, на которых вы всего за неделю сможете получить массу полезной информации. И конечно, если вам понадобятся услуги опытных разработчиков, мы также занимаемся контрактным программированием. За подробностями обращайтесь на наш сайт по адресу https://www.bignerdranch.com. СпасибоБез таких читателей, как вы, наша работа была бы невозможной. Спасибо вам за то, что купили и прочитали нашу книгу. перейти в каталог файлов
| Образовательный портал
Как узнать результаты егэ
Стихи про летний лагерь
3агадки для детей |