Thứ Bảy, 2 tháng 2, 2013

Bài 2 – Giao diện người dùng với MIDP 2.0



Đây là bài 2 trong loạt bài đầu tiên nói về khám phá J2ME với MIDP 2.0. Ở bài 1, tôi đã cho bạn thấy cách lấy về, cài đặt và sử dụng Wireless Toolkit khi phát triển các MIDlet. Bài 1 cũng cho biết cách phát triển MIDlet mà không sử dụng Toolkit, để bạn hiểu được tầm quan trọng đằng sau những hoạt động liên quan đến việc tạo một MIDlet. Phần cuối bài 1 là một khám phá về vòng đời của MIDlet.
Trong bài 2 này, bạn sẽ tạo ra các thành phần giao diện người dùng cho một ứng dụng J2ME. Vì tương tác với người dùng là chuyện muôn thuở với bất kỳ MIDlet nào. Đối với kích thước màn hình là rất quan trọng để bạn hiểu được những cơ bản nhất của MIDlet. Mọi tương tác người dùng đều được thực hiện thông qua một thành phần UI. Trên thực tế, khi bạn tạo một ứng dụng đơn giản như DateTimeApp, người dùng chỉ dùng một thành phần có tên là Alert để hiển thị ra một thông báo alert ra màn hình. Thông điệp này được sự được đưa ra màn hình bởi sự trợ giúp của  một thành phần khác có tên là Display.
1.Kiến trúc giao diện người dùng
MIDP 2.0 cung cấp các lớp UI trong các gói javax.microedition.lcdui  javax.microedition.lcdui.game. Chữlcdui có nghĩa là liquid crystal display user interface (giao diện người dùng màn hình tinh thể lỏng, ý nói đây là gói thư viện cho UI trên màn hình LCD). Còn gói game chứa các lớp liên quan đến việc phát triển một game UI. Tôi sẽ thảo luận về gói game này trong bài 3.
Các lớp trong gói javax.microedition.lcdui có thể được chia ra thành 2 nhóm: nhóm API cấp thấp và nhóm API cấp cao. Các lớp thuộc nhóm cấp cao hoàn hảo cho phát triển các MIDlet nhắm đến tối đa số lượng các thiết bị, bởi vì những lớp này không cung cấp đầy đủ chính xác việc kiểm soát qua hiển thị của chúng. Những lớp này bao gồm:
Hình 1 – Các lớp UI cấp cao
Những lớp thuộc nhóm cấp thấp thích hợp cho MIDlet mà ta phải kiểm soát chính xác theo vị trí và hiển thị của các thành phần. Dĩ nhiên có nhiều kiểm soát hơn thì ít linh động hơn. Nếu MIDlet của bạn được phát triển bằng những lớp cấp thấp này, thì nó chỉ có thể triển khai được trên vài thiết bị mà thôi, do chúng yêu cầu chính xác việc kiểm soát qua hiển thị look and feel. Chỉ có 2 lớp trong nhóm này.
Hình 2 – Các lớp UI cấp thấp
Có một lớp khác trong nhóm cấp thấp là GameCanvas, nhưng không đưa ra ở đây mà sẽ nói ở bài 3.
Do bạn có thể trình bày một thành phần UI lên một màn hình thiết bị, cho dù là cấp cao hay cấp thấp, đều phải cài đặt giao diện Displayable. Một lớp displayable có thể là một tiêu đề, một ticker, hay một lệnh nào đó được kết hợp với nó. Khi cài đặt giao diện này thì cũng đồng thời bao hàm cả 2 lớp Screen, Canvas và các lớp con của chúng cũng cài đặt giao diện này. Lớp Graphics không cài đặt giao diện này, do nó xử lý với đồ họa 2D trực tiếp.
Hình 3 – Canvas và Screen cài đặt giao diện Displayable
Một lớp Displayable là một phần tử UI có thể được thể hiện ra trên màn hình của thiết bị trong khi lớp Displaytrừu tượng chức năng hiển thị của một màn hình thiết bị thực tế. Lớp Displayable cung cấp các phương thức lấy về thông tin màn hình và đưa ra hay thay đổi phần tử UI hiện hành mà bạn muốn được hiển thị. Vì thế, một MIDlet trình bày một phần tử UI Displayable trên một Display bằng việc sử dụng phương thứcsetCurrent(Displayable current) của lớp Display.
Như gợi ý trong tên của phương thức, Display có thể chỉ có một phần tử Displayable mỗi lúc, và phần tử đó trở thành phần tử hiện hành trong hiển thị. Phần tử hiện hành đang được hiển thị có thể được truy cập bằng cách sử dụng phương thức getCurrent(), sẽ trả về một thể hiện của phần tử Displayable. Phương thức tĩnhgetDisplay(MIDlet midlet) trả về thể hiện hiển thị hiện hành được kết hợp với phương thức MIDlet của bạn. Đoạn mã nhỏ sau đây sẽ giúp hiểu được các khái niệm MIDlet ta vừa đọc. Hãy chỉnh lại ứng dụng DateTimeApp như sau:
package huetoday.j2me.part1;
import java.util.Date;
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;

public class DateTimeApp extends MIDlet {

  Alert timeAlert;

  public DateTimeApp() {
    timeAlert = new Alert("Tieu de cua Alert!");
    timeAlert.setString(new Date().toString());
  }

  public void startApp() {
    Display.getDisplay(this).setCurrent(timeAlert);
  }

  public void pauseApp() {
  }

