[Java] 用 InputMap ActionMap KeyStroke 並解決按鍵遲鈍問題

這篇等於是結合前面兩篇 利用 getInputMap、getActionMap 和 KeyStroke 從外部新增 Actionjavax 中 KeyListener 的遲鈍問題 的結果

概念上是用 KeyStroke.getKeyStroke("pressed RIGHT") 得到按下右鍵時,讓 InputMap 對應到 "rightAct" 字串,再讓 ActionMap 對應到一個動作,而這個動作物件是在外部再描述,讓 myLabel 裡的物件成員 ArrayList<Character> pressedKey 來增減內容,當沒有 'r' 時增加 'r' 進去,而且若是第一次按鍵就啓動 timer;而 KeyStroke.getKeyStroke("released RIGHT") 放開右鍵時,對應到 "rightCancel" 字串,由 ActionMap 對應到動件,在外描描述此動作,當 pressKey 之中有 'r' 時刪去,如果完全沒有按鍵時則 timer 停止。timer 會讓 update() 不停的執行,而 update() 內容也由外部覆寫,處理目前按下了左鍵還是右鍵,讓 myLabel 更新他的位置來左右移動。

GameObject.java

package game;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import javax.swing.JLabel;
import javax.swing.Timer;

public class GameObject extends JLabel implements ActionListener{

  private int delay;
  private Timer timer;
  private ArrayList<Character> pressedKeyList = new ArrayList<>();

  public GameObject() {
    delay = 16;
    timer = new Timer(delay, this);
    timer.setInitialDelay(0);

  }

  public void update() {
    System.out.println(pressedKeyList);   // 之後由外部覆寫此處內容
  }

  public void setDelay(int delay) {
    this.delay = delay;
  }
	
  public void startTimer() {
    timer.start();
  }

  public void stopTimer() {
    timer.stop();
  }

  public boolean hasKey(char key) {     // 串列中是否已有鍵
    return pressedKeyList.contains(key);
  }

  public void addKey(char key) {       // 在串列中增加
    pressedKeyList.add(key);
  }

  public void delKey(char key) {       // 從串列中拿掉
    pressedKeyList.remove(pressedKeyList.indexOf(key));
  }

  public boolean keyStart() {          // 判斷是否初次按鍵用
    return pressedKeyList.size() == 1;
  }

  public boolean keyEnd() {            // 判斷是否不再有按鍵用
    return pressedKeyList.size() == 0;
  }

  @Override
  public void actionPerformed(ActionEvent e) {

    update();
  }
}

MyFrame.java

package game;

import java.awt.Color;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;

public class MyFrame extends JFrame{

  GameObject myLabel;
  JPanel myPanel;

  MyFrame(){

    myLabel = new GameObject() {

    public void update() {          // 覆寫要更新的部份

      int deltaX = 0;               // 也就是左右移動
      if(this.hasKey('l')) {        // 若增加上下移動的deltaY統計
        deltaX -= 2;                // 最後 setLocation 就可實現斜向移動
      }
      if(this.hasKey('r')) {
        deltaX += 2;
      }
      this.setLocation( this.getX() + deltaX , this.getY() );
    }
  };

    myLabel.setSize(80,60);
    myLabel.setBackground(Color.green);
    myLabel.setOpaque(true);
    myLabel.setLocation(100,400); 
                                         // 偵測到按下左鍵
    myLabel.getInputMap().put(KeyStroke.getKeyStroke("pressed LEFT"), "leftAct");
    myLabel.getActionMap().put("leftAct", new AbstractAction() {

    @Override                            // 則會運作此部份,由外部覆寫決定內容
    public void actionPerformed(ActionEvent e) {
      if(!myLabel.hasKey('l')) {         // 將在物件成員 pressedKey 中增加按鍵
        myLabel.addKey('l');
      }
      if(myLabel.keyStart()) {           // 並判斷是否開啓計時器
        myLabel.startTimer();            // 持續 update()
      }
    }
  });                                     // 偵測到放開左鍵
    myLabel.getInputMap().put(KeyStroke.getKeyStroke("released LEFT"), "leftCancel");
    myLabel.getActionMap().put("leftCancel", new AbstractAction() {

    @Override                             // 則會運作此部份
    public void actionPerformed(ActionEvent e) {
      if(myLabel.hasKey('l')) {           // 在 pressedKey 中拿掉按鍵
        myLabel.delKey('l');
      }
      if(myLabel.keyEnd()) {              // 並判斷是否關閉計時器
        myLabel.stopTimer();              // 不再 update()
      }
    }
  });
    myLabel.getInputMap().put(KeyStroke.getKeyStroke("pressed RIGHT"), "rightAct");
    myLabel.getActionMap().put("rightAct", new AbstractAction() {

    @Override
    public void actionPerformed(ActionEvent e) {
      if(!myLabel.hasKey('r')) {         // 按下右鍵的部份,同前
        myLabel.addKey('r');
      }
      if(myLabel.keyStart()) {
        myLabel.startTimer();
      }
    }
  });
    myLabel.getInputMap().put(KeyStroke.getKeyStroke("released RIGHT"), "rightCancel");
    myLabel.getActionMap().put("rightCancel", new AbstractAction() {

    @Override
    public void actionPerformed(ActionEvent e) {
      if(myLabel.hasKey('r')) {         // 放開右鍵的部份,同前
        myLabel.delKey('r');
      }
      if(myLabel.keyEnd()) {
        myLabel.stopTimer();
      }
    }
  });    

    myPanel = new JPanel();
    myPanel.setLayout(null);
    myPanel.add(myLabel);

    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setSize(500,600);
    this.add(myPanel);
    this.setVisible(true);
  }
}

Main.java

package game;

public class Main {
  public static void main(String[] args) {
    new MyFrame();
  }
}

這樣算是有完成本來的期待,但大部份東西都在外部再覆寫,弄的很冗長很不漂亮,期待未來技術精進可以再改良。

留言