2023.02.07 - 안드로이드 앱개발자 과정

CHA·2023년 2월 7일
0

Java



GUI 프로그래밍

안드로이드로 넘어가기 이전에 GUI 프로그래밍에 대한 맛을 보기 위해 Java 언어에서 GUI 프로그래밍을 위해 미리 설계된 API 를 이용해보겠습니다. AWT, Swing, JavaFX 등이 있는데, 그중에서 Swing 을 이용하여 GUI 프로그래밍이 어떠한 방식을 취하는지에 대해 알아봅시다. 단, 문법적인 측면보다는 GUI 를 이용한 프로그래밍이라는것이 어떠한 것인지 알아보는것에 중점을 둡시다.

먼저, 지금까지 해왔던 CLI 프로그래밍의 경우 순차적인 방식을 취합니다. 코드가 한줄한줄 읽히면서 차례대로 콘솔에 출력되는 형태였습니다. 반면에 GUI 의 경우 사용자의 선택에 따라 반응을 설계해야 하는 방식입니다. 가령, 사용자가 어떠한 버튼하나를 눌렀을 경우에 어떠한 결과를 도출할 지를 결정해야 하며 그 부분을 설계해주어야 합니다. 그럼 Swing 을 이용하여 GUI 프로그래밍을 시작해봅시다.


여러가지 컴포넌트

자바의 스윙에서는 컴포넌트 객체 위주로 기능과 화면이 만들어집니다. 각 컴포넌트의 종류 등은 아래 도식을 참고해봅시다.

가볍게 보자는 의미에서 간단한 부분들은 코드의 주석으로 설명해놓겠습니다.

컨테이너 생성

JFrame frame = new JFrame("프레임 제목");
frame.setSize(500,400); // 사이즈 조절
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
frame.setLocation(300, 200); // 시작 위치 조정

frame.setVisible(true); // 화면에 보이게 해주는 기능

setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) 메서드 없이 실행할 경우, 완성된 프레임을 종료하더라도 프로그램 자체는 끝나지 않습니다. 그래서 프레임을 닫으면 프로그램 또한 종료할 수 있게 만드는 메서드입니다.

컴포넌트 추가

JButton b1 = new JButton("버튼");
// 컴포넌트 추가 
JButton b2 = new JButton("추가하기");

frame.add(b1); // 컨테이너에 추가 
frame.add(b2);

배치관리자

기본적으로 JFrame 은 BorderLayout 이라는 배치관리자가 설정되어 있습니다. 물론 다른 레이아웃으로 변경이 가능합니다.

frame.setLayout(new FlowLayout());
// FlowLayout() 으로 레이아웃 변경 

frame.add(b1,BorderLayout.NORTH);
// BorderLayout 의 사용.
frame.add(b2,BorderLayout.CENTER);

JTextField

JTextField text = new JTextField(20);
frame.add(text);

JTextField 객체의 파라미터로 20을 넣어주었습니다. 이는 FlowLayout 일때 20글자까지 입력할 수 있는 칸을 제공하겠다는 의미입니다. 만일, BorderLayout 의 경우라면 위치에 맞추어 제공되므로 의미가 없게됩니다.

JLabel & image

JLabel label = new JLabel("This is Label"); 
// 파라미터로 내용입력 가능!
frame.add(label);

Swing 에는 이미지용 컴포넌트가 따로 존재하지 않고 JLabel 에 아이콘을 보여주는 기능을 활용해야 합니다.

ImageIcon ic = new ImageIcon("image/ms14.png");
// 현 프로젝트의 image 패키지 안에 있는 ms14.png 이름의 이미지 파일을 이미지 아이콘 객체의 파라미터로 만들어줌.
Image img = ic.getImage();
// getImage() 로 Image 객체를 img 에 넣어줌.

앞서 파일입출력시에 스트림을 통해 데이터를 전달했듯이 이미지도 비슷한 과정을 거칩니다. 여기에서는 Image 객체가 그 역할을 대신 해줍니다. 그러면, 이미지의 크기 변경을 해봅시다.

// 위 예제에서 뽑아온 이미지 객체를 기반으로 
// 사이즈가 조절된 새로운 이미지 객체를 생성함.
Image cpIm = img.getScaledInstance(128, 128, Image.SCALE_SMOOTH);
ImageIcon cpIc = new ImageIcon(cpIm);

// 이미지아이콘은 컴포넌트가 아니다! 
// 그래서 화면에 보여주려면 JLabel 을 활용해야한다!
JLabel img = new JLabel();
img.setIcon(cpIc);
frame.add(img);

생성자를 통한 화면구성