  public void destroyApp(boolean unconditional) {
  }
}
Code 1 – DateTimeApp MIDlet
Một phần tử UI Displayable gọi là Alert được tạo ra trong phương thức dựng. Khi AMS gọi phương thứcstartApp(), hiển thị hiện hành hiện hữu cho MIDlet này được lấy ra bằng việc sử dụng phương thứcDisplay.getDisplay(). Sau đó Alert sẽ trở thành phần tử hiện hành trong hiển thị, bằng cách thiết lập nó là tham số vào phương thức setCurrent().
Như trong hình 1, có 4 phần tử UI cấp cao có thể được hiển thị lên màn hình của MIDlet. Giờ ta sẽ nghiên cứu chi tiết từng phần tử một.
2.Đối tượng Alert
Bạn đã biết cách tạo một thông điêp cảnh báo đơn trong đoạn mã 1. Alert được dùng để thông báo hay đưa ra một thông điệp lỗi xuất hiện trên màn hình trong một khoảng thời gian ngắn và sau đó biến mất. Bạn có thể điều khiển vài cách hiển thị của Alert bằng cách gọi các phương thức phù hợp hay sử dụng đúng phương thức dựng của nó.
- Tiêu đề phải được thiết lập trong khi tạo alert và tiêu đề không thể thay đổi được về sau: Alert(“Tieu de cua Alert!”);
- Để thiết lập thông điệp hiển thị cho alert, dùng phương thức setString(“Thong diep hien thi.”) hoặc chuyển thông điệp vào phương thức dựng: Alert(“Tieu de cua ban!”, “Thong diep hien thi.”, null, null);
- Sử dụng phương thức setTimeout(int time) để thiết lập thời gian (tính theo ms) hiển thị thông điệp alert lên màn hình. Nếu bạn chuyển giá trị time là Alert.FOREVER, thì thông điệp sẽ xuất hiện mãi mãi.
- Có 5 kiểu alert được định nghĩa bởi lớp AlertType: ALARM, CONFIRMATION, ERROR, INFO, và WARNING. Mỗi loại alert này có kiểu dáng và âm thanh báo động khác nhau. – Kết hợp một ảnh với alert dùng phương thứcsetImage(Image img);
- Thiết lập một indicator cho alert dùng phương thức setIndicator(Gauge gauge);
3.Đối tượng List
Một List chứa một hay nhiều (phần tử) lựa chọn, và phải có một phần văn bản, một ảnh tùy chọn, và một font tùy chọn cho phần văn bản. List cài đặt giao diện Choice, là giao diện xác định các thao tác cơ bản list. List phải có một tiêu đề, và phải xác định một chính sách cho việc lựa chọn các phần tử của list. Chính sách này có thể là cho phép chọn chỉ một phần tử (Choice.EXCLUSIVE), cho phép lựa chọn nhiều phần tử (Choice.MULTIPLE), hoặc chọn phần tử được tô sáng hiện hành (Choice.IMPLICIT). Hình 4 cho thấy sự khác nhau của 3 kiểu lựa chọn này.
Hình 4 – Các chính sách lựa chọn cho các phần tử List
Bạn có thể tạo một list theo 2 cách:
- Tạo một list không chứa bất kỳ phần tử nào cả, và sau đó nối thêm hay chèn các phần tử một cách độc lập.
- Tạo các phần tử trước và sau đó tạo một list với những phần tử này.
Code 2 cho thấy cả 2 cách này:
package huetoday.j2me.part2;

import javax.microedition.lcdui.List;
import javax.microedition.lcdui.Choice;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;

public class ListExample extends MIDlet {

   List fruitList1;
   List fruitList2;

   public ListExample() {
      fruitList1 = new List("Select the fruits you like",
                   Choice.MULTIPLE);
      fruitList1.append("Orange", null);
      fruitList1.append("Apple", null);
      fruitList1.insert(1, "Mango", null);
      // inserts between Orange and Apple

      String fruits[] = {"Guava", "Berry", "Kiwifruit"};
      fruitList2 = new List("Select the fruits you like - List 2",
                   Choice.IMPLICIT, fruits, null);
   }

   public void startApp() {
      Display display = Display.getDisplay(this);
      display.setCurrent(fruitList1);

      try{
  Thread.currentThread().sleep(3000);
      } catch(Exception e) {}

      display.setCurrent(fruitList2);
   }

   public void pauseApp() {
   }

   public void destroyApp(boolean unconditional) {
   }
}
Code 2 – Ví dụ về List
Các phần tử List có thể được thay đổi sau khi list được tạo ra. Bạn có thể thay đổi các thành phần này một cách riêng rẽ bằng việc thay đổi văn bản, font  hay ảnh của chúng theo chỉ mục list (tính từ 0). Bạn có thể xóa các phần tử trong list bằng phương thức delete(int index) hoặc deleteAll(). Mọi thay đổi đều tác động đến list ngay lập tức, dù nếu list là thành phần UI hiện hành đang được trình bày trên màn hình.
4.Đối tượng Text Box
Văn bản được nhập vào bởi người dùng sử dụng một textbox. Cũng như các phần tử UI khác, một textbox cũng có một số tính năng đơn giản và có thể được thiết lập theo yêu cầu của bạn. Bạn có thể giới hạn số ký tự tối đa cho phép nhập vào, nhưng bạn cần thận trọng vì giá trị này phụ thuộc vào thiết bị bạn đang có. Chẳng hạn, giả sử bạn yêu cầu một textbox chỉ được phép nhập tối đa 50 ký tự với phương thức setMaxSize(50), nhưng thiết bị chỉ cấp phát tối đa 30 ký tự. Và do đó, chỉ có thể nhập được 32 ký tự.
Bạn cũng có thể ràng buộc việc thay đổi văn bản trong textbox với một số cờ được định nghĩa trong lớpTextField. Ví dụ, để chỉ cho phép nhập địa chỉ email, bạn sẽ cần thiết lập cờ TextField.EMAILADDR trong phương thức setConstraints(). Để không cho phép chỉnh sửa textbox, bạn dùng cờ TextField.UNEDITABLE. Cả hai ràng buộc này có thể thực hiện bằng cách dùng toán tử so sánh bit OR, như sau:setConstraints(TextField.EMAILADDR | TextField.UNEDITABLE);
Có 6 ràng buộc cho kiểu nội dung cụ thể của textbox được phép nhập là: ANY, EMAILADDR, NUMERIC, PHONENUMBER, URL, và DECIMAL_ANY. Tương tự, có 6 ràng buộc cho kiểu hiển thị: PASSWORD, UNEDITABLE, SENSITIVE, NON_PREDICTIVE, INITIAL_CAPS_WORD, và INITIAL_CAPS_SENTENCE. Chú ý là không phải tất cả những thiết lập này đều chạy trên tất cả các thiết bị.
Để thiết lập nội dung cho textbox bạn có thể sử dụng cặp phương thức sau. Dùng setString(String text) để thiết lập nội dung với một giá trị String, dùng insert(String text, int position) để để chèn String vào vị trí bạn muốn. Đoạn mã sau cho thấy cách sử dụng 2 phương thức này, cùng với một số ràng buộc.
package huetoday.j2me.part2;

