I.Giới thiệu

Trong J2ME, người dùng tương tác với MIDlet thông qua các thành phần giao diện. Có 2 loại giao diện:

  • Giao diện cấp cao(High level API)
  • Giao diện cấp thấp(Low level API)

Giao diện cấp cao

Giao diện cấp cao có tính linh động rất cao, được sử dụng để tương tác với người dùng cuối. Tuy nhiên hạn chế của nó là các control rất ít, và đặc biệt là xây dựng các giao diện đồ họa phức tạp thì không thể đáp ứng.

Giao diện cấp thấp

Giao diện cấp thấp được sử dụng trong các ứng dụng đòi hỏi có đồ họa phức tạp và truy cấp đến các sự kiện mức thấp. Nó cung cấp các phương thức để xử lý các sự kiện phím, game và màn hình cảm ứng. Các API của nó được dùng để vẽ các image, thực hiện các chuyển động, cuộn hay xoay màn hình. Nó được sử dụng mạnh mẽ trong game hay các ứng dụng đòi hỏi tính đồ họa phức tạp.

Sơ đồ tổng quát của nó như sau:

II.Các thành phần của giao diện cấp thấp

Trong bài này, chúng ta sẽ tìm hiểu 2 lớp quan trọng trong giao diện cấp thấp, đó là Canvas và Graphics.

1.Canvas

Canvas  là lớp cơ sở trong J2ME đượ sử dụng để xử lý các sự kiện cấp thấp và các vấn đề liên quan đến đồ họa để hiển thị lên màn hình.

Do Canvas là một lớp ảo(abstract), nên bản thân lớp Canvas sẽ không thể tạo ra đối tượng trực tiếp mà phải thông qua các lớp con của nó. Các lớp con của Canvas, sau khi extends Canvas, phải cài đặt phương thức ảo của nó, trong Canvas chỉ có phương thức paint() là phương thức ảo, nên ta có thể cài đặt con của lớp Canvas như sau:

public class MainCanvas extends Canvas{
  public void paint(Graphics g){
   g.setColor(255,255,255);
   g.drawString(“SMI Java”,0,0,Graphics.TOP|Graphics.LEFT);
  }
}

1.1.Thêm một Command vào Canvas

Giống như tất cả các lớp con khác của lớp Displayable, lớp Canvas cho phép ứng dụng đăng ký sự kiện Command.

Ví dụ:

public class CommandCanvas extends Canvas implements CommandListener {


private CanvasMidlet midlet;

private int width;

private int height;

private static final String EXIT = “Exit”;

private static final String OK   = “Ok”;

private final Command CMD_EXIT=new Command(EXIT,Command.EXIT,0);

private final Command CMD_OK=new Command(OK,Command.OK,1);

public CommandCanvas(CanvasMidlet midlet){

this.midlet=midlet;

width=getWidth();

height=getHeight();

}

public void paint(Graphics g){

g.setColor(255,255,255);

g.fillRect(0,0,width,height);

this.addCommand(CMD_EXIT);

this.setCommandListener(this);

}

public void commandAction(Command cmd,Displayable disp){

if(cmd==cmdExit){

midlet.notifyDestroyed();

midlet.destroyApp(true);

}

}

}
1.2.Thiết lập chế độ vẽ Canvas lên toàn bộ màn hình

Có 2 chế độ để Canvas được hiển thị:

  • Normal: trong chế độ này, chiều cao vùng hiển thị canvas bằng tổng chiều cao màn hình trừ đi vùng tiêu đề và vùng vẽ các command.
  • Full: trong chế độ này, chiều cao vùng hiển thị canvas bằng tổng chiều cao màn hình.

Các đối tượng Canvas thường được tạo ra theo chế độ mặc định. Để thiết lập chế độ full màn hình, bạn chỉ cần gọi phương thức setFullScreenMode(boolean mode).

Việc gọi phương thức setFullScreenMode (boolean mode) thực chất là gọi phương thức sizeChanged(int w, int h), bình thường thì phương thức này không làm gì. Ứng dụng có thể lợi dụng phương thức này để thay đổi chiều cao vào chiều rộng vùng vẽ đối các thiết bị có chức nay xoay màn hình.

1.3.Xử lý sự kiện

Lớp Canvas có các phương thức xử lý sự kiện sau đây

