Creating Mobile Apps with Appcelerator Titanium
上QQ阅读APP看书,第一时间看更新

Now we can code

Let's talk about the user interface first.

The user interface

For this application, the user interface is pretty straightforward. As mentioned earlier, we will need to create one view per page, and then delegate the display to the PageFlip component.

Of course, everything has to be contained in a window, so after opening the app.js file and deleting all of its content, we create a standard window using Ti.UI.createWindow with Interactive eBook for iPad as its title. We also store its reference in the win variable for later use, as shown in the following code:

var win = Titanium.UI.createWindow({
    title: 'Interactive eBook for iPad'
});
Importing the PageFlip module

While installing, the module might have seemed complex; using it, on the other hand, is quite simple. Once a module is installed and configured for a project, we can invoke it just as we would invoke any other CommonJS component.

var PageFlip = require('ti.pageflip');

Thereafter, every time we need to refer to the PageFlip Native module, we can do so through the PageFlip variable.

Adding some colorful views

For the sake of testing the module's behavior, we will create two basic empty views. This will give us the ability to perform the first test very quickly.

var orangePage = Ti.UI.createView({ backgroundColor: 'orange' });
var bluePage = Ti.UI.createView({ backgroundColor: 'blue' });

We then create our PageFlip view that will wrap the two previously created views using the module's PageFlip.createView function. We want it to have a curl effect when the user changes the page, and that curl effect should last for 0.3 seconds. We also need to set the landscapeShowsTwoPages property to false in order to show only one page at a time. Finally, we set the pages property with an array containing both the views, as shown in the following code:

var pageflip = PageFlip.createView({
    transition: PageFlip.TRANSITION_CURL,
    transitionDuration: 0.3,
    landscapeShowsTwoPages: false,
    pages: [ orangePage, bluePage ]
});

There are other transition effects which we can choose from, such as those shown in the following table:

We add the pageflip component containing our two views to the window, and then open the main window just as we normally do, as shown in the following code:

win.add(pageflip);
win.open();
Making sure the pages actually turn

Let's do our very first test using a native module, by clicking on the Run button from the App Explorer tab.

The following screenshot shows the output screen on our first run, when swiping our finger across the first (orange) view:

Even at this preliminary stage, we can already see that it is working. And it doesn't take much imagination to see that the addition of extra pages can be done easily since all the basics are in place.

Orientation

Since we will be using different UI components in the application, we need to ensure that it will look the same for everyone. Therefore, we decided to choose one orientation and stick with it (for this application at least). So, we need to prevent the device's orientation from changing when a user rotates his/her device.

This is relatively simple; we can do this by modifying the tiapp.xml file and by leaving only Ti.UI.LANDSCAPE_LEFT and Ti.UI.LANDSCAPE_RIGHT in the orientations list. This will force the device's orientation to support only landscape mode, as shown in the following code:

<iphone>
    <orientations device="ipad">
        <orientation>Ti.UI.LANDSCAPE_LEFT</orientation>
        <orientation>Ti.UI.LANDSCAPE_RIGHT</orientation>
    </orientations>
</iphone>

We could also have achieved the same result by setting those same constants to the orientationModes property while creating the window. It would look similar to the following code snippet in the app.js file:

var win = Titanium.UI.createWindow({
    title: 'Interactive eBook for iPad',
    orientationModes: [ Ti.UI.LANDSCAPE_LEFT,
                        Ti.UI.LANDSCAPE_RIGHT ]
});
Tip

While this method also works, it is not always recommended when developing an application with many windows. Otherwise, you will have to define these properties on every single window. This property is used more in the context of the exception than the rule. For example, a photo gallery should be displayed in landscape mode, while the rest of the application should be displayed in portrait mode.

Why not just LANDSCAPE?

We have seen the use of LANDSCAPE_LEFT and LANDSCAPE_RIGHT in the preceding section. But why not simply specify LANDSCAPE and be done with it? And what do these LEFT and RIGHT attributes actually mean?

The answer to these questions is quite simple actually. There is no attribute such as LANDSCAPE, because on iPad, there is no single landscape mode to rule all the components. When a user is holding his or her iPad sideways, he/she will either have the Home button to the left (LANDSCAPE_LEFT), or to the right (LANDSCAPE_RIGHT). This allows us to be more flexible in cases where we absolutely need the user to have access to the Home button in a certain way.