import javax.microedition.lcdui.TextBox;
import javax.microedition.lcdui.TextField;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;

public class TextBoxExample extends MIDlet {

   private TextBox txtBox1;
   private TextBox txtBox2;

   public TextBoxExample() {
      txtBox1 = new TextBox(
                "Your Name?", "", 50, TextField.ANY);
      txtBox2 = new TextBox(
                "Your PIN?", "", 4,
                TextField.NUMERIC | TextField.PASSWORD);
   }

   public void startApp() {
      Display display = Display.getDisplay(this);
      display.setCurrent(txtBox1);

      try{
         Thread.currentThread()Sleep(5000);
      } catch(Exception e) {}

      txtBox1.setString("Bertice Boman");

      try{
         Thread.currentThread()Sleep(3000);
      } catch(Exception e) {}

      // inserts 'w' at the 10th index to make the
      // name Bertice Bowman
      txtBox1.insert("w", 10);

      try{
         Thread.currentThread()Sleep(3000);
      } catch(Exception e) {}

      display.setCurrent(txtBox2);
   }

   public void pauseApp() {
   } 

   public void destroyApp(boolean unconditional) {
   }
}
Code 3 – Sử dụng Textbox
5.Đối tượng Form
Một Form là một tập hợp các thể hiện của giao diện Item. Lớp Textbox là một thành phần UI độc lập, cònTextField là một thể hiện Item. Về bản chất, một Textbox có thể được trình bày trên màn hình thiết bị mà không cần một Form, nhưng một trường textfield yêu cầu một Form. Một item được thêm vào Form sử dụng phương thức append(Item item), thêm một item mới vào phía dưới Form và gán cho item đó một index nhằm thể hiện vị trí của item đó trên Form. Item đầu tiên có index là 0. Bạn cũng có thể sử dụng phương thức insert(int index, Item newItem) để chèn mới một item vào một vị trí cụ thể hoặc sử dụng phương thức set(int index, Item newItem) để thay thế một item tại một vị trí cụ thể cho bởi index.
Có 8 kiểu Item khả dụng trên Form:
StringItem: Một nhãn không thể thay đổi bởi người dùng. Item này có thể chứa tiêu đề văn bản, cả hai nội dung này đều có thể là null đóng vai trò là chỗ chứa – placeholder. Lớp Form cung cấp một cách viết ngắn gọn khi thêm một StringItem cùng tiêu đề rỗng: append(String text) – DateField: Cho phép người dùng nhập vào ngày tháng/thời gian theo 3 định dạng: DATETIME, hoặc DATE_TIME – TextField: Tương tự như Textbox
ChoiceGroup: Tương tự như List
Spacer:  Dùng để định vị trí cho các thành phần UI bằng cách đặt một số khoảng trống giữa chúng. Thành phần này không thể nhìn thấy được.
Gauge: Một gauge được dùng để mô phỏng một thanh tiến trình. Tuy nhiên, gauge có thể được dùng trong chế độ tương tác với người dùng, như khi trình bày một bộ chỉnh âm lượng.
ImageItem: Một item chứa một ảnh! Cũng như StringItem, lớp Form cung cấp một phương thức rút gọn cho việc thêm một ảnh mới: append(Image image).
CustomItem: CustomItem là một lớp trừu tượng cho phép việc tạo ra các lớp con có các thuộc tính của riêng chúng, tương tác của riêng chúng, và cơ chế thông báo của riêng chúng. Nếu bạn yêu cầu một thành phần UI khác với thành phần được cung cấp sẵn, bạn có thể tạo ra lớp con của CustomItem rồi thêm vào Form. Những Item này này (ngoại trừ CustomItem) có thể thấy ở hình 5, và tương ứng với đoạn mã Code 4 sau. (Chú ý: file ảnh duke.gif nên để trong thư mục res của ứng dụng MIDlet) .
package huetoday.j2me.part2;

import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Gauge;
import javax.microedition.lcdui.Spacer;
import javax.microedition.lcdui.ImageItem;
import javax.microedition.lcdui.TextField;
import javax.microedition.lcdui.DateField;
import javax.microedition.lcdui.StringItem;
import javax.microedition.lcdui.ChoiceGroup;

import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Choice;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;

public class FormExample extends MIDlet {

   private Form form;
   private Gauge gauge;
   private Spacer spacer;
   private ImageItem imageItem;
   private TextField txtField;
   private DateField dateField;
   private StringItem stringItem;
   private ChoiceGroup choiceGroup;

   public FormExample() {
      form = new Form("Your Details");

      // a StringItem is not editable
      stringItem = new StringItem("Your Id: ", "WXP-890");
      form.append(stringItem);

      // you can accept Date, Time or DateTime formats
      dateField = new DateField("Your DOB: ", DateField.DATE);
      form.append(dateField);

      // similar to using a TextBox
      txtField = new TextField("Your Name: ", "", 50, TextField.ANY);
      form.append(txtField);

      // similar to using a List
      choiceGroup = new ChoiceGroup(
                    "Your meals: ", Choice.EXCLUSIVE,
      new String[] {"Veg", "Non-Veg"}, null);
      form.append(choiceGroup);

      // put some space between the items to segregate
      spacer = new Spacer(20, 20);
      form.append(spacer);

      // a gauge is used to show progress
      gauge = new Gauge("Step 1 of 3", false, 3, 1);
      form.append(gauge);

      // an image may not be found,
      // therefore the Exception must be handled
      // or ignored
      try {
         imageItem = new ImageItem(
         "Developed By: ",
  Image.createImage("/duke.gif"),
  ImageItem.LAYOUT_DEFAULT,
  "DuKe");
  form.append(imageItem);
      } catch(Exception e) {}
   }

