Java tutorial
/* * Copyright 2014 WNCOutdoors.info * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ResultsMapFragment.java * Fragment which displays the results of a user's query on a map. */ package info.wncwaterfalls.app; import android.app.Activity; import android.content.Context; import android.database.Cursor; import android.location.Address; import android.location.Location; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; import com.commonsware.cwac.loaderex.acl.SQLiteCursorLoader; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.GoogleMap.OnInfoWindowClickListener; import com.google.android.gms.maps.MapView; import com.google.android.gms.maps.MapsInitializer; import com.google.android.gms.maps.model.BitmapDescriptorFactory; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.LatLngBounds; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import info.wncwaterfalls.app.R; import info.wncwaterfalls.app.ResultsListFragment.OnWaterfallQueryListener; import info.wncwaterfalls.app.ResultsListFragment.OnWaterfallSelectListener; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; public class ResultsMapFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>, OnInfoWindowClickListener { private static final String TAG = "ResultsListFragment"; private MapView mMapView; private OnWaterfallQueryListener sQueryListener; // Listener for loader callbacks private OnWaterfallSelectListener sSelectListener; // Listener for user waterfall selections private OnLocationQueryListener sLocationQueryListener; // Listener for location searches private boolean mLocationDetermined = false; private boolean mActivityCreated = false; private static AttrDatabase db = null; private SQLiteCursorLoader cursorLoader = null; // Oh this makes me sad private Map<Marker, Long> mMarkersToIds = new HashMap<Marker, Long>(); private boolean mMapReady; private LatLngBounds mResultBounds; public interface OnLocationQueryListener { public void determineLocation(Fragment requestor); public void stopLocationClient(); public short getSearchLocationDistance(); public Address getOriginAddress(); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mActivityCreated = true; initializeLoaderIfReady(); } @Override public void onAttach(Activity activity) { super.onAttach(activity); // Make sure the containing activity implements the search listener interface try { sQueryListener = (OnWaterfallQueryListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnWaterfallQueryListener"); } // And the select listener interface try { sSelectListener = (OnWaterfallSelectListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnWaterfallSelectListener"); } // And the interfaces for obtaining locations from a LocationClient try { sLocationQueryListener = (OnLocationQueryListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnQueryLocationListener"); } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); db = new AttrDatabase(getActivity()); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_information_map, container, false); mMapView = (MapView) view.findViewById(R.id.information_map_view); mMapView.onCreate(savedInstanceState); MapsInitializer.initialize(getActivity()); // TODO: Check returned error code? return view; } // Called when the Activity becomes visible. @Override public void onStart() { mMapReady = false; super.onStart(); // Will invoke onLocationDetermined when complete. } // Route certain fragment lifecycle events to MapView @Override public void onResume() { super.onResume(); if (null != mMapView) { mMapView.onResume(); } GoogleMap googleMap = mMapView.getMap(); googleMap.getUiSettings().setMyLocationButtonEnabled(true); // Initialize to terrain map; user can switch. googleMap.setMapType(GoogleMap.MAP_TYPE_TERRAIN); // Set map to run our callback when info window clicked googleMap.setOnInfoWindowClickListener(this); sLocationQueryListener.determineLocation((Fragment) this); googleMap.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() { @Override public void onMapLoaded() { mMapReady = true; zoomToBounds(); } }); } @Override public void onPause() { super.onPause(); if (null != mMapView) { mMapView.onPause(); } } // Called when the Activity is no longer visible. @Override public void onStop() { sLocationQueryListener.stopLocationClient(); super.onStop(); } @Override public void onDestroy() { super.onDestroy(); if (null != mMapView) { mMapView.onDestroy(); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (null != mMapView) { mMapView.onSaveInstanceState(outState); } } @Override public void onLowMemory() { super.onLowMemory(); if (null != mMapView) { mMapView.onLowMemory(); } } // Async location determination callback method public void onLocationDetermined() { // Get our loader manager, and initialize the // query based on the containing Activity's searchMode // TODO: Hide the overlay. mLocationDetermined = true; initializeLoaderIfReady(); } // If we have a location and a fully-realized activity, initialize the loader. // Prevents race between onLocationDetermined & onActivityCreated. private void initializeLoaderIfReady() { if (mLocationDetermined && mActivityCreated) { getLoaderManager().initLoader(0, null, this); } } // LoaderManager.LoaderCallbacks<Cursor> methods @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { Bundle qBundle = sQueryListener.onWaterfallQuery(); // Get the query from our parent activity and pass it to the loader, which will execute it cursorLoader = new SQLiteCursorLoader(getActivity(), db, qBundle.getString("query"), qBundle.getStringArray("args")); return cursorLoader; } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { int count = cursor.getCount(); if (count == 0) { Context context = getActivity(); CharSequence text = "No results found for your search."; int duration = Toast.LENGTH_LONG; Toast toast = Toast.makeText(context, text, duration); toast.show(); } else { // Put the results on a map! GoogleMap googleMap = mMapView.getMap(); double searchLocationDistanceM = 0; LatLngBounds.Builder boundsBuilder = new LatLngBounds.Builder(); if (cursor.moveToFirst()) { // First, add the "searched for" location, if it was a location search. Location originLocation = new Location(""); Address originAddress = sLocationQueryListener.getOriginAddress(); if (originAddress != null) { // Get searched-for distance and convert to meters. short searchLocationDistance = sLocationQueryListener.getSearchLocationDistance(); searchLocationDistanceM = searchLocationDistance * 1609.34; // Build up a list of Address1, Address2, Address3, if present. ArrayList<String> addressList = new ArrayList<String>(); for (int i = 0; i <= 3; i++) { String line = originAddress.getAddressLine(i); if (line != null && line.length() > 0) { addressList.add(line); } } String addressDesc = TextUtils.join("\n", addressList); if (addressDesc == "") { addressDesc = originAddress.getFeatureName(); } if (addressDesc == "") { addressDesc = "Searched Location"; } // Create the LatLng and the map marker. LatLng originLatLng = new LatLng(originAddress.getLatitude(), originAddress.getLongitude()); googleMap.addMarker(new MarkerOptions().position(originLatLng) .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)) .title(addressDesc)); boundsBuilder.include(originLatLng); // In case only one result :) // Translate into a Location for distance comparison. originLocation.setLatitude(originAddress.getLatitude()); originLocation.setLongitude(originAddress.getLongitude()); } else { // Not a location search; don't add point for searched-for location // and don't check radius from that point. //Log.d(TAG, "Skipped adding origin address to map."); } // Next, add the results waterfalls. // Use do...while since we're already at the first result. do { // Get some data from the db Long waterfallId = cursor.getLong(AttrDatabase.COLUMNS.indexOf("_id")); double lat = cursor.getDouble(AttrDatabase.COLUMNS.indexOf("geo_lat")); double lon = cursor.getDouble(AttrDatabase.COLUMNS.indexOf("geo_lon")); // Make sure this one's actually within our search radius. SQL only checked // the bounding box. Location waterfallLocation = new Location(""); waterfallLocation.setLatitude(lat); waterfallLocation.setLongitude(lon); if (originAddress == null || (originLocation.distanceTo(waterfallLocation) <= searchLocationDistanceM)) { // Not a location search (originAddress is null: show all) or within radius. // Display on map. String name = cursor.getString(AttrDatabase.COLUMNS.indexOf("name")); LatLng waterfallLatLng = new LatLng(lat, lon); Marker waterfallMarker = googleMap.addMarker(new MarkerOptions().position(waterfallLatLng) .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)) .title(name)); // Save the id so we can retrieve it when clicked mMarkersToIds.put(waterfallMarker, waterfallId); boundsBuilder.include(waterfallLatLng); } } while (cursor.moveToNext()); // Zoom and center the map to bounds mResultBounds = boundsBuilder.build(); zoomToBounds(); } } } private void zoomToBounds() { if (mMapReady && mResultBounds != null) { GoogleMap googleMap = mMapView.getMap(); googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(mResultBounds, 15)); } } @Override public void onLoaderReset(Loader<Cursor> loader) { // mAdapter.changeCursor(cursor); } // Marker info window click method @Override public void onInfoWindowClick(Marker marker) { long waterfallId = mMarkersToIds.get(marker); sSelectListener.onWaterfallSelected(waterfallId); } }