위의 예제에서 주제에 맞게 짤라서 코드를 구성했지만, 만일 위 예제 모두 한 메인메서드에서 구성이 되었다면 꽤 긴 코드구성이 될겁니다. 그리고 컴포넌트를 생성하고 프레임에 붙여줄 때마다 참조변수를 통해 add() 메서드를 호출해야 하니 귀찮음이 이만저만이 아니죠. 그래서 우리는 프레임 객체가 생성이 될 때 한꺼번에 컴포넌트를 붙여주고 프레임의 설정또한 마무리 하고싶습니다. 코드를 보죠.

public class Main2 {

	public static void main(String[] args) {
		MyFrame frame = new MyFrame();	
	}
}

public class MyFrame extends JFrame{
	JTextArea ta;
	JTextField tf;
	
	// 2_ 생성자 메소드
	public MyFrame() {
		// 3_ 객체가 생성될 때 본인의 설정을 모두 해놓기
		setSize(500, 500);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		
		setLocation(750, 250);
		tf = new JTextField("Hello",20);
		add(tf,BorderLayout.SOUTH);
		
		ta = new JTextArea();
		setVisible(true);
	}
}

버튼 클릭 이벤트

대부분의 핸드폰을 보면, 버튼을 눌렀을 때 어떠한 정보를 띄워주거나, 앱이 실행되거나 게임의 경우 공격을 하는 등의 행위를 하게 됩니다. 그래서 이번에는 사용자가 버튼을 클릭했을 때, 어떠한 작업을 해야할지 정해주는 작업을 해봅시다. 예제에서는 버튼을 클릭하면 설정해두었던 레이블의 내용과 이미지가 바뀌는 작업을 해볼겁니다.

1. 생성자 메서드를 통한 초기값 설정

public class ButtonEventTest extends JFrame {
	JLabel label;
	JButton btn;
	
	public ButtonEventTest() {
		setSize(500,500);
		setTitle("버튼 클릭 이벤트 처리");setDefaultCloseOperation(EXIT_ON_CLOSE);
		setLocationRelativeTo(rootPane);
		setLayout(new FlowLayout());
		
		btn = new JButton("버튼");
		label = new JLabel("Hello Java");
		
		add(btn);
		add(label);
		
		setVisible(true);
	}
	public static void main(String[] args) {
		new ButtonEventTest();
	}

}

메인메서드에서는 ButtonEventTest 의 객체를 생성해주면서 생성자 메서드를 호출시킵니다. 생성자 메서드 내부에 frame의 설정들을 초기화시켜주었습니다. 여기에서 ButtonEventTest 클래스는 JFrame 을 상속하고 있기 때문에 JFrame 에 대한 참조변수를 생성하지 않고 바로 설정값을 변경할 수 있습니다.

또한 버튼과 레이블 컴포넌트를 생성해주었습니다. 이때 만일, 버튼과 레이블의 참조변수를 생성자 메소드 안에서 만들게 되면, 생성자 메소드 내부에서만 사용이 가능한 지역변수로 생성이 되기때문에 항상 컴포넌트의 참조변수는 멤버변수로 만들어주는것이 좋습니다.

2. 버튼 클릭

자바의 설계자는 버튼을 클릭하고 그 반응을 듣는 리스너 객체를 만들어주었습니다. 이 리스너 객체를 버튼에 띡 하고 붙여주면 버튼이 클릭되었을 때 실행할 수 있는 동작을 메서드로 만들 수 있습니다.

// ActionListener listener = new ActionListener(); 
ClickListener clickListener = new ClickListener();
btn.addActionListener(clickListener);

// inner class 
class ClickListener implements ActionListener{

	@Override
	public void actionPerformed(ActionEvent e) {
		System.out.println("Clicked!!!");
		
		if(btnFlag == 1) {
			label.setText("Hello Android"); 
			btnFlag *= -1;
		} else {
			label.setText("Hello Java");
			btnFlag *= -1;
		}
	}
}

ActionListener 인터페이스를 이용하여 객체를 직접 생성할 수 없으므로, ClickListener 클래스를 생성하여 ActionListener 를 implements 해준 뒤, 추상메소드를 구현합시다. 그리고 난뒤, ClickListener의 객체를 버튼에 addActionListener() 메서드의 파라미터로 넘겨주면, 버튼이 눌렸을 때 아까 구현했던 메서드가 실행되게 됩니다.

이때 버튼으로 넘어간 리스너 객체는 버튼이 눌리는지 안눌리는지 계속적으로 관찰하여 버튼이 눌리는 순간 메서드를 실행하게 됩니다. 그런데 일반적으로 우리는 메서드를 우리가 실행해왔습니다. 참조변수를 이용하거나 static 메소드의 경우 클래스의 이름을 통해 실행했었습니다. 이번의 경우에는 버튼이 눌리기만 했는데 메서드가 실행되었습니다. 이 경우에는 사실 운영체제가 메서드를 실행해줍니다. 이렇게 이벤트가 발생했을 때 실행되는 메서드를 콜백메서드 라고 합니다.