   public void startApp() {
      Display display = Display.getDisplay(this);
      display.setCurrent(form);
   }

   public void pauseApp() {
   }

   public void destroyApp(boolean unconditional) {
   }
}
Code 4 – Sử dụng  Form
Hình 5 – Các thành phần của một Form
6.Đối tượng Image, Ticker, Gauge
Sử dụng các đối tượng như Image, Ticker, và Gauge làm các thành phần UI trên MIDlet khá là dễ. Trong đóGauge là một thành phần chỉ có thể được hiển thị trên Form nhằm thông báo tiến trình hay điều khiển một tính năng MDlet (như âm lượng). Một Ticker, có thể được gắn vào mọi thành phần UI có kế thừa lớp trừu tượngDisplayable, có nhiệm vụ chạy một đoạn văn bản qua màn hình. Một Image (ảnh) có thể được dùng với nhiều thành phần UI, bào gồm cả với Form, như thấy ở mục trước.
Vì Ticker có thể được dùng với tất cả các thành phần displayable, cung cấp một phương thức thuận tiện để hiển thị thông tin về thành phần hiện hành lên màn hình. Lớp Displayable cung cấp phương thức setTicker(Ticker ticker), và tự Ticker có thể được tạo ra bằng phương thức dựng Tiker(String msg), với thông điệp msg mà bạn muốn hiển thị. Bằng cách sử dụng phương thức setString(String MSG), bạn có thể thay đổi thông điệp này, và thay đổi có hiệu lực ngay lập tức. Chẳng hạn, Form được dùng ở mục 5 có thể có ticker của riêng nó được hiển thị bằng cách thiết lập form.setTicker(new Ticker(“Welcome to Vanadalay Industries!!!”)). Kết quả sẽ cho ra một ticker đi qua phía trên màn hình khi người dùng đang điền thông tin vào Form. Xem hình 6.
Hình 6 – Thiết lập một Ticker trên một đối tượng kế thừa lớp trừu tượng Displayable
Ở mục 5, ta đã thấy một ví đụ Gauge trong chế độ không tương tác. Gauge thể hiện một tiến trình hoàn thành Form. Một Gauge không tương tác có thể được dùng để thể hiện tiến trình của một tác vụ nào đó; ví dụ, khi thiết bị có thể đang thử tạo một kết nối hay đọc một kho dữ liệu, hoặc khi người dùng thực hiện một Form. Ở mục trước, ta đã tạo một Gauge bằng cách chỉ rõ 4 giá trị. Nhãn (“Step 1 of 3″), chế độ tương tác (false), giá trị tối đa (3), và giá trị khởi tạo (1). Tuy nhiên, khi mà bạn không biết một hoạt động mất bao lâu để thực hiện xong, bạn có thể sử dụng giá trị tối đa là vô hạn INDEFINITE.
Một Gauge ở chế độ không tương tác có một giá trị tối đa INDEFINITE thu được nhiều ý nghĩa đặc biệt. (Bạn không thể tạo ra một Gauge tương tác với một giá trị INDEFINITE). Kiểu Gauge có 1 trong 4 trạng thái, được phản ánh bằng giá trị khởi tạo (cũng là giá trị hiện hành của Gauge). Các trạng thái này là: CONTINUOUS_IDLE, INCREMENTAL_IDLE, CONTINUOUS_RUNNING, và INCREMENTAL_UPDATING. Mỗi trạng thái thể hiện kết quả tốt nhất của thiết bị khi cho người dùng biết hoạt động hiện hành của MIDlet, bạn có thể sử dụng chúng để thể hiện những trạng thái của riêng bạn. Code 5 cho thấy một ví dụ sử dụng những trạng thái Gauge không-tương tác này, cùng với một ví dụ một Gauge tương tác. Nhớ rằng một Gauge là một UI Item, và do đó, chỉ có thể được hiển thị như là một phần của Form.
package huetoday.j2me.part2;

import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Gauge;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;

public class GaugeExample extends MIDlet {

 private Form form;
 private Gauge niIndefinate_CI;
 private Gauge niIndefinate_II;
 private Gauge niIndefinate_CR;
 private Gauge niIndefinate_IU;

 private Gauge interactive;

 public GaugeExample() {
  form = new Form("Gauge Examples");

  niIndefinate_CI =
    new Gauge(
    "NI - Cont Idle",
    false,
    Gauge.INDEFINITE,
    Gauge.CONTINUOUS_IDLE);
  form.append(niIndefinate_CI);

  niIndefinate_II =
    new Gauge(
    "NI - Inc Idle",
    false,
    Gauge.INDEFINITE,
    Gauge.INCREMENTAL_IDLE);
  form.append(niIndefinate_II);

  niIndefinate_CR =
    new Gauge(
    "NI - Cont Run",
    false,
    Gauge.INDEFINITE,
    Gauge.CONTINUOUS_RUNNING);
  form.append(niIndefinate_CR);

  niIndefinate_IU =
    new Gauge(
    "NI - Inc Upd",
    false,
    Gauge.INDEFINITE,
    Gauge.INCREMENTAL_UPDATING);
  form.append(niIndefinate_IU);

  interactive =
    new Gauge(
    "Interactive ",
    true,
    10,
    0);
  form.append(interactive);

 }

 public void startApp() {
  Display display = Display.getDisplay(this);
  display.setCurrent(form);
 }

