Displaying results

Once we obtain the translation results, we need to display them. The design says that we can have a separate view for this, which slides in to display the entry view. A scene seems like a good fit for each of these views; we'll create one that stores the various text elements of the translation in the rows of a table view.

Getting ready

Copy the scenetemplate.lua file in the project directory to a new file named result.lua and open it.

Getting on with it

The design says that this display will contain a list of the text and its translation, and a bar with a button to return to the previous view. We want the button bar to appear above the table, but simply creating a table view and placing a rectangle above it runs into a snag; while Corona's tableView objects offer a lot of convenience, they don't currently have an easy way to clear or reset them. The simplest way to make sure a table is up to date with its intended contents is to create the table each time the scene is visited, and delete it when the scene is no longer visible; this means that we can always enter the contents into a fresh, empty table.

To keep elements stacked correctly as they're destroyed and recreated, what we'll do is create a couple of groups and establish them as zones into which different sorts of content can be stacked, so that we have a place into which we can put new tables so that they will always be behind the controls.

Note

This is a common pattern in graphics programming, to create layers within a stack of visual elements, so that regardless of the order of items in each layer, they're always separated from the layers above and below them. The zones this creates typically are reserved for elements with specific meanings or purposes and are sometimes referred to as strata , a word taken from geology and referring to a stack of distinct layers with different characteristics.

Constructing the strata

Find the createScene function for this scene, and replace the placeholder comment with the creation of some groups that will serve as our strata, or layers. Groups follow the same rules we've just described; if group 2 is in front of group 1, then each thing in group 2 is in front of all things in group 1:

function scene:createScene( event )
  local group = self.view

  self.Data = display.newGroup()
  group:insert(self.Data)
  self.Controls = display.newGroup()
  group:insert(self.Controls)
end

The control layer doesn't need to be refreshed from one trip through the scene to another, so we can create it now. In the style of the iOS navigation bars, we'll create a blue field to block out the background and give the button a natural place to live. Because this is not an immersive app like a game, we'll leave room for the status bar at the top of the screen.

  group:insert(self.Controls)
  self.Controls.y = display.statusBarHeight
  display.newRect(self.Controls, 0, 0, display.contentWidth, display.contentHeight * 0.08)
    :setFillColor(0x90, 0xA0, 0xC0)
end

Adding the controls

Buttons and other widgets offer a lot of customization options, but most of these have sensible defaults. To facilitate providing only the information you need to, each widget constructor takes a table as its single argument, which can contain only the settings that you want to override or specify. The default button appearance is a white rounded rectangle with a black border and a black label, but we want to adjust our button to make it look more at home on our blue background.

    :setFillColor(0x90, 0xA0, 0xC0)
  local buttonHeight = self.Controls.height * 0.8
  self.Back = widget.newButton {
    left = display.contentWidth * 0.05, top = buttonHeight * 0.1;
    label = "Back", yOffset = buttonHeight * -0.1, labelColor = {default = {0xFF, 0xFF, 0xFF}, over = {0x70, 0x70, 0x70}};
    height = buttonHeight, width = buttonHeight * 2.75;
    fontSize = buttonHeight * 0.66;
    onEvent = relay;
  }
  self.Back:setFillColor(0xB0, 0xC0, 0xF0)
  self.Controls:insert(self.Back)
end

We set the button's size to be a little shorter than the bar it's on, its font size to be small enough to fit in that height, and its width to be in proportion.

Buttons can have many more customizations, such as using embossed text for the label, or using custom images instead of simple rounded rectangles. The most important customization a button has, however, is its behavior. Buttons are supposed to do something when you activate them, so the creation of a button also takes a function that specifies what will happen when the user interacts with the button by touching the button, releasing a touch that began in it, or sliding their finger around after touching the button.

Widgets handle events a little strangely. Rather than dispatching events directly to the widget as an event target, they take a function at creation time which is the only receiver of that widget's events. However, that function can also take charge of broadcasting events onto the widget, allowing them to be handled the way most other events are.

local scene = storyboard.newScene()

local widget = require "widget"

local function relay(event, ...)
    event.target:dispatchEvent{name = 'buttonEvent'; target = event.target; phase = event.phase}
  return true
end

-- Called when the scene's view does not exist:
function scene:createScene( event )

Note

You can still register your event response function directly to your button if you prefer; this makes the code a little less flexible, but sometimes code needs efficiency more than it needs flexibility. Buttons also allow you to specify up to two different listeners to handle press and release events separately from each other; if you use any of these, you should not specify the general onEvent function.

Once our button receives events the way we expect it to, we can set the scene up as a listener, to respond to the button's activation by returning to the previous scene. The name we specified for button events is, fittingly, buttonEvent:

  self.Controls:insert(self.Back)
  self.Back:addEventListener('buttonEvent', self)
end

Since the scene is being used as the listener, it needs a method named after the event to handle it:

local function relay(event, ...)
  event.target:dispatchEvent(event, ...)
  return true
end

function scene:buttonEvent(event)
  if event.phase == 'ended' then
    storyboard.gotoScene(self.Origin)
  end
end

-- Called when the scene's view does not exist:
function scene:createScene( event )

