Speech Bubble Popups Containing a View for Android MapView

Update May 2012: There have been lots of requests in the comments for the source. Sorry for the delay on this one - I finally put the whole app that I’d been working on up on github. The part from this tutorial specifically is here if you want to take a look.

I’ve recently been playing around with Android, and have been building a small app that uses MapView to display various locations on the map. I succeeded in drawing markers on the MapView using ItemizedOverlay, but was having a fair bit of difficulty drawing little bubble popups that appeared when you tapped on the OverlayItems (which unfortunately isn’t available in the API as a ‘standard’ feature).

My main problem was I wanted to be able to draw a full View on top of the map at the right location. I had a good old search on Google for a while, and couldn’t seem to find any easy way to do this, so I thought I’d write up how I ended up with the bubble in the screenshot. (Now I know how it’s done however, the Google results seem a lot more¬†knowledgeable on the subject - I was probably searching for the wrong things :-( ).

My first approach was to build a subclass of Overlay and override its draw() method, where I converted the View to a Drawable to draw it directly onto the MapView canvas (passed to draw() ). This worked, but naturally events would no longer work properly in the View if it contained things like buttons, so it was back to the drawing board.

Next, I tried subclassing Dialog, and used a custom Dialog theme with my bubble background. The View I wanted was just injected into the Dialog through its setContentView method. This worked again, but I found myself fighting with the Dialog class a fair bit, and positioning it turned out to be a bit fiddly - there had to be a better way.

Finally I noticed that MapView inherits from the Android ViewGroup, which means it can contain other Views. After a bit more probing, and a peek at the implementation of¬†android-mapviewballoons (which is excellent, but didn’t quite fit my purposes due to lack of custom balloon layouts) found a way to do it which should probably have been obvious from the outset!

The general approach is to add a new child View to the MapView, and then use the MapView.LayoutParams to position it. Here’s a quick outline example if you’re unfamiliar (the View I’m displaying has a NinePatch as the background attribute of a top level LinearLayout, which makes the bubble wrap the content):

This is in the initial activity setup - my balloon is re-used for all OverlayItems drawn on my map:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//Reference to our MapView 
MapView mapView = (MapView) activity.findViewById(R.id.mapview);

//Get a LayoutInflater and load up the view we want to display. 
//The false in inflater.inflate prevents the bubble View being added to the MapView straight away 
LayoutInflater inflater = activity.getLayoutInflater();
LinearLayout bubble = (LinearLayout) inflater.inflate(R.layout.bubble, mapView, false);

//Set up the bubble's close button 
ImageButton bubbleClose = (ImageButton) bubble.findViewById(R.id.bubbleclose);

bubbleClose.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        Animation fadeOut = AnimationUtils.loadAnimation(ResultsMapResultsDisplayer.this.activity, R.anim.fadeout);
        bubble.startAnimation(fadeOut);
        bubble.setVisibility(View.GONE);
    }
});

This next part is the important bit, and actually positions the bubble on the MapView using MapView.LayoutParams. It’s called by the onTap method of the ItemizedOverlay that contains my map markers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private void displaySearchResultBubble(final SearchResult result) {

    map.removeView(bubble);
    bubble.setVisibility(View.GONE);

    TextView venueName = (TextView) bubble.findViewById(R.id.venuename);
    venueName.setText(result.getName());

    TextView venueTime = (TextView) bubble.findViewById(R.id.venueopenfor);
    venueTime.setText("Open for " + result.getOpenFor() + "h");

    TextView venueFee = (TextView) bubble.findViewById(R.id.venuefee);
    venueFee.setText("Entry fee " + result.getPrice());

    MapView.LayoutParams params = new MapView.LayoutParams(
            LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
            result.getPoint(), MapView.LayoutParams.BOTTOM_CENTER);

    bubble.setLayoutParams(params);

    map.addView(bubble);
    map.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));

    Runnable r = new Runnable() {
        public void run() {
            Animation fadeIn = AnimationUtils.loadAnimation(activity, R.anim.fadein);
            bubble.setVisibility(View.VISIBLE);
            bubble.startAnimation(fadeIn);
        }
    };

    Projection projection = map.getProjection();

    Point p = new Point();
    projection.toPixels(result.getPoint(), p);

    p.offset(0, -(bubble.getMeasuredHeight() / 2));
    GeoPoint target = projection.fromPixels(p.x, p.y);


    mapController.animateTo(target, r);

}

That’s it! It’s a fairly quick example but feel free to let me know in the comments if you want any more detail.

Vim XPath Plugin

I’ve built a plugin that adds XPath search functionality to Vim. Check out the demo video below.

This video and blurb is actually about the old version of the plugin. It has since been rewritten, although it is functionally very similar to the video below. There is no XPath auto-completion anymore though, and results instead appear in the vim location list.

Vim XPath Plugin Demo from Dave Aitken on Vimeo.

Features: * Results shown in a pop-up results window. Jump to the line of a matching result easily

Requirements: * Python support enabled in Vim * lxml library installed for Python. For more info, and to grab the plugin visit my github. Let me know what you think / any improvement suggestions in the comments below.

Streaming Spotify to a PS3 From Ubuntu

A post came up a couple of days ago on the Spotify “What’s new” feed: how to stream spotify wirelessly to your stereo, which got me thinking about something I’ve been after for a while - the ability to stream spotify audio to PS3. This is mentioned in the link, but the main method they talk about is for Windows, and a solution for mac is touched upon. I figured I’d have a go at doing this on Ubuntu. Before you have a go at this, I should mention a slight drawback - there’s a fair delay on the audio steam that reaches the PS3, around 20 or 30 seconds. This makes it good for playing a playlist as background music, but if you’re wanting to be able to flick around loads of different songs then you might get a bit frustrated. Read on if you’re still keen…

Read on →

CakePHP: PagesController With Admin Routing

Note this page was first published in April 2009, so is likely to be quite out of date now.

For small Cake websites with admin routing enabled, I like to use the Auth component to require a login for all admin routes, and allow access to everything else using the following beforeFilter in the AppController superclass:

1
2
3
4
5
6
7
8
9
10
<?
function beforeFilter(){
    $admin = Configure::read('Routing.admin');
    if (isset($this->params[$admin]) and $this->params[$admin]){
        $this->layout = 'admin';
    } else {
        $this->Auth->allow();
    }
}
?>

The problem with this however is static pages handled by the pages controller cannot be password protected. To resolve this problem, I had to overload the PagesController class that Cake comes with, and add in the required functionality. Part of the reason for doing this for me was to allow a setup where there was a password protected admin welcome page or control panel located at my_app_URL/admin, so I’ll show you the necessary routing to achieve that too.

Read on →

CakePHP Tutorial Part 2: Authentication and Tweaking

In the first part of my CakePHP tutorial, I showed you how to use Cake’s Bake utility to set up the basic back end of an online illustration portfolio. In this part, I’ll tweak the automatically generated code into useable website and admin section and show you how to use some of Cake’s features along the way, including the Authentication component.

If you followed the first part of the tutorial, we currently have a few sections that we’ve generated models, controllers and views for. By navigating to your_app/controllername/action in your browser, you can access the different functions of the application. But unless your users know this in advance, they have no way of accessing them.

Read on →