HTML5

We can draw text, graphics, and animations in a page of HTML5. The page can be displayed by both Android and Apple iOS, so you only have to write it once. The page is displayed in a WebView object in Android, and in a UIWebView object in iOS. These objects act as tiny web browsers. Android documentation: class WebView, Building Web Apps in WebView, and Debugging Web Apps. iOS documentation: see this example.

In the page of HTML5, the graphics are drawn in the language JavaScript using a canvas object. See the HTML5 canvas tutorial.

The page of HTML or HTML5 displayed by a WebView can come from one of three sources:

  1. a file in the app’s assets folder
  2. the web (exercise 4), for example
    1. http://oit2.scps.nyu.edu/~meretzkm/INFO1-CE9705/src/html5/japan.html
    2. http://oit2.scps.nyu.edu/~meretzkm/INFO1-CE9705/src/html5/manhattan.html
  3. a string in a .java file (exercise 5)

Source code in Html5.zip

  1. MainActivity.java: onCreate configures the WebView object.
  2. activity_main.xml creates the WebView.
  3. res/values/dimens/dimens.xml
  4. res/values-w820dp/dimens.xml for wider screens.
  5. japan.html in the assets folder. Multiplication by π/180 converts degrees to radians.
  6. AndroidManifest.xml: a WebView requires the <uses-permission> element (exercise 4).
  7. build.gradle (Module: app)

Create the project

Create the assets folder

Select the app folder at the top of the Android Studio project view and pull down
File → New → Folder → Assets Folder
Creates a source root for assets which will be included in the APK.
☐ Change Folder Location
Target Source Set: main     (vs. debug or release)
Finish

Create the japan.html file

Control-click on the assets folder you just created in the Android Studio project view and select
New → File
Enter a new file name: japan.html
OK

