Google Maps Navigation Material Design

Asked

Viewed 306 times

0

Someone knows reference material to develop an Android app similar to the new Google Maps navigation and animation format?

The idea is to position the Marker Infowindow in the region below the screen. And as soon as the user touches, or makes a Swipe up, this display the full details of the location.

I couldn’t find anything specific in the Google Developer documentation. Any ideas?

Thanks for the help!

  • In guidelines of Material Design has nothing so specific, but it gives some pretty generic navigation patterns. For the standard of "Sliding Panel", Similar to Google Maps, I recommend looking at this answer that provides an alternative to implementing it: http://answall.com/a/28086/6436.

  • This was exactly the desired effect, @Wakim, thank you! Just an Obs: in the post mentioned, you provide a gist that is no longer accessible. You could send me the code that implements together with Maps to get an idea of how to do?

  • Ah, I’ll take a look at why it’s not available, anything I take the code and put there again.

  • I really appreciate if you make this code available. It will help me a lot!

  • I really didn’t find the repository with the code. I can mount a repository on github with an example and I’ll let you know later.

1 answer

2


I believe you’re looking for Androidslidinguppanel, is a library providing a Widget with the functionality you are looking for.

Integration with the Google Maps is simple, in my example I made the access to Google Places API for Android. Where I searched some places and created Markers, synchronized with the SlidingUpPanelLayout.

My layout got:

<?xml version="1.0" encoding="utf-8"?>
<com.sothree.slidinguppanel.SlidingUpPanelLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:sothree="http://schemas.android.com/apk/res-auto"
    android:id="@+id/sliding_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="bottom"
    sothree:umanoPanelHeight="72dp"
    sothree:umanoShadowHeight="4dp"
    sothree:umanoAnchorPoint="0.5"
    tools:context=".MapsActivity">

    <fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment" />

    <include layout="@layout/sliding_panel" />
</com.sothree.slidinguppanel.SlidingUpPanelLayout>

My Activity became:

public class MapsActivity extends FragmentActivity implements GoogleApiClient.ConnectionCallbacks,
    GoogleApiClient.OnConnectionFailedListener, ViewPager.OnPageChangeListener,
    PlacesListAdapter.ListCallback, GoogleMap.OnMarkerClickListener {

    private GoogleMap mMap; // Might be null if Google Play services APK is not available.
    private GoogleApiClient mGoogleApiClient;

    private RecyclerView mRecyclerView;
    private ViewPager mPager;

    private PlacesListAdapter mAdapter;
    private PlacesPagerAdater mPagerAdapter;
    private PlacesInfoWindowAdapter mInfoWindowAdapter;

    private List<PlaceLikelihood> mPlaces;
    private List<Marker> mMarkers;

    private GoogleMap.OnMyLocationChangeListener mListener = new GoogleMap.OnMyLocationChangeListener(){
        @Override
        public void onMyLocationChange(Location location) {
            moveToLocationOneShot(location);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_maps);
        setUpMapIfNeeded();

        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addApi(Places.GEO_DATA_API)
                .addApi(Places.PLACE_DETECTION_API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();

        configureViewPager();
        configureRecyclerView();
        queryForNearbyPlaces();
    }

    @Override
    protected void onResume() {
        super.onResume();
        setUpMapIfNeeded();

        mGoogleApiClient.connect();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mGoogleApiClient.disconnect();
    }

    private void setUpMapIfNeeded() {
        // Do a null check to confirm that we have not already instantiated the map.
        if (mMap == null) {
            // Try to obtain the map from the SupportMapFragment.
            mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap();
            // Check if we were successful in obtaining the map.
            if (mMap != null) {
                setUpMap();
            }
        }
    }

    private void configureViewPager() {
        mPager = (ViewPager) findViewById(R.id.view_pager);
        mPager.setAdapter(mPagerAdapter = new PlacesPagerAdater(this));

        mPager.setOnPageChangeListener(this);
    }

    private void configureRecyclerView() {
        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.setAdapter(mAdapter = new PlacesListAdapter(this).setListCallback(this));
        mRecyclerView.setHasFixedSize(true);
    }

    private void queryForNearbyPlaces() {
        PendingResult<PlaceLikelihoodBuffer> result = Places.PlaceDetectionApi.getCurrentPlace(mGoogleApiClient, null);

        result.setResultCallback(new ResultCallback<PlaceLikelihoodBuffer>() {
            @Override
            public void onResult(PlaceLikelihoodBuffer likelyPlaces) {
                mPlaces = new ArrayList<>(likelyPlaces.getCount());
                mMarkers = new ArrayList<>(likelyPlaces.getCount());
                mInfoWindowAdapter = new PlacesInfoWindowAdapter(MapsActivity.this);

                int i = 0;

                for (PlaceLikelihood placeLikelihood : likelyPlaces) {
                    mPlaces.add(placeLikelihood.freeze());
                    mMarkers.add(mMap.addMarker(buildMarkerForPlace(placeLikelihood.getPlace(), i++)));
                }

                mAdapter.setData(mPlaces);
                mPagerAdapter.setData(mPlaces);
                mInfoWindowAdapter.setData(mPlaces);

                likelyPlaces.release();

                mMap.setInfoWindowAdapter(mInfoWindowAdapter);
            }
        });
    }

    MarkerOptions buildMarkerForPlace(Place place, int position) {
        MarkerOptions mo = new MarkerOptions();

        mo.position(place.getLatLng());
        mo.title(place.getName().toString());
        mo.snippet(Integer.toString(position));

        return mo;
    }

    private void setUpMap() {
        mMap.setMyLocationEnabled(true);
        mMap.setBuildingsEnabled(true);
        mMap.setOnMyLocationChangeListener(mListener);
        mMap.setInfoWindowAdapter(mInfoWindowAdapter);
        mMap.setOnMarkerClickListener(this);
    }

    private void moveToLocationOneShot(Location location) {
        moveToLocation(location);
        mMap.setOnMyLocationChangeListener(null);
    }

    private void moveToLocation(Location location) {
        moveToLocation(getLatLng(location));
    }

    private void moveToLocation(LatLng latLng) {
        mMap.animateCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition(latLng, 16f, 0f, 0f)));
    }

    private LatLng getLatLng(Location loc) {
        return new LatLng(loc.getLatitude(), loc.getLongitude());
    }

    @Override
    public void onConnected(Bundle bundle) {
        Log.d("TAG", "onConnected");
    }

    @Override
    public void onConnectionSuspended(int i) {
        Log.d("TAG", "onConnectionSuspended");
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        Log.d("TAG", "onConnectionFailed");
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}

    @Override
    public void onPageSelected(int position) {
        mRecyclerView.smoothScrollToPosition(position);
        showMarker(mMarkers.get(position));
    }

    void showMarker(Marker marker) {
        moveToLocation(marker.getPosition());
        marker.showInfoWindow();
    }

    @Override
    public void onPageScrollStateChanged(int state) {}

    @Override
    public void onItemSelected(View item) {
        int position = mRecyclerView.getChildLayoutPosition(item);

        mPager.setCurrentItem(position, true);
        showMarker(mMarkers.get(position));
    }

    @Override
    public boolean onMarkerClick(Marker marker) {
        int position = mMarkers.indexOf(marker);

        mPager.setCurrentItem(position, true);
        mRecyclerView.smoothScrollToPosition(position);

        return false;
    }
}

And not forgetting the dependencies:

repositories {
    mavenCentral()
}

compile 'com.android.support:support-v4:22.0.0'
compile 'com.android.support:appcompat-v7:22.0.0'

compile 'com.google.android.gms:play-services-maps:7.0.0'
compile 'com.google.android.gms:play-services-location:7.0.0'
compile 'com.android.support:recyclerview-v7:22.0.0'

compile 'com.sothree.slidinguppanel:library:3.0.0'

Of course some details are still missing, but you have a good code to get you started.

The other artifacts, like Adapters which were used, are in the code repository.

  • This code is an exact complement of what I am developing. Very good, thank you! However, I couldn’t load the locations (and I couldn’t find the list of locations in the code), which causes me to display the map and empty Sliding below...

  • Consultation of sites and completion of Adapters is done in the method queryForNearbyPlaces, more specifically in the method of Callback. If you’re not returning anything it’s because you need to set up your API key in the Google console (Google Places for Android).

  • Yes, the Places API is enabled and with the key generated in the console (browser key). By the way, as I understood, the API_KEY to be informed on Androidmanifest would be the Key browser (I tried both generated keys, Android key and browser key, but returned nothing in both cases). That’s right?

  • There in the API area there are two: "Places API" and "Places API for Android", you need to activate the second. In the case of Key, it is the same Android Key, putting the SHA and the right package. In the case of Android Studio it generates an automatic SHA when creating the integration project with Google Maps. Also take a look if there are any errors in the console.

  • No error in Console, but I was with the wrong service enabled. Now that I adjusted these key details and replaced the service for "Places API for Android", the application started to crash, and displays the error "android.view.Inflateexception: Binary XML file line #2: Error inflating class <Unknown>". What can it be?

  • Take a look at the Logcat by the full stacktrace, must have given error in some View of the layout.

  • It was a simple naming error in Colors.xml. All solved and working! Thank you @Wakim!

Show 2 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.