 public void pauseApp() {
 }

 public void destroyApp(boolean unconditional) {
 }
}
Code 5 – Sử dụng Gauge
Mỗi thiết bị di động sẽ sử dụng tập ảnh của riêng nó để thể hiện Gauge, do đó các Gauge sẽ khác nhau trên các thiết bị đó và kể cả với bộ mô phỏng trong Toolkit.
Bạn cũng có thể kết hợp một image với một thành phần UI như là Alert hay Choice. Khi một ảnh được tạo ra, hoặc là bằng cách đọc từ một vị trí vật lý hoặc bằng cách tạo ra một ảnh trong bộ nhớ, và ảnh chỉ nằm trong bộ nhớ, không hiện lên màn hình. Bạn nên cẩn thận khi sử dụng các ảnh này, và nên hạn chế kích thước ảnh phù hợp với khả năng nhỏ nhất để tránh lưu ảnh vượt quá bộ nhớ giới hạn hiện có.
Lớp Image cung cấp một số phương thức tĩnh để tạo ra hay lấy được các ảnh dùng cho MIDlet. Một ảnh được rạo ra trong bộ nhớ bằng cách sử dụng phương thức createImage(int width, int height). Một image được tạo ra bằng cách này ban đầu có các pixel đều là màu trắng, và bạn có thể lấy được một đối tượng đồ hòa trên image này bằng phương thức getGraphics() nhằm thay đổi cách nó được biểu hiện trên màn hình. Một số các đối tượng Graphics có thể tìm hiểu thêm ở mục API cấp thấp.
Để thu được ảnh không thể được thay đổi, bạn có thể sử dụng một trong 2 phương thức: createImage(String imageName) hoặc createImage(InputStream stream). Phương thức đầu tiên được dùng để tìm kiếm một ảnh trong gói jar, còn phương thức thứ 2 là cho việc đọc một image qua một mạng. Để tạo một ảnh không thể được thay đổi từ dữ liệu trong bộ nhớ, bạn có thể sử dụng phương thức createImage(byte[] imageData, int imageOffset, int imageLength) hoặc createImage(Image source). Phương thức đầu tiên cho phép bạn tổ chức một ảnh từ một mảng các byte, còn phương thức thứ hai cho phép bạn tạo một image từ một image đang tồn tại.
Chú ý các quy ước đặc tả MIDlet hỗ trợ định dạng ảnh PNG. Vì thế, tất cả các thiết bị hỗ trợ MIDlet đều sẽ hiển thị một ảnh PNG. Những thiết bị khác có thể hỗ trợ thêm một số định dạng khác như GIF, JPEG, nhưng không đảm bảo.
Bạn đã được xem một ví dụ cách lấy về một image từ mục Form. Ở code 4, một image được nằm trong một lớpImageItem để nó có thể được hiển thị lên một Form. Image này được giữ trong thư mục res của MIDlet và trình mô phỏng có thể tự động tìm thấy nó. Phương thức createImage(String imageName) sử dụng phương thứcClass.getResourceAsStream(String imageName) mới thực sự xác định image này. Toolkit đóng gói cẩn thận image này đúng thư mục khi bạn tạo một file jar. Trong trường hợp này, thư mục đó sẽ là thư mục gốc chứa file jar. Phải đảm bảo rằng dù bạn tham chiếu image như thế nào trong MIDlet thì image vẫn ở đúng thư mục. Chẳng hạn, nếu bạn muốn để tất cả ảnh trong thư mục images trong gói jar mà không phải ở thư mục gốc của jar, bạn cần để các image dưới thư mục res. Để tham chiếu đến các ảnh trong thư mục images của bạn, bạn cần:createImage(“/images/duke.gif”);
7.Xử lý các lệnh người dùng
Không phải một thành phần UI nào cũng được phép tương tác với người dùng. Một MIDlet tương tác với một người dùng thông qua các command (lệnh). Một command tương đương với một button hay một menu item trong một ứng dụng thông thường, và chỉ có thể được kết hợp với một thành phần UI Displayable. Giống như một Ticker, lớp Displayable cho phép người dùng gắn một command vào nó bằng cách sử dụng phương thứcaddCommand(Command command). Không như Ticker, một thành phần UI Displayable có thể có nhiều command được kết hợp với nó.
Lớp Command giữ các thông tin về một command. Thông tin này gói gọn trong 4 thuộc tính. Các thuộc tính này là: một label ngắn, một label dài tùy chọn, một kiểu command, và một quyền ưu tiên. Bạn có thể tạo ra một command bằng cách cung cấp những giá trị này trong phương thức dựng:
Command exitCommand = new Command("EXIT", Command.EXIT, 1);
Chú ý là các lệnh này không thể thay đổi được một khi đã tạo ra.
Bằng cách chỉ rõ kiểu của một command, bạn có thể để cho thiết bị chạy ánh xạ MIDlet với mọi phím được định nghĩa trước trên thiết bị với các command. Chẳng hạn, một command có kiểu là OK sẽ được ánh xạ đến phím OK của thiết bị. Các kiểu command còn lại là: BACK, CANCEL, EXIT, HELP, ITEM, SCREEN, và STOP. Kiểu SCREEN liên quan đến một command được ứng dụng định nghĩa trước. Cả 2 kiểu command SCREEN và ITEM hầu như không bao giờ có các phím được ánh xạ bởi thiết bị.
Bằng cách chỉ rõ độ ưu tiên, bạn nói cho AMS chạy MIDlet ở đâu và cách thể hiện command. Giá trị quyền ưu tiên càng thấp thì độ ưu tiên càng cao. Ví dụ là có thể bạn muốn một command thoát chương trình và cho nó độ ưu tiên là 1. Vì khoảng không màn hình là giới hạn, thiết bị thu gọn lại các command ít quan trọng vào một menu. Độ ưu tiên thích hợp thường là 1. Hình 7 cho thấy trường hợp này.
Hình 7 – Cách các command được hiển thị. Menu nhảy ra khi người dùng nhấn phím tương ứng với menu command.
Chịu trách nhiệm cho hành vi của các command được thực hiện bởi một lớp cài đặt giao diện CommandListener, là giao diện có một phương thức đơn: commandAction(Command com, Displayable dis). Tuy nhiên, trước khi thông tin các command có thể đi vào bộ listener. Bộ listener đã được đăng ký với phương thứcsetCommandListener(CommandListener listener) từ lớp Displayable.
Để hiểu rõ hơn, code 6 cho thấy cách thêm các command vào Form trong code 4.
package com.j2me.part2;

