Ở phần này, ta tìm hiểu về các thành phần giao diện người dùng(UI) có sẵn trong J2ME nhằm tạo ra sự tương tác giữa người dùng với điện thoại, đây là vấn đề quan trọng bởi kích thước của màn hình điện thoại được giới hạn. J2ME cung cấp đặc tả MIDP chứa các thành phần giao diện đồ họa, hiện nay đã có rất nhiều phiên bản MIDP 3.0, tuy nhiên trong bài này tôi chỉ xét MIDP 2.0. Ta cùng tìm hiểu sơ đồ phân lớp của nó:

MIDP 2.0 cung cấp các lớp UI trong một gói javax.microedition.lcdui, trong đó lcdui là viết tắt của liquid crystal display user interface(LCD UI). Để show một phần tử UI lên màn hình, bạn phải sử dụng một lớp Displayable. Ví dụ, một lớp Displayable có thể có một title, một ticker, và các command liên kết với nó.
Lớp Display quản lý những cái gì hiển thị lên màn hình. Phương thức static getDisplay(MIDlet midlet) cho phép bạn truy cập vào Display của MIDlet. Sau đó, bạn sử dụng phương thức setCurrent(Displayable element) để lựa chọn đối tượng nào extend từ Displayable được hiển thị lên màn hình. Tại một thời điểm, chỉ có một đối tượng Displayable được hiển thị trên màn hình. Hãy xem lại ví dụ từ bài trước:

Display.getDisplay(this).setCurrent(alert);

Trong MIDP, nó phân chia các lớp thành 2 thành phần: các thành phần giao diện cấp cao(high-level interface component) và cấp thấp(low-level).
Các thành phần high-level được thực thi thông qua các lớp extends từ class Screen, còn các thành phần low-level được thực thi thông qua các lớp extends từ class Canvas. Và tất cả chúng đều extends từ class Displayable.
Bất kỳ một ứng dụng nào cũng có thể kết hợp cả các thành phần giao diện high-level và low-level để phục vụ cho mục đích của ứng dụng. Ví dụ, trong một ứng dụng game, List và Form có thể được sử dụng để chọn lựa hay cấu hình game, trong khi Canvas (hay GameCanvas) được sử dụng cho các thành phần tương tác của game như tạo nhân vật chuyển động, ảnh background.

Lớp Command:
Một MIDlet tương tác với người dùng thông qua các Command. Một Command tương đương với một menu item trong một ứng dụng thông thường, và nó chỉ có thể kết hợp với một phần tử UI Displayable. Lớp Displayable cho phép người dùng attacth một Command bằng cách sử dung phương thức addCommand(Command command). Một đối tượng Displayable có thể cón nhiều Command bên trong nó. Lớp Command được nắm giữ các thông tin về command. Thông tin này được đóng gói trong 4 thuộc tính: short label, optional long label, command type và priority. Ví dụ, sử dụng lớp Command để tạo ra đối tượng command thông qua cung cấp các giá trị trong Constructor:

// adds a command to exit the MIDlet 
comExit = new Command("Exit", Command.EXIT, 1); 

Lưu ý rằng các command không thay đổi khi chúng được tạo ra.

Nếu xác định command type, bạn có thể cho phép thiết bị chạy MIDlet ánh xạ(map) bất kỳ phím định sẵn nào trên thiết bị vào command đó. Ví dụ, một command với kiểu OK được ánh xạ vào phím OK của thiết bị. Trong MIDP 2.0, nó có sẵn các kiểu command sau: BACK, CANCEL, EXIT, HELP, ITEM, SCREEN và STOP. Kiểu SCREEN liên quan đến một command được map trong ứng dụng cho màn hiền hiện thời. Cả SCREEN và ITEM không có bất kỳ các phím được ánh xạ trên thiết bị. Để nhận được phản hồi từ người dùng, bạn cần phải listen từ các command, điều này được thực hiện thông qua thực thi giao diện CommandListener. Trong ví dụ Hello World, Interface được thực thi thông qua phương thức commandAction().
Ví dụ:

alert.addCommand(comExit);
// adds a listener to the form  
alert.setCommandListener(this);
[...]
public void commandAction(Command cmd, Displayable display) {
  if (cmd == comExit) {
    exit();
  }             
}