3. 이미지 변경
앞선 예제에서는 버튼이 눌리면, label 에 담겨있던 문자가 다른 문자로 바뀌는 예제를 해보았습니다. 이번에는 버튼이 눌리면, 레이블에 담긴 이미지를 다른 이미지로 교체하는 작업을 해봅시다.

int imgFlag = 1;

ActionListener listener = new ActionListener() {
	
	@Override
	public void actionPerformed(ActionEvent e) {
		
		System.out.println("익명클래스 객체 clicked");
		
		if(imgFlag == 1) {
			Image img = new ImageIcon("image/ms21.png").getImage().getScaledInstance(200, 200, Image.SCALE_SMOOTH); // 메소드 체이닝 기법!
			imgLabel.setIcon(new ImageIcon(img));
			imgFlag = 0;
		} else {
			Image img = new ImageIcon("image/ms14.png").getImage().getScaledInstance(200, 200, Image.SCALE_SMOOTH); // 메소드 체이닝 기법!
			imgLabel.setIcon(new ImageIcon(img));
			imgFlag = 1;
		}
	}
};
btn2.addActionListener(listener);

기존에 ActionListener 인터페이스로 객체를 생성할 수 없어, 다른 클래스에 구현을 시킨 뒤 객체를 생성하여 그 객체를 버튼에 붙여주는 방식으로 버튼 클릭을 처리하였습니다. 다만, 이 방식은 실제 구현이 된 클래스와 버튼이 코드상에서 멀리 떨어져있기 때문에 실제 버튼이 눌렸을 때 실행되는 메소드를 확인하기 어렵습니다. 그래서 예전에 배웠던 익명클래스의 개념을 사용하였습니다. 그래서 객체의 생성과 동시에 추상메서드를 구현하였으며, 그렇게 만들어진 객체를 버튼에 붙여주었습니다.

중간에 메소드를 호출하여 리턴값을 이용해 다시 메서드를 호출하는 방식을 사용해 앞서 보았던 이미지 교체방법을 2줄짜리 코드로 변경하였습니다. 이러한 방식을 메서드 체이닝 이라합니다.

사용자 입력 받아보기

이번에는 컴퓨터가 만들어낸 랜덤한 숫자를 맞혀보는 게임을 한번 만들어봅시다. 먼저, 사용자는 1부터 50까지의 숫자를 입력할 수 있으며, 버튼을 누르면 입력된 숫자가 정답 숫자보다 큰지, 작은지 판단해서 알려주는 게임입니다.

이 게임예제를 통해 우리는 사용자의 입력값을 어떻게 받아올 수 있는지, 그리고 버튼을 눌렀을 때 어떻게 그 값을 처리해야 하는지를 볼 수 있습니다.

public class NumberGame extends JFrame{
	
	JLabel label;
	JButton btn;
	JTextField tf;
	
	int com; 

	public NumberGame() {
		Random rand = new Random();
		com = rand.nextInt(50) + 1;
		
		setSize(500,500);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setTitle("숫자 맞히기 게임");
		setLocationRelativeTo(rootPane);
		
		label = new JLabel("1~50 중 랜덤한 숫자를 맞춰보세요");
		label.setHorizontalAlignment(JLabel.CENTER);
		
        btn = new JButton("확\t인");
		tf = new JTextField();
        tf.setHorizontalAlignment(JTextField.CENTER);
		
		add(label,BorderLayout.NORTH);
		add(btn,BorderLayout.CENTER);
		add(tf,BorderLayout.SOUTH);
		
		setVisible(true);
		
		ActionListener li = new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				
				int result = Integer.valueOf(tf.getText());
				
				if(com > result) label.setText("UP");
				else if(com < result) label.setText("DOWN");
				else label.setText("정답!");
			}
		};
		btn.addActionListener(li);
	}
	public static void main(String[] args) {
		new NumberGame();	
	}
}
  1. 랜덤클래스를 사용하여 랜덤값을 변수에 저장시킵니다.

  2. setHorizontalAlignment() 메서드를 통해 들어간 내용이 중간정렬되게 해줍니다.

  3. getText() 메서드를 통해 TextField 에 작성된 문자열을 받아올 수 있습니다.

이렇게 해서 자바의 모든 수업이 마무리되었습니다. 마지막으로 했던 Swing 은 안드로이드로 넘어가기 위한 수업이기 때문에 자세한 부분은 다루지 않았습니다. 가볍게 느낌만 가지고 안드로이드를 접하는것이 좋겠습니다.

profile
Developer

0개의 댓글