Phương thức Mô tả
protected void keyPressed(int keyCode) Được gọi khi phím vật lý trên keypad được được nhấn
protected void keyReleased(int keyCode) Được gọi khi một phím được nhả ra.
protected void keyRepeated(int keyCode) Được gọi khi một phím được nhấn liên tục. Sự kiện nàycó thể không được hỗ trợ cho tất cả các platform. Để kiểm tra thiết bị có hỗ trợ sự kiện này hay không, ta goi phương thức hasRepeatEvents()
protected void pointerPressed(int x, int y) Được goi khi một pointer được nhấn. Sự kiện nàycó thể không được hỗ trợ cho tất cả các platform. Để kiểm tra thiết bị có hỗ trợ sự kiện này hay không, ta goi phương thức hasPointerEvents()
protected void pointerDragged (int x, int y) Được goi khi một pointer được kéo rê. Sự kiện nàycó thể không được hỗ trợ cho tất cả các platform. Để kiểm tra thiết bị có hỗ trợ sự kiện này hay không, ta goi phương thức hasPointerMotionEvents()
protected void pointerReleased(int x, int y) Được goi khi một pointer được nhấn. Sự kiện nàycó thể không được hỗ trợ cho tất cả các platform. Để kiểm tra thiết bị có hỗ trợ sự kiện này hay không, ta goi phương thức hasPointerPointerEvents()

Phương thức keyRepeated() không phải lúc nào cũng được hỗ trợ bởi tất cả các thiết bị. Để kiểm tra xem phương thức này có được các thiết bị hỗ trợ hay không, ta gọi phương thức hasRepeatedEvents().

Giá trị keyCode trong 3 phương thức keyPressed(), keyReleased() và keyRepeated() cho biết giá trị mã phím khi người dùng nhấn các phím keypad trên thiết bị.

public void keyPressed(int keycode)
{
     if (keycode == KEY_NUM0)
     {
        System.out.println("Pressed Key 0");
     } else if(keycode == KEY_NUM1){
        System.out.println("Pressed Key 1");
     } else if(keycode == KEY_NUM2){
        System.out.println("Pressed Key 2");
     }
}

Ngoài ra, lớp Canvas còn cung cấp một số sự kiện để ánh xa qua lại giữa các phím mũi tên và các phím keypad nhằm để phục vụ cho các ứng dụng game. MIDP định nghĩa một số sự kiện phím ảo: UP, DOWN, LEFT, RIGHT, FIRE, GAME_A, GAME_B, GAME_C, GAME_D. Để ánh xạ các sự kiện phím, ta sử dụng phương thức getGameAction(int keyCode) như trong ví dụ dưới đây:

public void keyPressed(int key)
{
     int action = getGameAction(key);
     switch (action)
     {
          case UP:        
               form.append("Pressed UP arrow key");
          break;

          case LEFT:        
              form.append ("Pressed LEFT arrow key");
          break;
                 case GAME_B:        
              form.append ("Pressed GAME_B key");
          break;          
     }
}

1.4.Ngắt/hiển thị đối tượng Canvas

Để ngắt/hiển thị một đối tượng Canvas, bạn gọi 2 phương thức hideNotify() và showNotify(). Phương thức showNotify() được gọi trước khi đối tượng Canvas thực sự visible trên màn hình, ngược lại phương thức hideNotify() được gọi ngay sau khi đối tượng Canvas bị remove khỏi màn hình hiển thị.

/*called when an interrupt such as incoming call is received*/
protected void hideNotify() {
 }
 /*called when an interrupt such as incoming call is ended*/
protected void showNotify() {
 }

1.5.Phương thức paint(Graphics g)

  • Phương thức paint(Graphics g) được sử dụng để vẽ các đối tượng graphics lên màn hình. Để gọi phương thức paint(Graphics g), đơn giãn bạn chỉ cần tạo ra một instance của lớp Canvas và sau đó gọi phương thức paint(Graphics g).
  • Có thể gọi phương thức paint(Graphics g) một cách bất đồng bộ thông qua gọi phương thức repaint().
  • Việc gọi phương thức paint() thông qua gọi phương thức repaint() có thể không được thực hiện ngay lập tức. Nó có thể bị trì hoãn cho đến khi luồng(flow) điều khiển trả về từ phương thức xử lý sự kiện hiện tại. Sự chậm trễ ở đây không phải là vấn đề lớn, nhưng trong khi xử lý các chuyển động, cách an toàn nhất để kích hoạt việc vẽ lại là gọi phương thức Display.callSerially() hay yêu cầu vẽ lại từ một thread riêng biệt hoặc TimerTask. Ngoài ra, ứng dụng có thể ép buộc việc vẽ lại ngay lập tức bằng cách gọi phương thức serviceRepaints().Ứng dụng có thể sử dụng phương thức phương thức Display.callSerially() hay serviceRepaints() để đồng bộ với phương thức paint().