Như bạn thấy, phương thức commandAction() nhận 2 tham số: đó là Command được thực thi và Displayable đang được hiển thị hiện tại.

Giao diện người dùng cấp cao(High-level User Interface):
Các API của giao diện người dùng cấp cao được thiết kế cho các ứng dụng kinh doanh của các khách hàng mà các thành phần client của nó chạy trên các thiết bị di động. Đối với các loại ứng dụng này, tính linh động(portability) qua nhiều thiết bị là rất quan trọng. Để đạt được tính linh động như vậy, các API của nó ở mức cao được trừu tượng hóa(abstraction) và cung cấp ít các điều khiển hơn các look anh feel. Điều này cho phép thiết bị sử dụng look and feel giao diên người dùng tự nhiên(native) để hiển thị thay thế cho các thành phần giao diện MIDP high-level. Điều này có nghĩa khi một môt ứng dụng được viết bằng API high-level, nó look and feel một cách tự động sử dụng look and feel của thiết bị mà ứng dụng đang chạy, còn đối với người dùng cuối, điều này cung cấp sự tương tác với người dùng một cách liền mạch, đó là ứng dụng MIDP làm việc như là các ứng dụng native trên thiết bị.
Tóm lại, khi sử dụng API high-level, bạn có thể:

  • Vẽ để được hiển thị bởi hệ thống phần mềm của thiết bị. Ứng dụng không định nghĩa giao diện trực quan như hình dáng, màu sắc… của các thành phần high-level.
  • Điều hướng, cuộn, và các tương tác nguyên thủy khác với các thành phần giao diện người dùng được thực hiện bởi thiết bị. Tuy nhiên, ứng dụng không nhận biết được các tương tác này.
  • Ứng dụng không thể truy cập vào các kỹ thuật input cụ thể, như các phím nhấn cụ thể nào đó.

Alert:
Ứng dụng Hello World sử dụng một alert. Phần tử này đại diện cho một màn hình(Screen) dùng để show dữ liệu đến người dùng và đợi một thời gian trước khi xử lý đối tượng Displayable tiếp theo. Một Alert thì có thể chứa môt chuỗi text và một image. Thông thường, Alert được sử dụng để thông báo lỗi hay các ngoại lệ khác.

TextBox:
Kế thừa từ lớp Screen, cho phép người dùng nhập và chỉnh sửa text. Lớp này có thể được cấu hình để thích nghi với các nhu cầu của bạn. Bạn có thể giới hạn maximum các ký tự hiển thị trong TextBox. Ngoài ra, bạn có thể ràng buộc các kiểu nhập cho TextBox bằng cách sử dụng các flag được định nghĩa trong lớp TextField. Có 6 ràng buộc(constrain) để giới hạn nội dung hiển thị, đó là: ANY, EMAILADRR, NUMBERIC, PHONENUMBER, URL và DECIMAL. Có 6 ràng buộc ảnh hưởng tới kiểu nhập: PASSWORD, UNEDITABLE, SENSITIVE, NON_PREDICTIVE, INITIAL_CAPS_WORD, và INITIAL_CAPS_SENSITIVE. Ví dụ, chỉ cho phép địa chỉ email được phép nhập trong TextBox, bạn thiết lập flag TextField.EMAILADRR sử dụng phương thức setConstrains(), ngoài ra để kết hợp nhiều ràng buộc cùng lúc, bạn sử dụng toán tử OR giữa các flag. Ví dụ:
setConstraints(TextField.EMAILADDR | TextField.UNEDITABLE);

List:
Một List chứa một danh sách các chọn lựa. Khi List được biểu diễn trên màn hình, người dùng có thể tương tác với nó bằng cách chon lựa các phần tử, di chuyển qua lại giữa các phần tử của List. Nó có các kiểu cấu hình sau:

  • Choice.EXCLUSIVE: chỉ có 1 phần tử được chọn lựa.
  • Choice.MULTIPLE: có thể có nhiều phần tử được chọn lựa.
  • Choice.IMPLICIT: phần tử được hightlight được chọn lựa.

Form:
Nó chứa nhiều item, bất kỳ lớp nào extends từ lớp Item để có thể được chứa trong một Form. Việc thực thi xử lý các layout, traversal, và scrolling. Nội dung của Form có thể được cuộn lại với nhau.
Các loại Item có thể được thêm vào trong Form:

StringItem: là một label và không cho phép người dùng sửa lên nó. Item này có thể chứa một tiêu đề và một text, và cả hai đều có thể null.

DateField: cho phép người dùng nhập ngày date/time một trong 3 dạng sau: DATE, TIME và DATE_TIME.

TextField: Tương tự như TextBox.

ChoiceGroup: Tương tự như List.

Gauge: sử dụng để mô phỏng process bar, tuy nhiên nó có thể sử dụng trong kiểu tương tác bởi người dùng, ví dụ nếu bạn muốn dùng nó để show một Volume Control.

ImageItem: Nắm giữ một image.

CustomItem: là một lớp ảo abstract cho phép các subclass tạo ra giao diện riêng, tương tác riêng và cơ chế thông báo riêng của nó. Nếu bạn muốn một phần tử UI khác so với các phần tử được cung cấp thì bạn có thể tạo ra subclass.

Giao diện người dùng cấp thấp(Low-level User Interface):
Các API của low-level user interface( như lớp Canvas) được thiết kế cho các ứng dụng cần sự sắp đặt và điều khiển các phần tử graphics một cách chính xác cũng như truy cập vào các low-level input event. Ví dụ điển hình là game board, một chart object hay một graph. Sử dụng low-level user interface, một ứng dụng có thể:

  • Kiểm soát những gì được vẽ trên màn hình.
  • Điều khiển được các sự kiện primitive như các sự kiện nhấn phím(key press) và nhả phím(key release)
  • Truy cập vào cụ thể từng phím và các thiệt bị đầu vào khác.

Ngoài ra, trong MIDP 2.0 còn cung cấp javax.microediton.lcdui.game. Gói này bao gồm 5 class dùng để thiết kế cho các game, đó là : GameCanvas, LayerManger, Layer, Sprite và TiledLayer. Bạn có thể tìm hiểu phần này sau.
Ví dụ về giao diện người dùng:
Đối với mỗi màn hình game, chúng ta tạo một phương thức init[ScreenName] để khởi tạo màn hình và trả về đối tượng Displayable được tạo ra:
Đối với Main Menu, bạn sử dụng component List để biểu diễn main options. Ví dụ:

public Displayable initMainForm() { 
if (mainForm == null) {
    // creates a implicit List where the current element is
    // the selected
    mainForm = new List("Menu",   List.IMPLICIT);
    // append list options
    mainForm.append("New Game",   null);
    mainForm.append("Options",   null);
    mainForm.append("Scores",   null);
    mainForm.append("Help", null);
    mainForm.append("Exit", null);
    // adds a select Command
    comSelect = new   Command("Select", Command.ITEM, 1);
    mainForm.setSelectCommand(comSelect);
    // adds a listener to the form 
    mainForm.setCommandListener(this);
  }
    return mainForm; 
}

Đối với Menu Settings, bạn chọn phần tử Form, và thêm một đối tượng ChoiceGroup để tùy chỉnh âm thanh:

public Displayable initSettingsForm() {
  // check if already created
  if (settingsForm == null) {
    settingsForm = new Form("Settings");
    settingsForm.addCommand(initBackCommand());
    settingsForm.setCommandListener(this);
    // creates a choice Group for sound options
    soundChoice = new ChoiceGroup("Sound", List.EXCLUSIVE);     
    soundChoice.append("On", null);
    soundChoice.append("Off", null);
    // appends the choice to the form
    settingsForm.append(soundChoice);     
  }
  return settingsForm;
}

Đối với Help Screen, bạn chọn phần tử Form với static message:

public Displayable initHelpForm() {
    if (helpForm == null) {
      helpForm = new Form("Help");
      helpForm
          .append("User cursors to move your pad, don't let "+
              "the ball go by you, hit all the bricks!");
      helpForm.setCommandListener(this);
      helpForm.addCommand(initBackCommand());
    }
    return helpForm;
  }

Để giới thiệu High Score, bạn sử dụng một Form với các item của nó là TextField và DateField:

public Displayable initNewHighScore(int score, int pos) {
    if (newHighScoreForm == null) {
      newHighScoreForm = new Form("New High Score");
      newHighScoreForm.setCommandListener(this);
      // create items
      highScoreName = new TextField("Name", "", 20, TextField.ANY);
      highScoreValue = new StringItem("Score", Integer.toString(score));
      highScorePosition = new StringItem("Position", Integer.toString(pos));
      // create save command
      highScoreSave = new Command("Save", Command.OK, 1);     
      // append command and itens to screen
      newHighScoreForm.addCommand(highScoreSave);
      newHighScoreForm.append(highScoreName);
      newHighScoreForm.append(highScoreValue);
      newHighScoreForm.append(highScorePosition);
    }
    // update score
    highScoreValue.setText(Integer.toString(score));
    // update pos
    highScorePosition.setText(Integer.toString(pos)+1);
    return newHighScoreForm;
  }

Màn hình game sẽ được đề cập trong bài tiếp theo. Bây giờ, hãy tạo một phương thức giả lập kết thúc game và sử dụng nó để thay thế:

public void endGame(int lifes, int score, int time) {
    Displayable nextScreen = initMainForm();
    String message;
    if (lifes == 0) {
      message = "Game Over!!";
    } else {
      message = "You Win!";
    }
    int pos = isHighScore(score);
    if (pos != -1) {
      nextScreen = initNewHighScore(score, pos);
    } 
    display(new Alert(message, message, null, AlertType.INFO), nextScreen);
  }

Bây giờ, tất cả các phương thức đã được tạo ra, bạn link chúng đến phương thức commandAction(). Rewrite code:

public void commandAction(Command cmd, Displayable display) {
  // check what screen is being displayed
  if (display == mainForm) {
    // check what command was used
    if (cmd == comSelect) {
      switch (mainForm.getSelectedIndex()) {
      case (0):
        // At the moment just go directly to the end of the game
        endGame(1, 200, 50);
        break;
      case (1):
        display(initSettingsForm());
        break;
      case (2):
        display(initScoreForm());
        break;
      case (3):
        display(initHelpForm());
        break;
      case (4):
        exit();
        break;
      }
    }
  } else if (display == highScoreForm) {
    if (cmd == comBack) {
      display(initMainForm());
    }
  } else if (display == settingsForm) {
    if (cmd == comBack) {
      soundOn = soundChoice.getSelectedIndex() == 0;
      display(initMainForm());
    }
  } else if (display == helpForm) {
    if (cmd == comBack) {
      display(initMainForm());
    }
  } else if (display == newHighScoreForm) {
      if (cmd == highScoreSave) {
        int pos = Integer.parseInt(highScorePosition.getText())-1;
        // advance all the scores
        for ( int i = scores.length-1; i > pos ; i--){
          scores[i].name  = scores[i-1].name;
          scores[i].value = scores[i-1].value;
          scores[i].when  = scores[i-1].when;
        }
        // insert new score
        scores[pos].name = highScoreName.getString();
        scores[pos].value = Integer.parseInt(highScoreValue.getText());
        scores[pos].when = new Date();       
        display(initScoreForm());
      }
    }
}

Tất cả các logic menu cho các MIDlet được xác định bên trong phương thức commandAction() này. Quyết định làm gì tiếp theo phụ thuộc vào màn hình hiển thị và command được chọn lựa. Từ menu chính, tôi chỉ đơn giãn chuyển hướng người dùng đến mỗi màn hình cụ thể. Các màn hình hiện tại chỉ có một back command, chỉ duy nhất một form NewHighScores có command save dùng để lưu thông tin về điểm số(score).
Bạn lưu ý cách thức sử dụng phương thức display(), vì đây là cách đơn giãn để kích hoạt đối tượng Displayable.

public void display(Displayable display) {
  // shows display  in the screen.
  Display.getDisplay(this).setCurrent(display);
}

Bài tiếp theo sẽ mô tả làm thế nào để cài đặt một Game Screen.
Code

Các bài liên quan:
Phát triển game đơn giãn trên Mobile(P1)
Phát triển game đơn giãn trên Mobile(P3)
Phát triển game đơn giãn trên Mobile(P4)
Phát triển game đơn giãn trên Mobile(P5)
Phát triển game đơn giãn trên Mobile(P6)
Phát triển game đơn giãn trên Mobile(P7)