Our partners give us access to their unsold inventory – empty seats on flights, empty hotel rooms, and extra cars on the lot – at big savings. By showing the name of our travel partner after customers book, Hotwire can get travel deals that are significantly below published prices.
Because we can’t disclose a lot of detailed information about the hotel, showing the general location of the hotel via neighborhood polygons on a map becomes a very important feature for our app.
The initial versions of our Android application had only listviews on the results page.
Incorporating a map view within the application serves as geographic context for all neighborhoods we display in a particular search result. This was imperative to take our app to the next level, but posed a unique UX challenge, and subsequently, a technical challenge that is detailed in this blog.
The UX Challenge
Given the importance of maps, we wanted to incorporate it into the Android app in a way that not only made it functional, easy, and a delight to use, but also minimized any friction between switching between the list and map views on the hotel results page.
To take on this challenge, the UX team started by researching other native apps out in the marketplace while looking for design patterns that were relevant to our situation. What we observed was that many of the apps were making users tap an icon to flip between the list and map views, similar to what we had done with our iOS app. The other possible option we discovered was a design pattern that combined the list and map view onto one screen (for example, on an older version of the Foursquare app and to some degree Google maps app). After talking, sketching, and wire-framing ideas for the maps feature we believed the combined list and map view would better suit the scenario. Our hypothesis for the combined list and map view was:
- It allowed the user to determine how they wanted to go about exploring the hotel results, rather than us predetermining that experience for them.
- It minimized any friction navigating between the list and map view.
To affirm the hypothesis, we produced several more rounds of wireframes which helped us vet out the concept. We also set up user labs to iron out our design prototypes, each one of them yielding invaluable feedback on our designs.
We initially wanted to build our own UI component extending the ScrollView class. However after some research we found a library called AndroidSlidingUpPanel, but that only took us half way to the desired functionality. In order to save time and not reinvent the wheel, we took the approach to customize it.
The first step was to create a layout file that contained all the interactive views. SlidingPanelLayout was included as a view in the layout file with two children:
- The main layout which contained the map view.
- The sliding panel layout which contained a draggable header and a listview. The draggable header layout was customized by adding more views to it.
Once we had the layout file, we created the fragment that inflated and rendered the layout. An inner class served as a listener with various callbacks to capture different user interaction with the sliding panel; e.g. change in position, anchor, expand, collapse, hide, etc.
A single swipe up gesture should let you slide the sliding panel to the top. Once it reaches the top of the screen the list-view inside the sliding panel should start scrolling for the same gesture. Similarly, when the sliding panel is at the top, a single swipe down gesture should slide the panel down. The library did not provide this functionality and hence we had to modify it.
We solved this problem by modifying the ScrollingSlidingPaneLayout class in AndroidSlidingUpPanel library.
- We removed onInterceptTouchEvent() from ScrollingSlidingPaneLayout and overrode dispatchTouchEvent() to handle all the motion events. Overriding dispatchTouchEvent() method let us take control over motion events and route the events to respective children as required.
- The first event that occurs on a user touch is ACTION_DOWN. We checked the touch coordinates to determine if the user touched the slidable panel (the main content within) or the draggable header (the top portion of the slidable panel) and route the events accordingly. The reason for this is to add a functional feature of collapsing the sliding panel with a user click on the drageable header.
- If the user moves the view, the next event after ACTION_DOWN is ACTION_MOVE.
- Here we check the delta in Y coordinates (between the start and end touch point), to determine if the user swiped up or down.
- If the user is sliding down and if the listview is not already scrolled to the top, we dispatch all motion events to the list view, otherwise we call onTouchEvent() of the sliding panel which will drag the sliding panel down.
- We determine if the list view is scrolled to the top by adding the following code in onScroll() method of the OnScrollListener.
This is how our final version looked. There were three snap points for the listview within the sliding panel view and the map is recentered every time the listview has snapped to an anchor point.
While the outcome has been positive overall, there is some data that suggests that a very small percentage of our users just end up interacting with the partial map view in the default state. Moreover this UX paradigm needs to be optimized for applications that run on tablets.