For our e-book application, there is no point in locking the orientation of the device, as long as it is running in the landscape mode. Therefore, we used both constants so that the users can rotate their iPads while still using our application.

The rich HTML page

Now that we have managed to test our application using the two empty views, let's replace those with the real content. We want our first page to provide a rich experience in terms of fonts and layout. For this case, HTML is a lot more suitable (and powerful) to achieve our goal.

But before we dive into it, we must think in terms of the code structure. If we create contents of every single page in the app.js file, it may prove to be cumbersome down the line. It will make the code harder to read and less maintainable. Therefore, we will create a new file called webpage.js that will contain all the code related to this e-books page.

The code is pretty straightforward since it contains a single function that creates a WebView using Ti.UI.createWebView, and assigns its url value with a local HTML file. We could have used a remote URL, but since we want to have an autonomous application, we use local HTML files.

function WebContentPage() {
  var htmlPage = Ti.UI.createWebView({
    url: 'webpage.html'
  });

The following function simply returns the newly created view:

  return htmlPage;
}

The reason for using the function instead of simply declaring the htmlPage variable is because we want to use the CommonJS pattern to instantiate our objects. In practice, this means that the file contents won't be loaded until they are actually instantiated (using the require function).

In order to make this new file callable, we export the function's name using the following statement. We need not worry about the inner workings as of now. But just be aware that another component can only use what is actually exported. All that is not exported is considered as private.

module.exports = WebContentPage;

We now have a rich WebView that brings in all of the functionalities of a browser.

The map view

For our second page, we will be using a map view to display a satellite image of a designated area. Just as for the first page, we will create a new file called mappage.js. This new file will contain a function that will be used to instantiate the map view.

We can create a map view using the Ti.Map.createView function, and save its reference in the map variable. We want it to show a satellite view and show some nice animation when centering to the location. We need the map region to adapt to the application's aspect ratio; we don't want the user to be able to move around the map as it would conflict with the page turning. Lastly, we set the region property with the property GPS latitude and longitude.

Notice the latitudeDelta and longitudeDelta attributes. They stand for the amount of distance displayed on the map, measured in decimal degrees.

function MapContentPage() {
  var map = Ti.Map.createView({
    mapType: Ti.Map.SATELLITE_TYPE,
    animate: true,
    regionFit: true,
    userLocation: false,
    touchEnabled: false,
    region: {
      latitude: 48.8587011132514,
      longitude: 2.2942328453063965,
      latitudeDelta: 0.01, 
      longitudeDelta: 0.01
    }
  });

We return the map variable in order to use it later:

  return map;
}

For the function to be accessible in a CommonJS context, we must export it using the following code:

module.exports = MapContentPage;

We now have a working satellite map view, which will be a nice addition to the book, as shown in the following screenshot:

The video player

Our third and last page will be showing a full-fledged video player. Titanium provides us with a Video Player component. We use a local file contained in the Resources directory as the URL. Just as for the WebView component, we could have used a remote file. But keep in mind that performance may become an issue during playback, depending on the quality of the connection.

We want the video player to have a dark background and give the user access to the video controls (Play/Pause button, Volume control, and Full Screen toggle). We also want the video to occupy the maximum space on the screen, while maintaining its aspect ratio (we don't want the image to stretch).

Finally, we don't want the video to start automatically as soon as it is loaded. The user will have to manually start the video playback.

function VideoContentPage() {
 var embeddedVideo = Titanium.Media.createVideoPlayer({
    url: 'videopage.mp4',
    backgroundColor: '#111',
    mediaControlStyle: Titanium.Media.VIDEO_CONTROL_DEFAULT,
    scalingMode: Titanium.Media.VIDEO_SCALING_ASPECT_FIT,
    autoplay: false
  });
  return embeddedVideo;
}

As always, we export the function in order to use it later:

module.exports = VideoContentPage;

With this, we can now view the video files directly by simply flipping to the page.

Final assembly

Now that all of our e-book pages have been created, it is now time to assemble them all for the final version. To do this, we load each file as CommonJS modules using the require function. Notice that the parentheses right after will give the effect of calling the function as soon as it is loaded.

Our app.js file should now look something similar to the following code snippet:

var page1 = require('webpage')();
var page2 = require('videopage')();
var page3 = require('mappage')();
var pageflip = PageFlip.createView({
  ...
  pages: [ page1, page3, page2 ]
});