Things to try

  1. activity_main.xml specified that the WebView should occupy the entire RelativeLayout (“match parent, match parent”), but it doesn’t. The RelativeLayout is blue, and part of it is visible because of its padding specified in activity_main.xml. Set the horizontal and vertical padding to 0dp in dimens.xml.

  2. The canvas should occupy the entire body of the web page, but it doesn’t. The body is cyan, and part of it is visible because it has a margin along its edges. Set the margin to zero. In japan.html, change
    <BODY STYLE = "background-color: cyan;" onLoad = "load();">
    
    to
    <BODY STYLE = "
    	background-color: cyan;
    	margin-left: 0px;
    	margin-top: 0px;
    " onLoad = "load();">
    


    On a Samsung Galaxy S5 in portrait orientation, the screen is 1080 × 1920 pixels. Subtracting the status and action bars leaves us with 1080 × 1677; see Japan. On this platform there are 480 pixels per inch; see Text. And on every platform, there are 160 density-independent pixels (dp) per inch. Therefore this platform has 3 pixels per dp. The JavaScript alert prints the dimensions of the canvas in dp: originaly 328 × 527 and now 360 × 559.


  3. When I ran the app on the Samsung Galaxy S5 in Genymotion, the canvas filled the web page and I saw no cyan. But on my Azpen A727 tablet, the canvas was always 320 × 240 and most of the web page was filled with the cyan background. This is because of a bug that prevents window.innerWidth and window.innerHeight from receiving their correct values until a fraction of a second after load is called. I fixed it by changing load to the following.
    //This function is called when the web page has been loaded into the WebView.
    
    function load() {
        window.setTimeout(load2, 300); //milliseconds
    }
    
    //This function is called 300 milliseconds after the web page has been loaded
    //into the WebView.
    
    function load2() {
        try {
            var canvas = document.getElementById("flag");
            //etc.: all the code that was originally in load.
    

  4. In onCreate, change
        webView.loadUrl("file:///android_asset/japan.html");
    
    to
        webView.loadUrl("http://www.nyt.com/");	//New York Times
    
    Add the following element to the AndroidManifest.xml file immediately before the <application> element. (It’s already added.)
    	<uses-permission android:name="android.permission.INTERNET" />
    
    Then change it back.

  5. In onCreate, comment out the call to loadUrl and comment in the call to loadData.

  6. Let the Javascript load function do something more interesting. Change the page of HTML to the following. See the Manhattan project and HTML5 on iPhone.
    <!doctype html>
    <HTML>
    <HEAD>
    <META CHARSET = "utf-8">
    <TITLE>Manhattan</TITLE>
    
    <SCRIPT TYPE = "text/javascript">
    
    //Constructor for class Location.
    
    function Location(longitude, latitude) {
        this.longitude = longitude;
        this.latitude = latitude;
    }
    
    //This function is called when the web page has been loaded into the WebView.
    
    function load() {
        window.setTimeout(load2, 300); //milliseconds
    }
    
    //This function is called 300 milliseconds after the web page has been loaded
    //into the WebView.
    
    function load2() {
        try {
            var canvas = document.getElementById("map");
            var context = canvas.getContext("2d");
    
            //Make the canvas occupy the whole web page.
            canvas.width  = window.innerWidth;
            canvas.height = window.innerHeight;
    
            //Fill the map with a white background.
            context.fillStyle = "white";
            context.fillRect(0, 0, canvas.width, canvas.height);
    
            /*
            An array of Location objects,
            counterclockwise around the shore of Manhattan.
            Longitude west of Greenwich is negative.
            Latitude north of the equator is positive.
            */
            var point = new Array(
                new Location(-73.971548,	40.72921),	//East River at East 17th Street
                new Location(-73.974595,	40.735519),	//24
                new Location(-73.971806,	40.742998),	//34
                new Location(-73.96215,		40.754767),	//53
                new Location(-73.954296,	40.762146),	//65
                new Location(-73.946185,	40.771474),	//81
                new Location(-73.942022,	40.776154),	//89
                new Location(-73.942022,	40.776154),	//96
                new Location(-73.93816,		40.787008),	//103
                new Location(-73.929534,	40.795326),	//118
                new Location(-73.929062,	40.800946),	//125
                new Location(-73.934212,	40.808775),	//Harlem River at 132nd Street
                new Location(-73.933868,	40.817772),	//143
                new Location(-73.935113,	40.83547),	//163
                new Location(-73.922195,	40.855857),	//Dyckman Street
                new Location(-73.91078,		40.869878),	//218
                new Location(-73.911767,	40.873416),	//Broadway Bridge
                new Location(-73.922968,	40.877018),	//Henry Hudson Parkway Bridge
                new Location(-73.926916,	40.877082),	//Hudson River
                new Location(-73.933096,	40.867379),	//Riverside Drive
                new Location(-73.943224,	40.852417),	//Hudson River at West 181st Street
                new Location(-73.946786,	40.850339),	//George Washington Bridge
                new Location(-73.946786,	40.850339),	//168
                new Location(-73.95052,		40.834626),	//155
                new Location(-73.955026,	40.827417),	//144 sewage treatment plant
                new Location(-73.956399,	40.828034),	//144
                new Location(-73.959446,	40.82365),	//137
                new Location(-73.957601,	40.822676),	//137
                new Location(-73.994765,	40.771669),	//57
                new Location(-73.995152,	40.769524),	//54
                new Location(-73.999872,	40.763316),	//44
                new Location(-74.001718,	40.762276),	//42
                new Location(-74.007726,	40.754052),	//29
                new Location(-74.009442,	40.749825),	//23
                new Location(-74.00794,		40.748362),	//21
                new Location(-74.009228,	40.740754),	//Meatpacking District
                new Location(-74.010344,	40.739258),	//Gansevoort Street
                new Location(-74.011545,	40.726218),	//Holland Tunnel
                new Location(-74.013176,	40.718315),	//Battery Park City
                new Location(-74.016609,	40.718737),	//Battery Park City
                new Location(-74.019227,	40.706539),	//South Cove
                new Location(-74.014893,	40.70078),	//Battery Park
                new Location(-74.009314,	40.701919),	//Heliport
                new Location(-73.997984,	40.708523),	//north of Brooklyn Bridge
                new Location(-73.977985,	40.710475),	//Corlears Hook Park
                new Location(-73.976011,	40.712752),	//Grand Street
                new Location(-73.972964,	40.720819)	//East 6th Street
            );
    
            //Three transformations:
            //Transformation 1: move the origin (0, 0) to the center of the canvas.
            context.translate(canvas.width / 2, canvas.height / 2);
    
            //Center of Manhattan, near the Angel of the Waters in Central Park.
            var center = new Location(-73.965, 40.79);
            var latitude = center.latitude * Math.PI / 180; //latitude in radians
    
            //The screen will cover 1/4 of a degree of latitude vertically,
            //approx 15 miles.  New York, New York, it's a helluva town!
    
            //pixels per degree of latitude (approx 60 miles)
            var verticalScale = 4 * canvas.height;
            //pixels per degree of longitude (approx 45 miles in New York)
            var horizontalScale = verticalScale * Math.cos(latitude);
    
            //Transformation 2: magnify the map, and make the Y axis point up.
            context.scale(horizontalScale, -verticalScale);
    
            //Transformation 3: move the camera from Latitude 0, Longitude 0
            //(in the South Atlantic) to the center of Manhattan.
            context.translate(-center.longitude, -center.latitude);
    
            context.beginPath();
            context.moveTo(point[0].longitude, point[0].latitude);
            for (var i = 1; i < point.length; ++i) {
                context.lineTo(point[i].longitude, point[i].latitude);
            }
            context.closePath();
    
            context.fillStyle = "red";
            context.fill();
        } catch (exception) {
            alert("canvas not supported: " + exception);
        }
    }
    </SCRIPT>
    </HEAD>
    
    <BODY STYLE = "
        background-color: cyan;
        margin-left: 0px;
        margin-top: 0px;
    " onLoad = "load();">
    <CANVAS ID = "map">
    </CANVAS>
    </BODY>
    
    </HTML>
    

  7. Let the Java Activity object call a Javascript function in the web page. We can’t do this until the page is fully loaded. A WebViewClient will tell us when the page is fully loaded.

    Create another Javascript function in the web page.

    function f() {
        alert(window.innerWidth + " \u00D7 " + window.innerHeight);
    }
    
    In onCreate, insert a WebViewClient into the WebView before inserting the WebChromeClient into the WebView.
            webView.setWebViewClient(new WebViewClient() {
                @Override
                public void onPageFinished(WebView view, String url) {
                    Toast toast = Toast.makeText(MainActivity.this, url + " loaded", Toast.LENGTH_LONG);
                    toast.show();
                    view.loadUrl("javascript:f()");
                }
            });
    
    The toast says “file:///android_asset/japan.html loaded”. How could we return a return value from f?

  8. Could a Javascript function call a method of a Java object? See this demo.