import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.Gauge;
import javax.microedition.lcdui.Spacer;
import javax.microedition.lcdui.ImageItem;
import javax.microedition.lcdui.TextField;
import javax.microedition.lcdui.DateField;
import javax.microedition.lcdui.StringItem;
import javax.microedition.lcdui.ChoiceGroup;

import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Choice;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Command;
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.CommandListener;

public class FormExample
  extends MIDlet
  implements CommandListener {

 private Form form;
 private Gauge gauge;

 private Spacer spacer;
 private ImageItem imageItem;
 private TextField txtField;
 private DateField dateField;
 private StringItem stringItem;
 private ChoiceGroup choiceGroup;

 public FormExample() {
  form = new Form("Your Details");

  // a StringItem is not editable
  stringItem = new StringItem("Your Id: ", "WXP-890");
  form.append(stringItem);

  // you can accept Date, Time or DateTime formats
  dateField =
    new DateField("Your DOB: ", DateField.DATE);
  form.append(dateField);

  // similar to using a TextBox
  txtField = new TextField(
   "Your Name: ", "", 50, TextField.ANY);
  form.append(txtField);

  // similar to using a List
  choiceGroup = new ChoiceGroup(
   "Your meals: ",
   Choice.EXCLUSIVE,
   new String[] {"Veg", "Non-Veg"},
   null);
  form.append(choiceGroup);

  // put some space between the items
  spacer = new Spacer(20, 20);
  form.append(spacer);

  // a gauge is used to show progress
  gauge = new Gauge("Step 1 of 3", false, 3, 1);
  form.append(gauge);

  // an image may not be found,
  // therefore the Exception must be handled
  // or ignored
  try {
   imageItem = new ImageItem(
    "Developed By: ",
    Image.createImage("/duke.gif"),
    ImageItem.LAYOUT_DEFAULT,
    "Duke");
   form.append(imageItem);
  } catch(Exception e) {}

  // create some commands and add them
  // to this form
  form.addCommand(
   new Command("EXIT", Command.EXIT, 2));
  form.addCommand(
   new Command("HELP", Command.HELP, 2));
  form.addCommand(
   new Command("OK", Command.OK, 1));

  // set itself as the command listener
  form.setCommandListener(this);
 }

 // handle commands
 public void commandAction(
  Command com, Displayable dis) {

  String label = com.getLabel();

  if("EXIT".equals(label))
    notifyDestroyed();
  else if("HELP".equals(label))
    displayHelp();
  else if("OK".equals(label))
    processForm();
 }

 public void displayHelp() {
  // show help
 }

 public void processForm() {
  // process Form
 }

 public void startApp() {
  Display display = Display.getDisplay(this);
  display.setCurrent(form);
 }

 public void pauseApp() {
 }

 public void destroyApp(boolean unconditional) {
 }
}
Code 6 – Thêm các command vào Form
Những điểm khác nhau với Code 4 được tô đậm lên.  Command Listener trong trường hợp này chính là lớp Form, và do đó, nó hiện thực phương thức commandAction(). Chú ý rằng phương thức này cũng chấp nhận một tham số Displayable, rất là hữu ích. Bởi vì các command là không thể thay đổi được, chúng có thể được gắn vào nhiều đối tượng Displayable, và tham số này có thể giúp nhận ra đối tượng Displayable nào đã gọi command.
8.Làm việc với các hàm API cấp thấp
Các API cấp thấp cho MIDlet gồm có các lớp Canvas và GraphicsCanvas là lớp trừu tượng; bạn phải tự tạo ra các canvas (bức vẽ) của riêng bạn bằng cách kế thừa lớp này và phải cài đặt lại phương thức paint(Graphics g), là phương thức thực sự vẽ cái gì đó lên thiết bị. Lớp Canvas và Graphics làm việc cùng nhau nhằm cung cấp các kiểm soát mức thấp trên một thiết bị. Hãy bắt đầu với một Canvas đơn giản.
Code 7 cho thấy một canvas vẽ một hình vuông màu đen ở giữa màn hình thiết bị.
package com.j2me.part2;

import javax.microedition.lcdui.Canvas;
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Graphics;

public class CanvasExample
  extends MIDlet {

 Canvas myCanvas;

 public CanvasExample() {
  myCanvas = new MyCanvas();
 }

 public void startApp() {
  Display display = Display.getDisplay(this);

  // remember, Canvas is a Displayable so it can
  // be set on the display like Screen elements
  display.setCurrent(myCanvas);

  // force repaint of the canvas
  myCanvas.repaint();
 }

 public void pauseApp() {
 }

 public void destroyApp(boolean unconditional) {
 }
}