Note that we return to the scene with its name stored in scene.Origin. For this to work properly, we'll need to set that value whenever the scene is started, which we'll set up after one important step.

Preloading scene visuals

Unlike our previous scenes, this scene will be moved onto the screen using a transition. This means that parts of the scene will be visible before the scene is completely on the screen; the enterScene event fires for a scene once its entrance transition is complete and it's fully on the screen. If we wait until this point to go through the usual process of filling in the scene's contents, it will appear to slide in mostly empty and then suddenly fill in with info once it stops moving. Since most people will find this a jarring experience, we want to populate the scene before it starts to appear. We'll do this by changing the data population from taking place in the enterScene event to the willEnterScene event, which fires as soon as the scene change is requested. So change the scene:enterScene function to scene:willEnterScene, and change the event registration from scene:addEventListener('enterScene', scene) to scene:addEventListener('willEnterScene', scene). Also change scene:exitScene to scene:didExitScene and update that listener registration in the same way.

Now we'll take care of that stray variable that wasn't assigned yet.

function scene:willEnterScene( event )
  local group = self.view

  self.Origin = storyboard.getPrevious()
end

Creating the list display

As said when we created the view strata, the easiest way to make sure the list is updated whenever the scene is reloaded is to destroy the table and create it from scratch:

function scene:willEnterScene( event )
  local group = self.view

  self.Origin = storyboard.getPrevious()

  local output = widget.newTableView{
    id = "results_output";
    width = display.contentWidth, height = display.contentHeight;
    topPadding = self.Controls.contentBounds.yMax, bottomPadding = 50;
  }
  self.Data:insert(output)
end

While table views support an enormous list of customizable options, the focus in this project is using the ready-to-go nature of widgets to make a project run very easily and quickly, so we're using defaults when reasonable. The only thing we're specifying is the padding on the top and bottom. These make sure that no part of the data being displayed is trapped under the control bars at the top or bottom. We set the top padding to start at the bottom of the control section, which allows for both its height and the height of the status bar.

Adding rows to the display

Now we need to add each piece of text to the table. As the project is laid out right now, only two items will ever be displayed: the English source text and its Japanese translation. However, it's not inconceivable that we might do something in the future like have a piece of text entry translated into several languages simultaneously. We'll start by adding a row to the table view for each item being displayed:

  self.Origin = storyboard.getPrevious()

  local texts = event.params
  local output = widget.newTableView{
    id = "results_output";
    width = display.contentWidth, height = display.contentHeight;
    topPadding = self.Controls.contentBounds.yMax, bottomPadding = 50;
  }
  self.Data:insert(output)
  for i, text in ipairs(texts) do
    output:insertRow{
      -- construct rows
    }
  end
end

Now we run into a small stumbling block. There are still a small number of useful facilities that Corona doesn't provide direct support for, and one of these is measuring the space required to display a piece of text. Fortunately, most of these missing facilities have some way to work around them, and we can determine how many lines are needed to show a given string by setting a hidden text object to show the string and checking how tall it becomes:

  self.Origin = storyboard.getPrevious()

  local width, height = display.contentWidth, display.contentHeight
  local texts = event.params
  local output = widget.newTableView{
    id = "results_output";
    width = display.contentWidth, height = display.contentHeight;
    topPadding = self.Controls.contentBounds.yMax, bottomPadding = 50;
  }
  self.Data:insert(output)
  local measure = display.newText(group, "", width, height, width, 0, native.systemFont, height * 0.075)
  measure.isVisible = false
  for i, text in ipairs(texts) do
    measure.text = text
    output:insertRow{
      rowHeight = measure.height;
    }
  end
end

We just need to make sure that we clean up this temporary text object once we're done with it:

    output:insertRow{
      height = measure.height,
      onRender = displayText,
    }
  end
  measure:removeSelf()
end

Finally, for each row that we provide, we need to explain to Corona how the row will appear and what data it will contain. You can use different functions for different rows in cases where they don't all appear the same, but all we're doing is showing the text of each item. The default color of new text objects is white, which doesn't show up well (or in fact at all) on a white background, so we set each new text object to display in black:

  local texts = event.params
  local function displayText(event)
    display.newText(event.row, texts[event.row.index], 12, height * 1/256, width, 0, native.systemFont, height * 0.075)
      :setTextColor(0x00)
  end
  local output = widget.newTableView{
    id = "results_output";
    width = display.contentWidth, height = display.contentHeight;
    topPadding = self.Controls.contentBounds.yMax, bottomPadding = 50;
    onRowRender = displayText;
  }
  self.Data:insert(output)

Cleaning up the scene after each use

Since we're creating a new table view each time this scene is visited, we need to make sure they get cleaned up, or we'll come back to overlapping displays (and creeping memory leaks):

function scene:didExitScene(event)
  while self.Data.numChildren > 0 do
    self.Data[1]:removeSelf()
  end
end

What did we do?

We developed a complete life cycle for a scene that will probably be reloaded multiple times while the app is running, working around some limitations of our target platform (which you'll find you need to do in nearly any programming environment). We separated visual environments logically as well as visually by storing them in separate groups, and used built-in capabilities provided by Corona to display buttons and complex program output to the user with a minimum of work on our part.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.41.187