2.Graphics

  • Đối tượng Graphics được sử dụng bởi Canvas, nó có thể được biểu diễn trực tiếp lên màn hình hay thông qua một bộ đệm off-screen trước khi vẽ lên màn hình.
  • Đối tượng Graphics hỗ trợ vẽ các chuỗi string, image, đường thẳng, đường tròn…
  • Cung cấp một kiểu màu sắc 24bit, trong đó 8bit cho mỗi thành phần màu red, green và blue. Không phải tất cả các thiết bị đều hỗ trợ đầy đủ 24bit màu, vì vậy chúng sẽ được ánh xạ các màu sắc yêu cầu của ứng dụng vào bảng màu hiện tại được hỗ trợ trên thiết bị.
  • Hệ thống tọa độ được tính bằng pixel, tọa độ gốc(0,0) mặc định nằm phía trên cùng bên trái như hình vẽ dưới đây:

  • Graphics có một phương thức rất quan trọng translate(int x, int y) để dịch chuyển tọa độ gốc, phương thức này thường dùng trong các kỹ thuật cuộn màn hình.
  • Ngoài phương thức translate(int x, int y), lớp Graphics còn có một phương thức nữa quan trọng khác là setClip(int x, int y, int width, int height), các vùng nằm ngoài vùng clip được thiết lập sẽ không có hiệu lực, chính điều này làm tăng tốc quá trình vẽ.

Ví dụ 1:

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

public class SplashMidlet extends MIDlet {

    /*current display object*/
    private Display display;

    /*object for displaying canvas screen*/
    private SplashCanvas canvas;

    /* midlet constructor*/
    public SplashMidlet() {
        display = Display.getDisplay(this);
        /*initializing the SplashCanvas class*/
        canvas = new SplashCanvas(this);
    }

    /*called when the midlet is called for first time*/
    public void startApp() {
        display.setCurrent(canvas);
    }

    /*called when any interrupt occurs and can handle the player*/
    public void pauseApp() {
        /*can write code here for handling all interrupts
        and for unhandled garbage collections*/
    }

    /*is used to destroy the Midlet cleanup all that are not
    handled by garbage collection*/
    public void destroyApp(boolean unconditional) {
    }

    /*This method is used to exit the midlet*/
    public void exitMidlet() {
        destroyApp(true);
        notifyDestroyed();
    }
}

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;

public class SplashCanvas extends Canvas implements Runnable, CommandListener {

    /*to create midlet object*/
    private SplashMidlet midlet;
    /*int object to get width of the screen*/
    private int width;
    /*int object to get width of the screen*/
    private int height;
    /*boolean object*/
    private boolean remSplash = false;
    /*this command is used to exit from the current application*/
    private final Command cmdExit = new Command("Exit", Command.EXIT,
      2);
    /*font object*/
    private Font font = null;

    public SplashCanvas(SplashMidlet midlet) {
        this.midlet = midlet;
        /*initializes the font to be displayed*/
        font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM);
        /*need to start the thread or timer to make splash screen remain
        for some time*/
        new Thread(this).start();
        setCommandListener(this);
    }

    public void paint(Graphics g) {
        /*gets the width of the screen*/
        width = getWidth();
        /*gets the height of the screen*/
        height = getHeight();
        /*sets the font property*/
        g.setFont(font);
        g.setColor(255, 255, 255);
        g.fillRect(0, 0, width, height);
        g.setColor(0, 0, 255);
        g.drawString("Splash Screen over", width / 2, height / 2, Graphics.HCENTER | Graphics.BASELINE);
        /*code to show splash screen*/
        if (!remSplash) {
            g.setColor(0, 0, 255);
            g.fillRect(0, 0, width, height);
            g.setColor(255, 255, 255);
            g.drawString("Welcome to ", width / 2, height / 2, Graphics.HCENTER | Graphics.BASELINE);
            g.drawString("Samsung Mobile Innovator", width / 2, (height / 2) +
                                                        font.getHeight(), Graphics.HCENTER | Graphics.BASELINE);
        }
    }

    public void run() {
        synchronized (this) {
            try {
                /*Spash Screen appears for 4 sec*/
                wait(4000L);

            } catch (InterruptedException ie) {
                ie.printStackTrace();
            }
        }
        exitSplash();
    }

    public void commandAction(Command cmd, Displayable disp) {
        if (cmd == cmdExit) {
            midlet.exitMidlet();
        }
    }

    /*to exit from the splash screen*/
    public void exitSplash() {
        remSplash = true;
        addCommand(cmdExit);
        repaint();
    }

    public void keyPressed(int keycode) {
        if (!remSplash) {
            exitSplash();
        }
    }
}

Ví dụ 2:

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

public class CanvasMidlet extends MIDlet {
    /*current display object*/   
    Display display;
    /*object for displaying canvas screen*/
    MainCanvas canvas;

    public CanvasMidlet(){
        display=Display.getDisplay(this);
        /*initializing the MainCanvas class*/
        canvas=new MainCanvas(this);
    }