class MyCanvas extends Canvas {
 public void paint(Graphics g) {
  // create a 20x20 black square in the center
  g.setColor(0x000000); // make sure it is black
  g.fillRect(
   getWidth()/2 - 10,
   getHeight()/2 - 10,
   20, 20);
 }
}
Code 7 – Tạo và hiển thị một Canvas
Lớp MyCanvas kế thừa Canvas và ghi đè phương thức paint(). Mặc dù phương thức này được gọi ngay khi canvas tạo ra thành phần displayable hiện hành (bằng setCurrent(myCanvas)), đó là một ý tưởng tốt để gọi phương thức repaint() trên canvas này sớm về sau. Phương thức paint() chấp nhận một đối tượng Graphics, là đối tượng cung cấp các phương thức cho việc vẽ các đối tượng 2D lên màn hình thiết bị. Ví dụ, trong code 7, một hình vuông màu đen được tạo ra ở giữa màn hình sử dụng đối tượng Graphics này. Chú ý rằng trước khi vẽ hình vuông bằng phương thức fillRect(), màu hiện hành được chọn là màu đen bằng phương thức g.setColor(). Điều này không cần thiết vì màu mặc định là màu đen rồi, ví dụ này chỉ minh họa cách thay đổi màu nếu bạn muốn làm sau này.
Nếu chạy đoạn mã 7, kết xuất trên trình mô phỏng sẽ như sau:
Hình 8 – Vẽ một hình vuông đơn giản ở giữa một Canvas
Chú ý rằng vùng bị tô đậm phía trên hình 8. Dù MIDlet vẫn đang chạy, AMS vẫn hiển thị màn hình trước đó. Đây là do phương thức paint(), màn hình trước đó không được xóa đi, và hình vuông được vẽ chồng lên đó. Để xóa màn hình, bạn có thể sử dụng đoạn mã sau trong phương thức paint(), trước khi hình vuông được vẽ ra.
g.setColor(0xffffff);
     // sets the drawing color to white
g.fillRect(0, 0, getWidth(), getHeight());
     // creates a fill rect which is the size of the screen
Chú ý rằng các phương thức getWidth() và getHeight() trả về kích thước của màn hình hiển thị và cũng là kích thước canvas ban đầu, là toàn bộ màn hình. Mặc dù kích thước của canvas không thể được thay đổi, bạn có thể thay đổi kích thước và vị trí của vùng cắt (clip area), là vùng thực sự đang diễn ra các thao tác vẽ vời. Một clip area, trong Graphics là vùng mà các thao tác vẽ được quản lý. Lớp Graphics cung cấp phương thức setClip(int x, int y, int width, int height) để thay đổi vùng clip này, với góc trên bên trái lúc này là (0, 0). Do vậy, nếu bạn sử dụng phương thức getClipWidth() và getClipHeight() trên đối tượng Graphics được chuyển cho phương thức vẽ trong code 7, cả hai phương thức trả về giá trị bằng với giá trị được trả về bởi getWidth() getHeight() của Canvas.
Đối tượng Graphics có thể được dùng để vẽ không chỉ các hình vuông, hình chữ nhật, mà còn có thể vẽ các cung, đường thẳng, các ký tự, các image, văn bản. Ví dụ, để vẽ dòng chữ “Hello world” lên đỉnh của hình vuông trong code 7, bạn có thể thêm đoạn mã sau trước hoặc sau khi hình vuông được vẽ:
g.drawString("Hello World", getWidth()/2, getHeight()/2 - 10,
              Graphics.HCENTER | Graphics.BASELINE);
