July 15, 2011
Canvas Plugin Bug Fix
A bug was discovered in my toDataURL canvas plugin for Android by Jared Sheets. The code has been changed to fix it. Please re-download the plugin below if you have a version released before July 15, 2011.
PhoneGap Canvas Plugin Downloads (Learn more about PhoneGap plugins)
Download CanvasPlugin.java
Download canvas.js
Download example usage HTML
or...
Download full Eclipse Project
June 18, 2011
One Screen Resolution to Rule Them All
Update: The technique below will also scale your page up. Bitmap images (e.g. PNG, JPEG) will look stretched and blurry when this happens (and Android WebView doesn't support SVG... yet). I don't advise shipping apps with images designed for 1280x800, so I would either block xlarge screens in the Android manifest and create a 'tablet' version or put some logic in your code that pulls the larger bitmaps from a server if a screen is detected that is bigger than your original design.
I completely ignored other screen resolutions when developing Scribble. PhoneGap lives in the Web world of pixels and Android lives in the confusing world of densities and resolutions. Things are upscaled & downscaled. Android 2.1 has weird bugs with the canvas tag. As a traditional Web developer, this annoyed me. Today, I make designs at somewhere around 900-1000 pixels wide and like an F-18, bro, I hit my only speed - go! This approach lead to a 67% attrition rate from my app installs. Oops.
Apps are a funny thing when placed within a Web context. An app is always an edge-to-edge design and should look consistent across all devices. The 'right' thing to do is use multiple stylesheets and all sorts of ems & percentages, I suppose. But that's annoying too. What a development and maintenance headache. When fixed layouts finally won over fluid in the design war of the 2000s, I felt a distinct Kumbaya moment. And just shrinking spacing & font sizes doesn't really lead to an acceptable-looking layout.
When you're designing an app in Photoshop, you simply decide where things should go, how much spacing to give them and you're done. But bitmap images have a static size, so when scaled, might not look so hot.
I began with the bigger - but not quite 10" tablet size dimension of 480x800. This can be called anything from "small" to "large" in Android's Manifest XML. So no filtering available by pixel dimension. Ugh. The only thing to do was to make a design for all sizes.
So in the interest of refusing to do more design work than was necessary (or what some would call laziness), I went after the CSS 'zoom' property. Zoom scales an element. When applied to the body tag, it scales the page. Perfect. I can create my design at 480x800 and if something smaller comes along, shrink it. Since zoom works just like pinch-to-zoom in a browser, the mobile device is doing its best to make things look good at the smaller size. With that theory, I set out to put it into practice in my app.
Guess what? It worked great! Not perfectly, but far better than taking all of the time to create multiple stylesheets and whatnot.
The problem areas were hard-coded numbers in Javscript code and the canvas tag. The hard-coded numbers in Javascript made sense, since they live outside of the DOM when rendered. But the canvas rendering doesn't make much sense to me. At any rate, passing the percentage scale change to the canvas width and height attributes and to all of my hard-coded numbers (in my case, the size of lines & circles when drawing on the canvas and the scribble import) worked.
The Code (I'm using jQuery to apply the zoom CSS)
var designWidth = 480; // zoom to fit this ratio
var designHeight = 762; // not 800 b/c top bar is 38 pixels tall
var scaleChange = 1; // % change in scale from above #s
function zoomScreen() {
var docWidth = window.outerWidth;
var docHeight = window.outerHeight;
if (docWidth != designWidth) {
var scaleX = docWidth / designWidth;
var scaleY = docHeight / designHeight;
if (scaleX < scaleY) {
$('body').css('zoom', scaleX);
scaleChange = scaleX;
} else {
$('body').css('zoom', scaleY);
scaleChange = scaleY;
}
}
}
June 14, 2011
Oh... Scribble for Android Released Last Week
I guess I should have posted the release of my first PhoneGap app here too.
My first PhoneGap game is now available in the Android Market. And it's entirely FREE, you lucky people! Search for 'scribble' and look for the hot pink letter S icon or go to the Scribble Market page.
Visit the Scribble website.
June 14, 2011
Native Share Menu in Android
This plugin will allow you to share images on the Web, images on your phone or HTML/plain text using Android's native sharing menu.
Limitations:
+ Currently only accepts JPEG, PNG and GIF binary formats but other types can be easily added.
+ Facebook does not show text with image attachments (Facebook Android App Bug 16728)
+ HTML only shares over Email & Bluetooth (3rd-party app setups limit this).
+ When plain text is shared, Facebook sharing only allows absolute URLs. All other sharing works with other text.
PhoneGap Share Plugin Downloads (Learn more about PhoneGap plugins)
Download SharePlugin.java
Download URLDownload.java (required for sharing images on the Web)
Download share.js
Download example usage HTML
or...
Download full Eclipse Project
Usage:
window.plugins.share.send(sharingObject:String, mimeType:String, options:JSON);
options.title:String
options.subject:String
options.success:Function
options.fail:Function
Acceptable values:
sharingObject: http(s):// or local file path
Note: http(s) use requires external write access in the Android Manifest XML.
mimeType: image/jpeg, image/png, image/gif, text/html, text/plain
options.title: Any string. This is the title at the top of the sharing menu.
options.subject: Any string. This is text that is shared with the binary file.
options.success: Function to call when the share menu is displayed.
options.fail: Function to call if the share menu did not display.
The success function will take 1 argument, which is an object with the following properties:
result
Result of the sharing. 1 was successful. 0 was unsuccessful.
debug
Java exception thrown if there was an error.
June 1, 2011
Updated: PhoneGap FileSystem Object
My FileSystem code had a bug where writing a shorter string to a file that had a previously longer string in it would keep part of the longer string. For example, if your file had "helloworld" in it and you wrote to that file again with "foobar", you would end up with "foobarorld". Fixed version of the code below for download. No usage has changed.
I wasn't truncating the file for a full overwrite, which lead to the bug. However, I think there may be a bit of a bug with PhoneGap 0.9.5's file truncating. The docs show an example where you can call a write, a truncate and then another write. In my experience, once I truncate, the following write call never executes. To get around this bug, I've resorted to deleting the file and then writing it again.
Download pgfs-0.2.js
Usage (all directories & files are relative paths from your file system's root)
PgFs.file_get_contents(file:String, {binary:Boolean, success:Function, fail:Function});
PgFs.file_put_contents(file:String, {contents:String, success:Function, fail:Function});
PgFs.mkdir(directory:String, {success:Function, fail:Function});
The success function in 'file_get_contents' will take 1 argument and that argument will be the contents of the file that was read. An example of a 'get' success function is this:
PgFs.file_get_contents('readme.txt', {success: gotFile, fail: noFile});
function gotFile(val) {
alert('Readme: ' + val);
}
function noFile(val) {
alert('readme.txt does not exist');
}
May 18, 2011
Scribble App Coming Early June
I gave myself an artificial deadline of May 21 to finish Scribble before I go on a vacation. But after about 14 days of relentless design & coding, it looks like I might fall a little short - I need a beta testing period before I thrust it into the Android Market anyway.
To the right are some screen shots of the app as it exists today.
May 13, 2011
HTML5 canvas tag toDataURL support in Android
Important Update:
If you downloaded this plugin before July 15th, download it again. There was a major bug with screen density. The plugin would work correctly for medium density devices only. Low and high desnity screens would over-capture and under-capture the canvas respectively. A huge thanks goes out to Jared Sheets for not only discovering this flaw but helping to provide the fix. Thank you, Jared!
The toDataURL function was critical for the app I'm building and I sorely learned that Android's webkit doesn't support it. So I leveraged PhoneGap's plugin architecture to create a work-around.
This plugin takes your canvas as an argument, which gets converted to x & y coordinates and a width & height for the region to "screenshot" from your PhoneGap WebView. That resulting bitmap is converted to either a PNG or JPEG and returned as a base64-encoded string.
This plugin could be generalized for any kind of screenshotting from within PhoneGap, but I had a specific purpose in mind with this.
PhoneGap Canvas Plugin Downloads (Learn more about PhoneGap plugins)
Download CanvasPlugin.java
Download canvas.js
Download example usage HTML
or...
Download full Eclipse Project
Usage:
window.plugins.canvas.toDataURL(canvas:HTMLCanvasElement, mimeType:String, success:Function, fail:Function);
The success function will take 1 argument, which is an object with the following properties:
data
Base64-encoded string that's either a PNG or JPEG
size
Length of the string. To be used for detecting encoding errors. If it's undefined or 0, something went wrong.
debug
Java exception thrown if there was an error encoding the canvas.
May 11, 2011
Updated: PhoneGap FileSystem Object
The first iteration of code I wrote to make reading/writing files easier with PhoneGap (0.9.5+) was a little messy and missing binary reads. So I've released a new version that addresses both. Note that you no longer have to call a function to bootstrap the functions, you must prepend the object name now before method calls and the 2nd argument in the methods is now JSON.
Download pgfs-0.1.js
Usage (all directories & files are relative paths from your file system's root)
PgFs.file_get_contents(file:String, {binary:Boolean, success:Function, fail:Function});
PgFs.file_put_contents(file:String, {contents:String, success:Function, fail:Function});
PgFs.mkdir(directory:String, {success:Function, fail:Function});
The success function in 'file_get_contents' will take 1 argument and that argument will be the contents of the file that was read. An example of a 'get' success function is this:
PgFs.file_get_contents('readme.txt', {success: gotFile, fail: noFile});
function gotFile(val) {
alert('Readme: ' + val);
}
function noFile(val) {
alert('readme.txt does not exist');
}
May 9, 2011
Android Text Input & You
Update:Phones that use HTC's Sense skin (e.g. the Droid Incredible) on top of Android change the native WebView to ignore the viewport tag, which I mentioned below is needed to prevent the zoom in on text entry. There is no known solution for this. I have had some limited success by using a setTimeout to repeatedly insert the viewport tag, which gets the phone out of the zoomed-in view. This also hides the keyboard though.
I had 2 recent issues with form input, specifically textboxes, on Android using PhoneGap.
Problem 1: Disabling screen zoom on input focus
The screen will zoom in on focus of the input textbox. You're beautifully designed 480x800 layout ends up looking like garbage. And when you hide the keyboard, it doesn't zoom back out.
The solution to this problem is to use the following meta tag in your HTML head tag:
<meta name="viewport" content="width=device-width; minimum-scale=1.0; maximum-scale=1.0; target-densityDpi=device-dpi; user-scalable=no">
You may have seen similar markup on the web and it didn't work - I had the same, frustrating problem. What some of the tags around the web omitted was the 'target-densityDpi' attribute.
You can also ignore the solutions on the web that suggest setting the 'setSupportZoom' to false in Java code for Android's WebSettings class. This doesn't fix this problem.
Problem 2: CSS background image behind textbox disappears on focus
This problem is specific to where on the page your textbox lives. If the textbox is located at the bottom of your page and there isn't enough room below it for the keyboard (about 400 pixels in portrait mode), you will see the keyboard "push up" the screen such that your textbox is still visible when you're typing (this is a good thing).
However, when the screen "pushes up" the bottom of your page, your CSS background will not display correctly - you will end up with an empty background with whatever the color of the page is. In my case, I was getting a white background behind my textbox.
The solution to this is to make sure there is enough room below your textbox for the keyboard. You can accomplish this by adjusting your design to include enough space below the textbox. This didn't work in my case, because I didn't want to adjust my design. My solution was to add height to the page on focus of the textbox and remove that height when the textbox loses focus.
Using jQuery, I wrote this (obviously this isn't a copy/paste solution for everyone):
$('.textInput').focus(function() {
$('#page').css('height', '150%');
});
$('.textInput').blur(function() {
$('#page').css('height', '100%');
});
May 5, 2011
PhoneGap: Simplified File Functions
Update: The file.js described here is out of date. See pgfs.js above.
I dove head first into Android apps & HTML 5's canvas tag this week. I hope to have a fully baked game soon.
In the meantime, as I work with PhoneGap's sweet framework, I've been creating little helper files that others might find useful. As of version 0.9.5, the directory & file reading/writing has changed to meet the W3C's specs (as of this writing, the updated documentation is still pending). I personally find the contruction annoying without another layer on top of it. So I present file.js for PhoneGap. Feel free to modify it and improve it as you see fit.
Usage: (all directories & files are relative paths)
Import this file.js file into your HTML document.
You must execute the following function before calling the functions below:
ezFs();
file_get_contents(file:String, success:Function, fail:Function);
file_put_contents(file:String, contents:String, success:Function, fail:Function);
mkdir(directory:String, success:Function, fail:Function);
The success function in 'file_get_contents' will take 1 argument and that argument will be the contents of the file that was read. An example of a 'get' success function is this:
file_get_contents('readme.txt', gotFile, noFile);
function gotFile(val) {
alert('Readme: ' + val);
}
function noFile(val) {
alert('readme.txt does not exist');
}
Limitations: These functions use the readAsText method from PhoneGap. This means this only works with text files, not binary files. If you need to read/write images, for instance, you will need to change this code.