    /*called when the midlet is called for first time*/
    public void startApp() {
        display.setCurrent(canvas);       
    }

    /*called when any interrupt occurs and can handle the player*/
    public void pauseApp() {       
        /*can write code here for handling all interrupts
          and for unhandled garbage collections*/
    }

     /*is used to destroy the Midlet cleanup all that are not
     handled by garbage collection*/
    public void destroyApp(boolean unconditional) {
    }

    /*This method is used to exit the midlet*/
    public void exitMidlet(){
        destroyApp(true);
        notifyDestroyed();
    }
}

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;

public class MainCanvas extends Canvas implements CommandListener {

    /*to create midlet object*/
    CanvasMidlet midlet;
    /*int object to get width of the screen*/
    int width;
    /*int object to get width of the screen*/
    int height;
    /*this command is used to exit from the current application*/
    private final Command cmdExit = new Command("Exit", Command.EXIT, 2);
    /*this command is used to select an event*/
    private final Command cmdSelect = new Command("Select", Command.OK, 2);
    /*left command object added at left side*/
    private Command leftCommand = null;
    /*right command object added at right side*/
    private Command rightCommand = null;
    /*font object*/
    private Font font = null;
    /*boolean object*/
    private boolean selected; 
    /*object to get keyvalue*/
    private String keyvalue = null;
    /*object to get keyname*/
    private String keyname = null;

    public MainCanvas(CanvasMidlet midlet) {
        this.midlet = midlet;
        /*to use full screen of the device for displaying*/
        setFullScreenMode(true);
        /*gets the width of the screen*/
        width = getWidth();
        /*gets the height of the screen*/
        height = getHeight();
        /*initializes the font to be displayed*/
        font = Font.getDefaultFont();
        addCommand(cmdExit, 1);
        addCommand(cmdSelect, 0);
    }

    public void showNotify() {
        /*can write code to resume the application*/
    }

    public void hideNotify() {
        /*can write code here for handling all interrupts
        and for unhandled garbage collections*/
    }

    /*to draw Commands on the screen*/
    public void drawCommands(Graphics g) {
        g.setColor(0, 0, 0);
        if (leftCommand != null && leftCommand.getLabel().equals("Select")) {
            g.drawString("Select", 0, (height – font.getHeight() – 1), 0);
        }

        if (rightCommand != null && rightCommand.getLabel().equals("Exit"))
        {
            g.drawString("Exit", (width – font.stringWidth("Exit") – 1), (height – font.getHeight() – 1), 0);
         }
    }

    /*to add commands*/
    public void addCommand(Command cmd, int pos) {
        if (pos == 0) {
            leftCommand = cmd;
        } else {
            rightCommand = cmd;
        }
    }

    public void paint(Graphics g) {
        g.setColor(138, 237, 244);
        g.fillRect(0, 0, width, height);
        /*sets the font property*/
        g.setFont(font);
        /*condition to display font color on clicking Select button*/
        if (selected) {
            g.setColor(255, 255, 255);
        } else {
            g.setColor(0, 0, 255);
        }
        g.drawString("Welcome to ", width / 2,0, Graphics.HCENTER | Graphics.TOP);
        g.drawString("Samsung Mobile Innovator", width / 2, font.getHeight(), Graphics.HCENTER | Graphics.TOP);
        g.setColor(0,0,0);
        g.drawString("KeyPressed", width / 2, font.getHeight()*2, Graphics.HCENTER | Graphics.TOP);       

        if (keyvalue != null) {
            g.drawString("keyvalue="+keyvalue, width / 2, font.getHeight()*3, Graphics.HCENTER | Graphics.TOP);
        }
        if (keyname != null) {
            g.drawString("keyname="+keyname, width / 2, font.getHeight()*4, Graphics.HCENTER | Graphics.TOP);
        }

        drawCommands(g);
    }

    public void commandAction(Command cmd, Displayable disp) {
        if (cmd == cmdExit) {
            midlet.exitMidlet();
        } else if (cmd == cmdSelect) {
            if (selected) {
                selected = false;
            } else {
                selected = true;
            }
            repaint();
        }
    }

    public void keyPressed(int keycode) {
        /*get keycode value to a string variable*/
        keyvalue = Integer.toString(keycode);
        /*get key names*/
        keyname=getKeyName(keycode);

        switch (keycode) {
            case -7:
                /*to handle the event of right softkey*/
                if (rightCommand != null) {
                    commandAction(rightCommand, this);
                }
                break;
            case -6:
                /*to handle the event of left softkey*/
                if (leftCommand != null) {
                    commandAction(leftCommand, this);
                }
                break;
        }
        repaint();
    }
}