
Этот пост участвует в конкурсе „Умные телефоны за умные посты“.
Попробуем решить следующую задачу: показать в приложении карту с пинами, то есть сделать стандартную функциональность, нужную для любого LBS приложения. Причем, сделать это в парадигме MVC – то есть модель данных и контроллер в C++, а QML занимается только отображением и логикой, связанной с UI. Для карты будем использовать использовать стандартный элемент Map, а заодно разберемся со связыванием модели данных из C++ и QML.
Для работы нужен установленный Qt SDK 1.1.4. Создаем новый проект: Qt Quick Application -> Qt Quick Components for Meego/Harmattan, далее все по дефолту. Поскольку мы будем использоваться Qt Mobilitiy, надо добавить в pro файл:
CONFIG += mobility
После этого имеем пустой проект с некой обвязкой на C++ и два qml файла – main.qml и MainPage.qml. Main мы трогать не будем, в нем лежит стандартные для платформы верхний статус бар и нижнее меню. В MainPage нужно импортировать нужное:
import QtMobility.location 1.2
и удалить все внутри элемента Page, заменив это на Map:
Map { id: map anchors.fill:parent plugin: Plugin {name:"nokia"} zoomLevel: 13 center: Coordinate { latitude: 55.755786; longitude: 37.617633 } }
Обращу внимание на определение Plugin – так надо писать, чтобы получить в карте тайлы от Nokia (Ovi) Maps. Есть возможность подключить другие плагины, но из коробки их нет. Далее мы определяем уровень приближения и координаты центра карты. Запускаем, и видим Москву.
Все хорошо, но нам нужен хотя бы скролл пальцем. Нативный контрол это делать не умеет, поэтому делаем сами. Поверх карты добавляем MouseArea которая будет отлавливать одиночные тачи и двигать карту:
MouseArea { anchors.fill:parent property int lastX : -1 property int lastY : -1 onPressed : { lastX = mouse.x; lastY = mouse.y; } onReleased : { lastX = -1; lastY = -1; } onPositionChanged: { if (lastX>=0) { map.pan(lastX- mouse.x, lastY - mouse.y) lastX = mouse.x lastY = mouse.y } } }
С зумом двумя пальцами можно разобраться аналогичным способом, используя элемент PinchArea.
Теперь надо нарисовать пины. Контрол карты поддерживает отрисовку в себе различных элементов, как графически примитивов, так и битмапов, но динамически это можно делать через методы addMapObject и removeMapObject, что не очень согласуется с нашим желанием делать MVC. Еще один вариант, использовать MapObjectView, но, как говорит документация «For model data, currently only LandmarkModel is supported. Using other types of models results in undefined behavior.» А LandmarkModel – это темный лес с кучей ограничений и ненужных нам действий. Нам надо что-то попроще. Так что мы пойдем своим путем, но сначала разберёмся с моделью в С++.
Модель будет проста: внутри будет список пинов, у каждого lat, lng и imageSource для url картинки для пина. Но во view мы будем отдавать уже не lat и lng, а абсолютные значения X и Y пина на экране. Также реализуем метод AddPin, которым будем добавлять пины в список, и DrawPins который будет вызываться из view при каких-то изменениях и пересчитывать значения X и Y. DrawPins должен быть объявлен как public slot, чтобы уметь принимать сигналы из view.
class PinList : public QAbstractListModel //… void PinList::addPin(QString imageSource, double lat, double lng) { //создаем внутренний пин с реальными координатами Pin *pin = new Pin(this); pin->imageSource = imageSource; pin->Lat = lat; pin->Lng = lng; beginInsertRows(QModelIndex(), this->Pins->length(), this->Pins->length()); this->Pins->append(pin); endInsertRows(); //говорим view, что данные изменились emit dataChanged(createIndex(0,0),createIndex(this->Pins->size(),0)); } //view передает нам текущие координаты углов в lat, lng и в x,y (считаем тут что один угол у нас в 0,0) void PinList::DrawPins(QString x,QString y,QString x_end,QString y_end, QString map_x, QString map_y) { //запоминаем переданные координаты map_first_lat = x.toDouble(); map_first_lng = y.toDouble(); map_second_lat = x_end.toDouble(); map_second_lng = y_end.toDouble(); map_x_end = map_x.toDouble(); map_y_end = map_y.toDouble(); //говорим view, что данные изменились emit dataChanged(createIndex(0,0),createIndex(this->Pins->size(),0)); } //вызывается из view для запроса полей модели //тут как раз пересчитываем из lat,lng в x,y QVariant PinList::data(const QModelIndex & index, int role) const { if (index.row() < 0 || index.row() > this->Pins->count()) return QVariant(); const Pin* pin = this->Pins->at(index.row()); QVariant result; switch (role) { case ImageSource: result = QVariant(pin->imageSource); break; case X: if ( pin->Lng > map_first_lng && pin->Lng < map_second_lng) { result =QVariant((map_x_end)*(pin->Lng - map_first_lng)/(map_second_lng - map_first_lng)); } break; case Y: if ( pin->Lat > map_second_lat && pin->Lat < map_first_lat) { result = QVariant(map_y_end*(pin->Lat - map_first_lat)/(map_second_lat - map_first_lat)); } break; } return result; }
Теперь надо нарисовать все это во view. Для этого определим, как выглядит пин:
//пин Component { id: landmarkMapDelegate Item { id:land width: 20; height: 20 ; x: X y: Y Image { source:ImageSource } } }
и нарисуем Repeater поверх карты
Item { anchors.fill:parent Repeater { model: pinlist delegate: landmarkMapDelegate } }
Вот тут важно: model привязано к идентификатору pinlist. Именно этот идентификатор нужно будет потом указать для привязки модели в C++.
Объявим функцию DrawPins, которая будет считать lat и lng углов карты, и передавать ее в модель
function drawPins() { var topLeft = map.toCoordinate(Qt.point(0,0)) var bottomRight = map.toCoordinate(Qt.point(map.width, map.height)) pinlist.DrawPins(topLeft.latitude,topLeft.longitude,bottomRight.latitude,bottomRight.longitude, map.width,map.height); }
И вызовем ее в OnPositionChanges карты.
Теперь осталось, собственно, связать нашу модель и View:
//создаем модель PinList* pinList = new PinList(0); //добавляем несколько пинов pinList->addPin("qrc:/icons/pin.png", 55.745, 37.6175); pinList->addPin("qrc:/icons/pin.png", 55.7575, 37.619697); pinList->addPin("qrc:/icons/pin.png", 55.751667, 37.617778); //связываем модель и вью viewer->rootContext()->setContextProperty("pinlist", pinList); //читаем qml viewer->setMainQmlFile(QLatin1String("qml/untitled/main.qml"));
Все.
Исходники