Kết quả khi thay đổi sẽ là:
Hình 9 – Vẽ văn bản sử dụng đối tượng Graphics
Văn bản, các ký tự, và image được định vị sử dụng khái niệm các điểm neo. Cú pháp đầy đủ của phương thứcdrawString() là drawString(String text, int x, int y, int anchor). Việc định vị neo quanh trục XY được chỉ rõ bằng toán tử bit OR với hai hằng. Một hằng chỉ rõ khoảng không chiều ngang (LEFT, HCENTER, RIGHT) và hằng kia chỉ rõ khoảng không theo chiều dọc (TOP, BASELINE, BOTTOM). Do đó, để vẽ văn bản “Hello world” lên đỉnh của hình vuông, khoảng không theo chiều ngang của neo cần ở giữa quanh vị trí giữa của canvas (getWidth()/2) và kể từ đây về sau, tôi sử dụng hằng Graphics.HCENTER. Tương tự, khoảng không theo chiều dọc được chỉ rõ bởi hằng BASELINE quanh vị trí trên cùng của hình vuông (getHeight()/2). Bạn cũng có thể sử dụng giá trị đặc biệt 0, tương đương với TOP | LEFT.
Các image cũng được vẽ và định vị trên màn hình tương tự như trên. Bạn có thể tạo các off-screen image bằng phương thức tĩnh createImage(int width, int height) trong lớp Image. Bạn có thể lấy về một đối tượng Graphics được kết hợp với image này bằng phương thức getGraphics(). Phương thức này chỉ có thể được gọi trên các image có thể được thay đổi. Một image được nạp lên từ hệ thống file của thiết bị, hoặc qua mạng, được coi là một image có thể được thay đổi, và mọi cố gắng khi lấy về một đối tượng Graphics như vậy sẽ ném ra một ngoại lệ IllegalStateException lúc thực thi.
Sử dụng các điểm neo với các image thì tương tự với văn bản và các ký tự. Các image cho phép một hằng bổ sung cho khoảng không theo chiều dọc, được chỉ rõ bởi Graphics.VCENTER. Và vì không có  khái niệm baseline cho một image, sử dụng hằng BASELINE sẽ ném ra một ngoại lệ.
Code 8 cho thấy một đoạn mã ngắn từ phương thức paint() của lớp MyCanvas(), tạo ra một off-screen image, thay đổi nó bằng cách thêm một image được nạp từ hệ thống file, và vẽ một đường màu đỏ qua image đó. Chú ý rằng bạn sẽ cần ảnh duke.gif trong thư mục res của ứng dụng CanvasExampleMIDlet.
// draw a modified image
try {
 // create an off screen image
 Image offImg = Image.createImage(25, 19);

 // get its graphics object and set its
 // drawing color to red
 Graphics offGrap = offImg.getGraphics();
 offGrap.setColor(0xff0000);

 // load an image from file system
 Image dukeImg =
   Image.createImage("/duke.gif");

 // draw the loaded image on the off screen
 // image
 offGrap.drawImage(dukeImg, 0, 0, 0);

 // and modify it by drawing a line across it
 offGrap.drawLine(0, 0, 25, 19);

 // finally, draw this modified off screen
 // image on the main graphics screen
 // so that it is just under the square
 g.drawImage(
  offImg, getWidth()/2,
  getHeight()/2 + 10,
  Graphics.HCENTER | Graphics.TOP);

} catch(Exception e) { e.printStackTrace(); }
Code 8 – Tạo, thay đổi, và hiển thị một off-screen image trên một Canvas
Màn hình kết quả như sau:
Hình 10 –  Văn bản, một hình vuông, và một ảnh được thay đổi vẽ lên một Canvas
Lớp Canvas cung cấp các phương thức để tương tác với người dùng, bao gồm cả các hành động game được định nghĩa trước, các sự kiện phím, và, nếu một thiết bị trỏ hiện diện, thì có thêm các sự kiện con trỏ. Thậm chí bạn có thể gắn các command cấp cao vào một canvas, tương tự với gắn các command lên một thành phần UI cấp cao.
Mỗi lớp Canvas tự động nhận các sự kiện phím qua lời gọi keyPressed(int keyCode), keyReleased(int keyCode), và keyRepeated(int keyCode). Cài đặt mặc định cho những phương thức này là rỗng, nhưng không phải là trừu tượng, tức là cho phép bạn chỉ ghi đè các phương thức mà bạn thích. Tương tự với các sự kiện phím, nếu một thiết bị con trỏ hiện diện, các sự kiện con trỏ được gửi tới các phương thức pointerDragged(int a, int y), pointerPressed(int x, int y), và pointerReleased(int x, int y).
Lớp Canvas định nghĩa các hằng cho key code và đều được đảm bảo sự hiện diện trên tất cả các thiết bị không dây. Những key code này xác định tất cả các con số (chẳng hạn, KEY_NUM0, KEY_NUM1, KEY_NUM2…), phím dấu sao (* KEY_STAR), phím dấu thắng (# KEY_POUND). Lớp này tạo điều kiện dễ dàng khi bắt lấy các sự kiện game bằng cách định nghĩa một số hằng game cơ bản. Có 9 hằng thích hợp với hầu hết các game: UP, DOWN, LEFT, RIGHT, FIRE, GAME_A, GAME_B, GAME_C, và GAME_D. Nhưng làm thế nào để một sự kiện phím truyền được cho một sự kiện game.
Bằng cách sử dụng phương thức getGameAction(). Một số thiết bị cung cấp một điều khiển điều hướng cho việc di chuyển quanh màn hình, trong khi một số thiết bị sử dụng các phím số 2, 4, 6, và 8. Để tìm ra phím hành động game nào được nhấn, lớp Canvas gói gọn thông tin này và quy định nó theo hình thức các hành động game. Với bạn, là nhà phát triển, cần bắt lấy key code được nhấn từ phía người dùng vào đúng phương thức, và sử dụng phương thức getGameAction() để quyết định phím được ấn tương ứng với một game action nào đó. Và bạn có thể đoán, nhiều key code có thể tương ứng với một hành động game, nhưng một key code đơn nhiều nhất chỉ tương ứng với một hành động game.
Code 9 mở rộng đoạn mã code 7, thêm vào công đoạn xử lý key code. Trong đoạn code này, hình vuông ở giữa màn hình di chuyển được quanh màn hình với sự điều khiển của các phím điều hướng.
package huetoday.j2me.part2;

import javax.microedition.lcdui.Canvas;
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Graphics;

public class CanvasExample
  extends MIDlet {

 Canvas myCanvas;

 public CanvasExample() {
  myCanvas = new MyCanvas();
 }

 public void startApp() {
  Display display = Display.getDisplay(this);

  // remember, Canvas is a Displayable so it can
  // be set on the display like Screen elements
  display.setCurrent(myCanvas);

  // force repaint of the canvas
  myCanvas.repaint();
 }

 public void pauseApp() {
 }

 public void destroyApp(boolean unconditional) {
 }
}

class MyCanvas extends Canvas {
 public void paint(Graphics g) {
  // create a 20x20 black square in the center

  // clear the screen first
  g.setColor(0xffffff);
  g.fillRect(0, 0, getWidth(), getHeight());

  g.setColor(0x000000); // make sure it is black

  // draw the square, changed to rely on instance variables
  g.fillRect(x, y, 20, 20);
 }

 public void keyPressed(int keyCode) {

  // what game action does this key map to?
  int gameAction = getGameAction(keyCode);

  if(gameAction == RIGHT) {
   x += dx;
  } else if(gameAction == LEFT) {
   x -= dx;
  } else if(gameAction == UP) {
   y -= dy;
  } else if(gameAction == DOWN) {
   y += dy;
  }

  // make sure to repaint
  repaint();
 }

 // starting coordinates
 private int x = getWidth()/2 - 10;
 private int y = getHeight()/2 - 10;

 // distance to move
 private int dx = 2;
 private int dy = 2;
}
Code 9 – Xử lý các sự kiện phím để di chuyển hình vuông.
Chú ý rằng trong đoạn code này, ta vẽ hình vuông đã được thay đổi dựa vào các biến thể hiện. Phương thứckeyPressed() đã được ghi đè lại và do đó, cho dù người dùng ấn một phím bất kỳ, phương thức này được gọi. Đoạn mã còn kiểm tra phím đã ấn có phải là một phím game hay không, và dựa theo phím game đó, thay đổi hệ tọa độ của hình vuông. Cuối cùng, gọi phương thức repaint() để kích hoạt phương thức paint(), là làm di chuyển hình vuông trên màn hình đến hệ trục tọa độ mới. Trong bài học này, bạn đã tạo ra các thành phần UI và được giới thiệu nhiều API giao diện người dùng cho MIDlet.
Trong bài học tiếp theo, bạn sẽ học cách sử dụng Gaming API của MIDP 2.0 trong góijavax.microedition.lcdui.game.

Không có nhận xét nào:

Đăng nhận xét