Ver3.0 ๐ ์๋ฐ๋ก ๋ง๋๋ ๋ชจ๋๋ฅผ ์ํ ์ธ์

๐ ์๋ฐ๋ก ๋ง๋๋ ๋ชจ๋๋ฅผ ์ํ ์ธ์
์๋ , ์น๊ตฌ! ๐ ์ค๋์ ์ ๋ง ์ค์ํ์ง๋ง ๋ง์ ๊ฐ๋ฐ์๋ค์ด ๊ฐ๊ณผํ๊ธฐ ์ฌ์ด ์ฃผ์ ์ ๋ํด ์ด์ผ๊ธฐํด๋ณผ๊น ํด. ๋ฐ๋ก ์ ๊ทผ์ฑ(Accessibility)์ด์ผ. ํน์ "๋ด ์ฑ์ ๋ชจ๋ ์ฌ๋์ด ์ฌ์ฉํ ์ ์์๊น?"๋ผ๋ ์ง๋ฌธ์ ์ค์ค๋ก์๊ฒ ํด๋ณธ ์ ์์ด? ๐ค
์ ์ธ๊ณ ์ธ๊ตฌ์ ์ฝ 15%๊ฐ ์ด๋ค ํํ๋ก๋ ์ฅ์ ๋ฅผ ๊ฐ์ง๊ณ ์๋ค๊ณ ํด. ์ด๊ฑด ๋จ์ํ ์ซ์๊ฐ ์๋๋ผ, ์ฐ๋ฆฌ๊ฐ ๋ง๋๋ ์ํํธ์จ์ด๋ฅผ ์ฌ์ฉํ๊ณ ์ถ์ดํ๋ ์์ต ๋ช ์ ์ ์ฌ์ ์ฌ์ฉ์๋ฅผ ์๋ฏธํ์ง. ์๋ฐ ๊ฐ๋ฐ์๋ก์ ์ฐ๋ฆฌ๋ ์ด๋ค์ ์ํ ํฌ์ฉ์ ์ธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค ์ฑ ์์ด ์์ด. ๊ทธ๋ฆฌ๊ณ ๋คํํ๋ ์๋ฐ๋ ์ด๋ฅผ ์ํ ๊ฐ๋ ฅํ ๋๊ตฌ๋ค์ ์ ๊ณตํ๊ณ ์์ด! ๐ฏ
๐ฏ ์ ๊ทผ์ฑ์ด ๋ญ๊ธธ๋ ์ด๋ ๊ฒ ์ค์ํ ๊น?
์ ๊ทผ์ฑ(Accessibility, ์ค์ฌ์ a11y๋ผ๊ณ ๋ ํด)์ ๋จ์ํ "์ฅ์ ์ธ์ ์ํ ๊ธฐ๋ฅ"์ด ์๋์ผ. ์ด๊ฑด ๋ชจ๋ ์ฌ์ฉ์๊ฐ ๋๋ฑํ๊ฒ ์ ๋ณด์ ์ ๊ทผํ๊ณ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์๋๋ก ํ๋ ์ค๊ณ ์ฒ ํ์ด์ง. ๐
์๊ฐํด๋ด. ์๊ฐ ์ฅ์ ๊ฐ ์๋ ์ฌ๋์ด ์คํฌ๋ฆฐ ๋ฆฌ๋๋ฅผ ์ฌ์ฉํด์ ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์ฑ์ ์ฌ์ฉํ๋ ค๊ณ ํ ๋, ๋ฒํผ์ ์ ๋๋ก ๋ ๋ ์ด๋ธ์ด ์๋ค๋ฉด? ์ฒญ๊ฐ ์ฅ์ ๊ฐ ์๋ ์ฌ๋์ด ์ค๋์ค ์๋ฆผ๋ง์ผ๋ก ์ค์ํ ์ ๋ณด๋ฅผ ์ ๋ฌ๋ฐ๋๋ค๋ฉด? ์ด๋ ์ฅ์ ๋ก ๋ง์ฐ์ค ์ฌ์ฉ์ด ์ด๋ ค์ด ์ฌ๋์ด ํค๋ณด๋๋ง์ผ๋ก๋ ๋ชจ๋ ๊ธฐ๋ฅ์ ์ ๊ทผํ ์ ์๋ค๋ฉด?
Perceivable (์ธ์ง ๊ฐ๋ฅ): ์ ๋ณด์ UI ์ปดํฌ๋ํธ๋ ์ฌ์ฉ์๊ฐ ์ธ์งํ ์ ์์ด์ผ ํด
Operable (์กฐ์ ๊ฐ๋ฅ): UI ์ปดํฌ๋ํธ์ ๋ด๋น๊ฒ์ด์ ์ ์กฐ์ ๊ฐ๋ฅํด์ผ ํด
Understandable (์ดํด ๊ฐ๋ฅ): ์ ๋ณด์ UI ์กฐ์์ ์ดํด ๊ฐ๋ฅํด์ผ ํด
Robust (๊ฒฌ๊ณ ํจ): ์ฝํ ์ธ ๋ ๋ณด์กฐ ๊ธฐ์ ์ ํฌํจํ ๋ค์ํ ์ฌ์ฉ์ ์์ด์ ํธ์์ ํด์๋ ์ ์์ด์ผ ํด
๊ทธ๋ฆฌ๊ณ ์ฌ๊ธฐ์ ์ค์ํ ๊ฑด, ์ ๊ทผ์ฑ์ ๊ณ ๋ คํ ์ค๊ณ๋ ๋ชจ๋ ์ฌ์ฉ์์๊ฒ ๋ ๋์ ๊ฒฝํ์ ์ ๊ณตํ๋ค๋ ๊ฑฐ์ผ. ์๋ฅผ ๋ค์ด, ๋ช ํํ ๋ ์ด๋ธ๊ณผ ๊ตฌ์กฐํ๋ ๋ด๋น๊ฒ์ด์ ์ ์ฅ์ ๊ฐ ์๋ ์ฌ์ฉ์์๊ฒ๋ ๋ ์ง๊ด์ ์ด๊ณ ์ฌ์ฉํ๊ธฐ ์ฌ์ด ์ธํฐํ์ด์ค๋ฅผ ๋ง๋ค์ด์ฃผ์ง. ๐ฑ
โ ์๋ฐ์ ์ ๊ทผ์ฑ ์ง์: Java Accessibility API
์๋ฐ๋ JDK 1.2๋ถํฐ Java Accessibility API๋ฅผ ์ ๊ณตํ๊ณ ์์ด. ์ด API๋ javax.accessibility ํจํค์ง์ ํฌํจ๋์ด ์๊ณ , ์๋ฐ ์ ํ๋ฆฌ์ผ์ด์
์ด ๋ณด์กฐ ๊ธฐ์ (Assistive Technology, AT)๊ณผ ํต์ ํ ์ ์๊ฒ ํด์ฃผ๋ ๋ค๋ฆฌ ์ญํ ์ ํ์ง. ๐
๋ณด์กฐ ๊ธฐ์ ์ด๋ผ๊ณ ํ๋ฉด ์คํฌ๋ฆฐ ๋ฆฌ๋(JAWS, NVDA ๋ฑ), ํ๋ฉด ํ๋๊ธฐ, ์์ฑ ์ธ์ ์ํํธ์จ์ด ๊ฐ์ ๊ฒ๋ค์ ๋งํด. ์ด๋ฐ ๋๊ตฌ๋ค์ Accessibility API๋ฅผ ํตํด ์ ํ๋ฆฌ์ผ์ด์ ์ UI ์ปดํฌ๋ํธ ์ ๋ณด๋ฅผ ์ฝ๊ณ , ์ฌ์ฉ์์ ์ ๋ ฅ์ ์ ๋ฌํ๊ฒ ๋ผ.
๐ง ํต์ฌ ์ธํฐํ์ด์ค์ ํด๋์ค๋ค
Java Accessibility API์ ํต์ฌ์ ๋ช ๊ฐ์ง ์ค์ํ ์ธํฐํ์ด์ค์ ํด๋์ค๋ก ๊ตฌ์ฑ๋์ด ์์ด. ํ๋์ฉ ์ดํด๋ณผ๊น?
๋ชจ๋ ์ ๊ทผ ๊ฐ๋ฅํ ๊ฐ์ฒด๊ฐ ๊ตฌํํด์ผ ํ๋ ์ต์์ ์ธํฐํ์ด์ค์ผ. ๋จ ํ๋์ ๋ฉ์๋๋ง ์ ์ํ๊ณ ์์ด:
AccessibleContext getAccessibleContext()์ด ๋ฉ์๋๋ ๊ฐ์ฒด์ ์ ๊ทผ์ฑ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ AccessibleContext๋ฅผ ๋ฐํํด. Swing์ ๋ชจ๋ ์ปดํฌ๋ํธ๋ ์ด๋ฏธ ์ด ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๊ณ ์์ด์, ์ฐ๋ฆฌ๋ ๋ฐ๋ก ์ฌ์ฉํ ์ ์์ง! ๐
// Accessible ์ธํฐํ์ด์ค ์ฌ์ฉ ์์
JButton button = new JButton("ํด๋ฆญํ์ธ์");
Accessible accessible = button;
AccessibleContext context = accessible.getAccessibleContext();
// ์ ๊ทผ์ฑ ์ ๋ณด ์ถ๋ ฅ
System.out.println("์ญํ : " + context.getAccessibleRole());
System.out.println("์ด๋ฆ: " + context.getAccessibleName());
System.out.println("์ค๋ช
: " + context.getAccessibleDescription());
AccessibleContext๋ ์ ๊ทผ์ฑ ์ ๋ณด์ ์ค์ฌ์ด์ผ. ์ด ํด๋์ค๋ ์ปดํฌ๋ํธ์ ์ญํ , ์ด๋ฆ, ์ค๋ช , ์ํ ๋ฑ ๋ณด์กฐ ๊ธฐ์ ์ด ํ์๋ก ํ๋ ๋ชจ๋ ์ ๋ณด๋ฅผ ์ ๊ณตํด. ๊ฐ Swing ์ปดํฌ๋ํธ๋ ์์ ๋ง์ AccessibleContext ๊ตฌํ์ฒด๋ฅผ ๊ฐ์ง๊ณ ์์ด.
| ๋ฉ์๋ | ์ค๋ช | ํ์ฉ ์์ |
|---|---|---|
getAccessibleName() |
์ปดํฌ๋ํธ์ ์ด๋ฆ ๋ฐํ | ์คํฌ๋ฆฐ ๋ฆฌ๋๊ฐ "์ ์ฅ ๋ฒํผ"์ด๋ผ๊ณ ์ฝ์ด์ค |
getAccessibleDescription() |
์ปดํฌ๋ํธ์ ์์ธ ์ค๋ช ๋ฐํ | "ํ์ฌ ๋ฌธ์๋ฅผ ์ ์ฅํฉ๋๋ค" |
getAccessibleRole() |
์ปดํฌ๋ํธ์ ์ญํ ๋ฐํ | BUTTON, TEXT_FIELD, LABEL ๋ฑ |
getAccessibleStateSet() |
ํ์ฌ ์ํ ์งํฉ ๋ฐํ | ENABLED, FOCUSED, VISIBLE ๋ฑ |
getAccessibleParent() |
๋ถ๋ชจ ์ปดํฌ๋ํธ ๋ฐํ | ๊ณ์ธต ๊ตฌ์กฐ ํ์์ ์ฌ์ฉ |
getAccessibleChildrenCount() |
์์ ์ปดํฌ๋ํธ ๊ฐ์ ๋ฐํ | ์ปจํ ์ด๋์ ๋ด์ฉ๋ฌผ ํ์ |
๐ญ AccessibleRole: ์ปดํฌ๋ํธ์ ์ ์ฒด์ฑ
AccessibleRole์ ์ปดํฌ๋ํธ๊ฐ ๋ฌด์์ธ์ง๋ฅผ ์ ์ํ๋ ์ด๊ฑฐํ ํด๋์ค์ผ. ๋ฒํผ์ธ์ง, ํ ์คํธ ํ๋์ธ์ง, ์ฒดํฌ๋ฐ์ค์ธ์ง ๋ฑ์ ๋ํ๋ด์ง. ๋ณด์กฐ ๊ธฐ์ ์ ์ด ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ์ฌ์ฉ์์๊ฒ ์ ์ ํ ๋ฐฉ์์ผ๋ก ์ปดํฌ๋ํธ๋ฅผ ์ค๋ช ํด์ค. ๐ช
// ๋ค์ํ AccessibleRole ์์
JButton button = new JButton("ํ์ธ");
System.out.println(button.getAccessibleContext().getAccessibleRole());
// ์ถ๋ ฅ: push button
JTextField textField = new JTextField();
System.out.println(textField.getAccessibleContext().getAccessibleRole());
// ์ถ๋ ฅ: text
JCheckBox checkBox = new JCheckBox("๋์ํฉ๋๋ค");
System.out.println(checkBox.getAccessibleContext().getAccessibleRole());
// ์ถ๋ ฅ: check box
JLabel label = new JLabel("์ด๋ฆ:");
System.out.println(label.getAccessibleContext().getAccessibleRole());
// ์ถ๋ ฅ: label
๊ฐ ์ญํ ์ ๋ณด์กฐ ๊ธฐ์ ์๊ฒ "์ด ์ปดํฌ๋ํธ๋ฅผ ์ด๋ป๊ฒ ๋ค๋ค์ผ ํ๋์ง"์ ๋ํ ํํธ๋ฅผ ์ ๊ณตํด. ์๋ฅผ ๋ค์ด, PUSH_BUTTON ์ญํ ์ ๊ฐ์ง ์ปดํฌ๋ํธ๋ ํ์ฑํ๋ ์ ์๋ค๋ ๊ฑธ ์๋ฏธํ๊ณ , TEXT ์ญํ ์ ํ ์คํธ๋ฅผ ์ ๋ ฅ๋ฐ์ ์ ์๋ค๋ ๊ฑธ ๋ํ๋ด์ง.
๐ฆ AccessibleState: ์ปดํฌ๋ํธ์ ํ์ฌ ์ํ
AccessibleState๋ ์ปดํฌ๋ํธ์ ํ์ฌ ์ํ๋ฅผ ๋ํ๋ด๋ ํด๋์ค์ผ. ํ์ฑํ๋์ด ์๋์ง, ์ ํ๋์ด ์๋์ง, ํฌ์ปค์ค๋ฅผ ๋ฐ์๋์ง ๋ฑ์ ์ ๋ณด๋ฅผ ์ ๊ณตํด. ์ด ์ ๋ณด๋ AccessibleStateSet์ผ๋ก ๊ด๋ฆฌ๋๋๋ฐ, ํ๋์ ์ปดํฌ๋ํธ๊ฐ ์ฌ๋ฌ ์ํ๋ฅผ ๋์์ ๊ฐ์ง ์ ์๊ธฐ ๋๋ฌธ์ด์ผ. ๐จ
JButton button = new JButton("ํด๋ฆญ");
AccessibleContext context = button.getAccessibleContext();
AccessibleStateSet stateSet = context.getAccessibleStateSet();
// ์ํ ํ์ธ
if (stateSet.contains(AccessibleState.ENABLED)) {
System.out.println("๋ฒํผ์ด ํ์ฑํ๋์ด ์์ต๋๋ค");
}
if (stateSet.contains(AccessibleState.FOCUSED)) {
System.out.println("๋ฒํผ์ด ํฌ์ปค์ค๋ฅผ ๋ฐ์์ต๋๋ค");
}
if (stateSet.contains(AccessibleState.VISIBLE)) {
System.out.println("๋ฒํผ์ด ํ๋ฉด์ ๋ณด์
๋๋ค");
}
// ๋ฒํผ ๋นํ์ฑํ
button.setEnabled(false);
stateSet = context.getAccessibleStateSet();
if (!stateSet.contains(AccessibleState.ENABLED)) {
System.out.println("์ด์ ๋ฒํผ์ด ๋นํ์ฑํ๋์์ต๋๋ค");
}
AccessibleStateSet์ ์ปดํฌ๋ํธ์ ํ์ฌ ์ํ๋ฅผ ๋ฐ์ํ๋ ์ค๋ ์ท์ด์ผ. ์ํ๊ฐ ๋ณ๊ฒฝ๋๋ฉด ๋ค์ getAccessibleStateSet()์ ํธ์ถํด์ ์ต์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์์ผ ํด. ์ด์ ์ ๊ฐ์ ธ์จ StateSet ๊ฐ์ฒด๋ ์๋์ผ๋ก ์ ๋ฐ์ดํธ๋์ง ์์!
๐จ ์ค์ ! ์ ๊ทผ ๊ฐ๋ฅํ Swing ์ ํ๋ฆฌ์ผ์ด์ ๋ง๋ค๊ธฐ
์ด๋ก ์ ์ถฉ๋ถํ ๋ฐฐ์ ์ผ๋, ์ด์ ์ค์ ๋ก ์ ๊ทผ ๊ฐ๋ฅํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค์ด๋ณผ๊น? ๊ฐ๋จํ ๋ก๊ทธ์ธ ํผ์ ์์ ๋ก ์ฌ์ฉํด๋ณผ๊ฒ. ์ด ๊ณผ์ ์์ ์ ๊ทผ์ฑ์ ๊ณ ๋ คํ์ง ์์ ์ฝ๋์ ๊ณ ๋ คํ ์ฝ๋๋ฅผ ๋น๊ตํด๋ณด๋ฉด์, ์ด๋ค ์ฐจ์ด๊ฐ ์๋์ง ํ์ธํด๋ณด์! ๐
โ ์ ๊ทผ์ฑ์ ๊ณ ๋ คํ์ง ์์ ๋์ ์
import javax.swing.*;
import java.awt.*;
public class BadLoginForm extends JFrame {
public BadLoginForm() {
setTitle("๋ก๊ทธ์ธ");
setSize(400, 200);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel(new GridLayout(3, 2, 10, 10));
// ๋ ์ด๋ธ๊ณผ ํ๋๋ฅผ ๋จ์ํ ๋์ด
panel.add(new JLabel("์์ด๋"));
JTextField userField = new JTextField();
panel.add(userField);
panel.add(new JLabel("๋น๋ฐ๋ฒํธ"));
JPasswordField passField = new JPasswordField();
panel.add(passField);
// ๋ฒํผ์ ์์ด์ฝ๋ง ์ฌ์ฉ
JButton loginBtn = new JButton("โ");
JButton cancelBtn = new JButton("โ");
panel.add(loginBtn);
panel.add(cancelBtn);
add(panel);
setVisible(true);
}
}
์ ์ฝ๋์ ๋ฌธ์ ์ ์ด ๋ญ๊น? ๐ค
1๏ธโฃ ๋ ์ด๋ธ๊ณผ ์ ๋ ฅ ํ๋์ ์ฐ๊ฒฐ์ด ๋ช ์์ ์ด์ง ์์ ์คํฌ๋ฆฐ ๋ฆฌ๋๊ฐ ์ด๋ค ํ๋์ธ์ง ์๋ ค์ฃผ์ง ๋ชปํจ
2๏ธโฃ ๋ฒํผ์ ์๋ฏธ ์๋ ํ ์คํธ๊ฐ ์์ด์ ์์ด์ฝ์ ์๋ฏธ๋ฅผ ์๊ฐ์ ์ผ๋ก ํ์ธํ ์ ์๋ ์ฌ์ฉ์๋ ๋ฒํผ์ ๊ธฐ๋ฅ์ ์ ์ ์์
3๏ธโฃ ์ ๊ทผ์ฑ ์ค๋ช (Accessible Description)์ด ์์ด์ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์ ๊ณตํ์ง ๋ชปํจ
4๏ธโฃ ํค๋ณด๋ ๋จ์ถํค๋ ๋๋ชจ๋(Mnemonic)์ด ์ค์ ๋์ง ์์ ํค๋ณด๋๋ง์ผ๋ก ํจ์จ์ ์ธ ํ์์ด ์ด๋ ค์
โ ์ ๊ทผ์ฑ์ ๊ณ ๋ คํ ์ข์ ์
์ด์ ๊ฐ์ ๋ก๊ทธ์ธ ํผ์ ์ ๊ทผ์ฑ์ ๊ณ ๋ คํด์ ๋ค์ ๋ง๋ค์ด๋ณด์:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class GoodLoginForm extends JFrame {
public GoodLoginForm() {
setTitle("๋ก๊ทธ์ธ");
setSize(400, 250);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
gbc.fill = GridBagConstraints.HORIZONTAL;
// ์์ด๋ ๋ ์ด๋ธ๊ณผ ํ๋
JLabel userLabel = new JLabel("์์ด๋(I):");
userLabel.setDisplayedMnemonic('I'); // Alt+I๋ก ์ ๊ทผ ๊ฐ๋ฅ
gbc.gridx = 0;
gbc.gridy = 0;
panel.add(userLabel, gbc);
JTextField userField = new JTextField(20);
userLabel.setLabelFor(userField); // ๋ ์ด๋ธ๊ณผ ํ๋ ์ฐ๊ฒฐ
// ์ ๊ทผ์ฑ ์ ๋ณด ์ค์
userField.getAccessibleContext().setAccessibleName("์์ด๋ ์
๋ ฅ");
userField.getAccessibleContext().setAccessibleDescription(
"๋ก๊ทธ์ธํ ์์ด๋๋ฅผ ์
๋ ฅํ์ธ์. ์๋ฌธ๊ณผ ์ซ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.");
gbc.gridx = 1;
gbc.gridy = 0;
panel.add(userField, gbc);
// ๋น๋ฐ๋ฒํธ ๋ ์ด๋ธ๊ณผ ํ๋
JLabel passLabel = new JLabel("๋น๋ฐ๋ฒํธ(P):");
passLabel.setDisplayedMnemonic('P'); // Alt+P๋ก ์ ๊ทผ ๊ฐ๋ฅ
gbc.gridx = 0;
gbc.gridy = 1;
panel.add(passLabel, gbc);
JPasswordField passField = new JPasswordField(20);
passLabel.setLabelFor(passField); // ๋ ์ด๋ธ๊ณผ ํ๋ ์ฐ๊ฒฐ
// ์ ๊ทผ์ฑ ์ ๋ณด ์ค์
passField.getAccessibleContext().setAccessibleName("๋น๋ฐ๋ฒํธ ์
๋ ฅ");
passField.getAccessibleContext().setAccessibleDescription(
"๋ก๊ทธ์ธํ ๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํ์ธ์. ๋์๋ฌธ์๋ฅผ ๊ตฌ๋ถํฉ๋๋ค.");
gbc.gridx = 1;
gbc.gridy = 1;
panel.add(passField, gbc);
// ๋ฒํผ ํจ๋
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
// ๋ก๊ทธ์ธ ๋ฒํผ
JButton loginBtn = new JButton("๋ก๊ทธ์ธ(L)");
loginBtn.setMnemonic('L'); // Alt+L๋ก ์ ๊ทผ ๊ฐ๋ฅ
loginBtn.getAccessibleContext().setAccessibleDescription(
"์
๋ ฅํ ์ ๋ณด๋ก ๋ก๊ทธ์ธ์ ์๋ํฉ๋๋ค");
// ์ํฐ ํค๋ก๋ ๋ก๊ทธ์ธ ๊ฐ๋ฅํ๋๋ก
getRootPane().setDefaultButton(loginBtn);
loginBtn.addActionListener(e -> {
// ๋ก๊ทธ์ธ ๋ก์ง
System.out.println("๋ก๊ทธ์ธ ์๋");
});
// ์ทจ์ ๋ฒํผ
JButton cancelBtn = new JButton("์ทจ์(C)");
cancelBtn.setMnemonic('C'); // Alt+C๋ก ์ ๊ทผ ๊ฐ๋ฅ
cancelBtn.getAccessibleContext().setAccessibleDescription(
"๋ก๊ทธ์ธ์ ์ทจ์ํ๊ณ ์ฐฝ์ ๋ซ์ต๋๋ค");
// ESC ํค๋ก๋ ์ทจ์ ๊ฐ๋ฅํ๋๋ก
cancelBtn.addActionListener(e -> dispose());
KeyStroke escapeKey = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
cancelBtn.registerKeyboardAction(
e -> dispose(),
escapeKey,
JComponent.WHEN_IN_FOCUSED_WINDOW
);
buttonPanel.add(loginBtn);
buttonPanel.add(cancelBtn);
gbc.gridx = 0;
gbc.gridy = 2;
gbc.gridwidth = 2;
panel.add(buttonPanel, gbc);
// ์ํ ํ์ ๋ ์ด๋ธ (์คํฌ๋ฆฐ ๋ฆฌ๋๊ฐ ์ฝ์ ์ ์๋๋ก)
JLabel statusLabel = new JLabel(" ");
statusLabel.getAccessibleContext().setAccessibleName("์ํ ๋ฉ์์ง");
gbc.gridy = 3;
panel.add(statusLabel, gbc);
add(panel);
// ์ด๊ธฐ ํฌ์ปค์ค ์ค์
addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
userField.requestFocusInWindow();
}
});
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new GoodLoginForm());
}
}
์! ์ฝ๋๊ฐ ํจ์ฌ ๊ธธ์ด์ก์ง? ํ์ง๋ง ์ด์ ์ด ๋ก๊ทธ์ธ ํผ์ ์ง์ ์ผ๋ก ๋ชจ๋ ์ฌ์ฉ์๊ฐ ์ฌ์ฉํ ์ ์๋ ํผ์ด ๋์์ด. ํ๋์ฉ ๊ฐ์ ๋ ์ ์ ์ดํด๋ณด์:
1. ๋ ์ด๋ธ๊ณผ ํ๋์ ๋ช ์์ ์ฐ๊ฒฐ
setLabelFor() ๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ ๋ ์ด๋ธ๊ณผ ์
๋ ฅ ํ๋๋ฅผ ์ฐ๊ฒฐํ์ด. ์ด์ ์คํฌ๋ฆฐ ๋ฆฌ๋๋ "์์ด๋ ์
๋ ฅ ํธ์ง ์์"๋ผ๊ณ ์ ํํ๊ฒ ์ฝ์ด์ค ์ ์์ง.2. ๋๋ชจ๋(Mnemonic) ์ค์
Alt+I, Alt+P, Alt+L, Alt+C ๊ฐ์ ํค๋ณด๋ ๋จ์ถํค๋ฅผ ์ค์ ํ์ด. ๋ง์ฐ์ค ์์ด๋ ๋น ๋ฅด๊ฒ ์ํ๋ ์ปดํฌ๋ํธ๋ก ์ด๋ํ ์ ์์ง.
3. ์ ๊ทผ์ฑ ์ด๋ฆ๊ณผ ์ค๋ช
๊ฐ ์ปดํฌ๋ํธ์ ๋ช ํํ ์ด๋ฆ๊ณผ ์์ธํ ์ค๋ช ์ ์ถ๊ฐํ์ด. ๋ณด์กฐ ๊ธฐ์ ์ด ์ฌ์ฉ์์๊ฒ ๋ ๋ง์ ์ ๋ณด๋ฅผ ์ ๊ณตํ ์ ์๊ฒ ๋์ง.
4. ํค๋ณด๋ ๋ด๋น๊ฒ์ด์
Enter ํค๋ก ๋ก๊ทธ์ธ, ESC ํค๋ก ์ทจ์๊ฐ ๊ฐ๋ฅํ๋๋ก ํ์ด. ํค๋ณด๋๋ง์ผ๋ก๋ ๋ชจ๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์์ด.
5. ์ด๊ธฐ ํฌ์ปค์ค ์ค์
์ฐฝ์ด ์ด๋ฆฌ๋ฉด ์๋์ผ๋ก ์์ด๋ ์ ๋ ฅ ํ๋์ ํฌ์ปค์ค๊ฐ ๊ฐ๋๋ก ํ์ด. ์ฌ์ฉ์๊ฐ ๋ฐ๋ก ์ ๋ ฅ์ ์์ํ ์ ์์ง.
๐ ๊ณ ๊ธ ์ ๊ทผ์ฑ ๊ธฐ๋ฅ ํ์ฉํ๊ธฐ
๊ธฐ๋ณธ์ ์ธ ์ ๊ทผ์ฑ ์ค์ ์ ๋์ด์, Java Accessibility API๋ ๋ ์ธ๋ฐํ ์ ์ด๋ฅผ ์ํ ๋ค์ํ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํด. ์ด๋ฐ ๊ณ ๊ธ ๊ธฐ๋ฅ๋ค์ ํ์ฉํ๋ฉด ์ ๋ง ๊ฐ๋ ฅํ ์ ๊ทผ์ฑ์ ๊ตฌํํ ์ ์์ด! ๐ช
๐ AccessibleText: ํ ์คํธ ์ ๊ทผ์ฑ
AccessibleText ์ธํฐํ์ด์ค๋ ํ ์คํธ ์ปดํฌ๋ํธ์ ๋ด์ฉ์ ์ ๊ทผํ ์ ์๊ฒ ํด์ค. ์คํฌ๋ฆฐ ๋ฆฌ๋๊ฐ ํ ์คํธ๋ฅผ ์ฝ๊ณ , ์ปค์ ์์น๋ฅผ ํ์ ํ๊ณ , ์ ํ๋ ํ ์คํธ๋ฅผ ํ์ธํ ์ ์๊ฒ ํ์ง.
import javax.accessibility.*;
import javax.swing.*;
public class AccessibleTextExample {
public static void main(String[] args) {
JTextArea textArea = new JTextArea("์๋
ํ์ธ์!\n์ ๊ทผ์ฑ ํ
์คํธ์
๋๋ค.");
AccessibleContext context = textArea.getAccessibleContext();
AccessibleText accessibleText = context.getAccessibleText();
if (accessibleText != null) {
// ์ ์ฒด ํ
์คํธ ๊ธธ์ด
int charCount = accessibleText.getCharCount();
System.out.println("์ ์ฒด ๋ฌธ์ ์: " + charCount);
// ํน์ ์์น์ ๋ฌธ์
char charAt5 = accessibleText.getAtIndex(
AccessibleText.CHARACTER, 5);
System.out.println("5๋ฒ์งธ ๋ฌธ์: " + charAt5);
// ํน์ ์์น์ ๋จ์ด
String wordAt5 = accessibleText.getAtIndex(
AccessibleText.WORD, 5);
System.out.println("5๋ฒ์งธ ์์น์ ๋จ์ด: " + wordAt5);
// ์ปค์ ์์น
int caretPos = accessibleText.getCaretPosition();
System.out.println("ํ์ฌ ์ปค์ ์์น: " + caretPos);
// ์ ํ๋ ํ
์คํธ
String selectedText = accessibleText.getSelectedText();
if (selectedText != null) {
System.out.println("์ ํ๋ ํ
์คํธ: " + selectedText);
}
// ํน์ ๋ฒ์์ ํ
์คํธ
String textBetween = accessibleText.getTextRange(0, 5);
System.out.println("0~5 ๋ฒ์์ ํ
์คํธ: " + textBetween);
}
}
}
์ด ๊ธฐ๋ฅ์ ํนํ ํ ์คํธ ํธ์ง๊ธฐ๋ ๋ฌธ์ ๋ทฐ์ด๋ฅผ ๋ง๋ค ๋ ์ค์ํด. ์คํฌ๋ฆฐ ๋ฆฌ๋๊ฐ ๋ฌธ์๋ฅผ ํจ๊ณผ์ ์ผ๋ก ํ์ํ ์ ์๊ฒ ํด์ฃผ๊ฑฐ๋ . ๐
๐ฌ AccessibleAction: ๋์ ์ ๊ทผ์ฑ
AccessibleAction ์ธํฐํ์ด์ค๋ ์ปดํฌ๋ํธ๊ฐ ์ํํ ์ ์๋ ๋์๋ค์ ์ ์ํด. ๋ฒํผ ํด๋ฆญ, ๋ฉ๋ด ์ ํ, ๋งํฌ ํ์ฑํ ๊ฐ์ ๋์๋ค์ ๋ณด์กฐ ๊ธฐ์ ์ด ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ์คํํ ์ ์๊ฒ ํ์ง.
import javax.accessibility.*;
import javax.swing.*;
public class AccessibleActionExample extends JFrame {
public AccessibleActionExample() {
setTitle("๋์ ์ ๊ทผ์ฑ ์์ ");
setSize(400, 300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton button = new JButton("์ ์ฅ");
button.addActionListener(e -> {
System.out.println("ํ์ผ์ด ์ ์ฅ๋์์ต๋๋ค!");
});
// AccessibleAction ์ ๋ณด ํ์ธ
AccessibleContext context = button.getAccessibleContext();
AccessibleAction action = context.getAccessibleAction();
if (action != null) {
int actionCount = action.getAccessibleActionCount();
System.out.println("์ฌ์ฉ ๊ฐ๋ฅํ ๋์ ์: " + actionCount);
for (int i = 0; i < actionCount; i++) {
String actionDesc = action.getAccessibleActionDescription(i);
System.out.println("๋์ " + i + ": " + actionDesc);
}
// ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ๋ฒํผ ํด๋ฆญ
// ๋ณด์กฐ ๊ธฐ์ ์ด ์ด๋ ๊ฒ ๋ฒํผ์ ํ์ฑํํ ์ ์์ด
JButton testButton = new JButton("๋์ ํ
์คํธ");
testButton.addActionListener(e -> {
boolean success = action.doAccessibleAction(0);
System.out.println("๋์ ์คํ " +
(success ? "์ฑ๊ณต" : "์คํจ"));
});
JPanel panel = new JPanel();
panel.add(button);
panel.add(testButton);
add(panel);
}
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new AccessibleActionExample());
}
}
๐ข AccessibleValue: ๊ฐ ์ ๊ทผ์ฑ
AccessibleValue ์ธํฐํ์ด์ค๋ ์ซ์ ๊ฐ์ ๊ฐ์ง ์ปดํฌ๋ํธ(์ฌ๋ผ์ด๋, ์คํผ๋, ํ๋ก๊ทธ๋ ์ค ๋ฐ ๋ฑ)์ ๊ฐ์ ์ ๊ทผํ๊ณ ๋ณ๊ฒฝํ ์ ์๊ฒ ํด์ค.
import javax.accessibility.*;
import javax.swing.*;
public class AccessibleValueExample extends JFrame {
public AccessibleValueExample() {
setTitle("๊ฐ ์ ๊ทผ์ฑ ์์ ");
setSize(400, 200);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
// ์ฌ๋ผ์ด๋ ์์ฑ
JSlider slider = new JSlider(0, 100, 50);
slider.setMajorTickSpacing(25);
slider.setMinorTickSpacing(5);
slider.setPaintTicks(true);
slider.setPaintLabels(true);
// ์ ๊ทผ์ฑ ์ ๋ณด ์ค์
AccessibleContext context = slider.getAccessibleContext();
context.setAccessibleName("๋ณผ๋ฅจ ์กฐ์ ");
context.setAccessibleDescription(
"0์์ 100 ์ฌ์ด์ ๊ฐ์ผ๋ก ๋ณผ๋ฅจ์ ์กฐ์ ํฉ๋๋ค");
// AccessibleValue ์ฌ์ฉ
AccessibleValue accessibleValue = context.getAccessibleValue();
if (accessibleValue != null) {
// ํ์ฌ ๊ฐ
Number currentValue = accessibleValue.getCurrentAccessibleValue();
System.out.println("ํ์ฌ ๊ฐ: " + currentValue);
// ์ต์๊ฐ
Number minValue = accessibleValue.getMinimumAccessibleValue();
System.out.println("์ต์๊ฐ: " + minValue);
// ์ต๋๊ฐ
Number maxValue = accessibleValue.getMaximumAccessibleValue();
System.out.println("์ต๋๊ฐ: " + maxValue);
// ๊ฐ ๋ณ๊ฒฝ ๋ฒํผ
JButton setButton = new JButton("75๋ก ์ค์ ");
setButton.addActionListener(e -> {
boolean success = accessibleValue.setCurrentAccessibleValue(75);
System.out.println("๊ฐ ์ค์ " +
(success ? "์ฑ๊ณต" : "์คํจ"));
});
panel.add(slider);
panel.add(setButton);
}
add(panel);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new AccessibleValueExample());
}
}
์ด๋ฐ ์ธํฐํ์ด์ค๋ค์ ํ์ฉํ๋ฉด ๋ณด์กฐ ๊ธฐ์ ์ด ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ชจ๋ ์ธก๋ฉด์ ์ ๊ทผํ ์ ์๊ฒ ๋ผ. ์ฌ์ฉ์๋ ์์ ์๊ฒ ํธํ ๋ฐฉ์์ผ๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ์ดํ ์ ์์ง! ๐ฎ
๐ AccessibleTable: ํ ์ด๋ธ ์ ๊ทผ์ฑ
ํ ์ด๋ธ์ ๋ณต์กํ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ํํํ๊ธฐ ๋๋ฌธ์ ์ ๊ทผ์ฑ ์ธก๋ฉด์์ ํน๋ณํ ์ฃผ์๊ฐ ํ์ํด. AccessibleTable ์ธํฐํ์ด์ค๋ ํ ์ด๋ธ์ ๊ตฌ์กฐ์ ๋ด์ฉ์ ๋ณด์กฐ ๊ธฐ์ ์ด ์ดํดํ ์ ์๊ฒ ํด์ค.
import javax.accessibility.*;
import javax.swing.*;
import javax.swing.table.*;
public class AccessibleTableExample extends JFrame {
public AccessibleTableExample() {
setTitle("ํ
์ด๋ธ ์ ๊ทผ์ฑ ์์ ");
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// ํ
์ด๋ธ ๋ฐ์ดํฐ
String[] columnNames = {"์ด๋ฆ", "๋์ด", "์ง์
"};
Object[][] data = {
{"๊น์ฒ ์", 28, "๊ฐ๋ฐ์"},
{"์ด์ํฌ", 32, "๋์์ด๋"},
{"๋ฐ๋ฏผ์", 25, "๊ธฐํ์"}
};
JTable table = new JTable(data, columnNames);
// ์ ๊ทผ์ฑ ์ ๋ณด ์ค์
AccessibleContext context = table.getAccessibleContext();
context.setAccessibleName("์ง์ ๋ชฉ๋ก");
context.setAccessibleDescription(
"์ง์๋ค์ ์ด๋ฆ, ๋์ด, ์ง์
์ ๋ณด๋ฅผ ๋ด์ ํ
์ด๋ธ์
๋๋ค");
// ๊ฐ ์ด์ ๋ํ ์ค๋ช
์ถ๊ฐ
TableColumnModel columnModel = table.getColumnModel();
columnModel.getColumn(0).setHeaderValue("์ด๋ฆ");
columnModel.getColumn(1).setHeaderValue("๋์ด");
columnModel.getColumn(2).setHeaderValue("์ง์
");
// AccessibleTable ์ ๋ณด ํ์ธ
AccessibleTable accessibleTable = context.getAccessibleTable();
if (accessibleTable != null) {
System.out.println("ํ ์: " +
accessibleTable.getAccessibleRowCount());
System.out.println("์ด ์: " +
accessibleTable.getAccessibleColumnCount());
// ํน์ ์
์ ์ค๋ช
Accessible cellAccessible =
accessibleTable.getAccessibleAt(0, 0);
if (cellAccessible != null) {
AccessibleContext cellContext =
cellAccessible.getAccessibleContext();
System.out.println("(0,0) ์
๋ด์ฉ: " +
cellContext.getAccessibleName());
}
// ์ด ํค๋ ์ ๋ณด
AccessibleTable columnHeader =
accessibleTable.getAccessibleColumnHeader();
if (columnHeader != null) {
System.out.println("์ด ํค๋๊ฐ ์ค์ ๋์ด ์์ต๋๋ค");
}
}
JScrollPane scrollPane = new JScrollPane(table);
add(scrollPane);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new AccessibleTableExample());
}
}
โข ํญ์ ๋ช ํํ ์ด ํค๋๋ฅผ ์ ๊ณตํ์ธ์
โข ๋ณต์กํ ํ ์ด๋ธ์ ๊ฒฝ์ฐ ์์ฝ ์ ๋ณด๋ฅผ AccessibleDescription์ ์ถ๊ฐํ์ธ์
โข ์ ๋ณํฉ์ ์ฌ์ฉํ ๋๋ ํนํ ์ฃผ์ํ์ธ์ - ๋ณด์กฐ ๊ธฐ์ ์ด ๊ตฌ์กฐ๋ฅผ ์ดํดํ๊ธฐ ์ด๋ ค์ธ ์ ์์ด์
โข ๊ฐ๋ฅํ๋ฉด ํ ์ด๋ธ ์บก์ ์ ์ถ๊ฐํ์ธ์
๐ฏ ์ปค์คํ ์ปดํฌ๋ํธ์ ์ ๊ทผ์ฑ ๊ตฌํ
๋๋ก๋ ๊ธฐ๋ณธ Swing ์ปดํฌ๋ํธ๋ก๋ ๋ถ์กฑํด์ ์ปค์คํ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด์ผ ํ ๋๊ฐ ์์ด. ์ด๋ด ๋๋ ์ ๊ทผ์ฑ์ ์์ผ๋ฉด ์ ๋ผ! ์ปค์คํ ์ปดํฌ๋ํธ์ ์ ๊ทผ์ฑ์ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์. ๐ ๏ธ
์ปค์คํ
์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ๋๋ AccessibleContext๋ฅผ ์ง์ ๊ตฌํํด์ผ ํด. ๋ณต์กํด ๋ณด์ด์ง๋ง, ๋จ๊ณ๋ณ๋ก ๋ฐ๋ผํ๋ฉด ์ด๋ ต์ง ์์!
๐จ ์ปค์คํ ์์ ์ ํ๊ธฐ ๋ง๋ค๊ธฐ
๊ฐ๋จํ ์์ ์ ํ๊ธฐ๋ฅผ ๋ง๋ค๋ฉด์ ์ ๊ทผ์ฑ์ ๊ตฌํํด๋ณด์:
import javax.accessibility.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class AccessibleColorPicker extends JComponent
implements Accessible {
private Color selectedColor = Color.RED;
private Color[] colors = {
Color.RED, Color.ORANGE, Color.YELLOW,
Color.GREEN, Color.BLUE, Color.MAGENTA
};
private int selectedIndex = 0;
public AccessibleColorPicker() {
setPreferredSize(new Dimension(300, 50));
setFocusable(true);
// ๋ง์ฐ์ค ํด๋ฆญ ์ฒ๋ฆฌ
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
int colorWidth = getWidth() / colors.length;
int clickedIndex = e.getX() / colorWidth;
if (clickedIndex >= 0 && clickedIndex < colors.length) {
setSelectedIndex(clickedIndex);
}
}
});
// ํค๋ณด๋ ๋ด๋น๊ฒ์ด์
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
if (selectedIndex > 0) {
setSelectedIndex(selectedIndex - 1);
}
break;
case KeyEvent.VK_RIGHT:
if (selectedIndex < colors.length - 1) {
setSelectedIndex(selectedIndex + 1);
}
break;
case KeyEvent.VK_HOME:
setSelectedIndex(0);
break;
case KeyEvent.VK_END:
setSelectedIndex(colors.length - 1);
break;
}
}
});
// ํฌ์ปค์ค ์๊ฐํ
addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
repaint();
}
@Override
public void focusLost(FocusEvent e) {
repaint();
}
});
}
private void setSelectedIndex(int index) {
selectedIndex = index;
selectedColor = colors[index];
repaint();
// ์ ๊ทผ์ฑ ์ด๋ฒคํธ ๋ฐ์
firePropertyChange(
AccessibleContext.ACCESSIBLE_VALUE_PROPERTY,
null,
selectedColor
);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int colorWidth = getWidth() / colors.length;
// ๊ฐ ์์ ๊ทธ๋ฆฌ๊ธฐ
for (int i = 0; i < colors.length; i++) {
g.setColor(colors[i]);
g.fillRect(i * colorWidth, 0, colorWidth, getHeight());
// ์ ํ๋ ์์ ํ์
if (i == selectedIndex) {
g.setColor(Color.WHITE);
g.drawRect(i * colorWidth + 2, 2,
colorWidth - 4, getHeight() - 4);
g.drawRect(i * colorWidth + 3, 3,
colorWidth - 6, getHeight() - 6);
}
}
// ํฌ์ปค์ค ํ์
if (hasFocus()) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(2,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10, new float[]{5, 5}, 0));
g2d.drawRect(1, 1, getWidth() - 2, getHeight() - 2);
}
}
public Color getSelectedColor() {
return selectedColor;
}
// AccessibleContext ๊ตฌํ
@Override
public AccessibleContext getAccessibleContext() {
if (accessibleContext == null) {
accessibleContext = new AccessibleColorPicker();
}
return accessibleContext;
}
// ๋ด๋ถ AccessibleContext ํด๋์ค
protected class AccessibleColorPicker extends AccessibleJComponent
implements AccessibleValue {
@Override
public AccessibleRole getAccessibleRole() {
return AccessibleRole.SLIDER;
}
@Override
public AccessibleStateSet getAccessibleStateSet() {
AccessibleStateSet states = super.getAccessibleStateSet();
if (hasFocus()) {
states.add(AccessibleState.FOCUSED);
}
if (isEnabled()) {
states.add(AccessibleState.ENABLED);
}
if (isVisible()) {
states.add(AccessibleState.VISIBLE);
}
states.add(AccessibleState.FOCUSABLE);
return states;
}
@Override
public String getAccessibleName() {
String name = super.getAccessibleName();
if (name == null) {
name = "์์ ์ ํ๊ธฐ";
}
return name;
}
@Override
public String getAccessibleDescription() {
String desc = super.getAccessibleDescription();
if (desc == null) {
desc = "ํ์ดํ ํค๋ก ์์์ ์ ํํ์ธ์. " +
"ํ์ฌ ์ ํ: " + getColorName(selectedColor);
}
return desc;
}
// AccessibleValue ๊ตฌํ
@Override
public Number getCurrentAccessibleValue() {
return selectedIndex;
}
@Override
public boolean setCurrentAccessibleValue(Number n) {
int newIndex = n.intValue();
if (newIndex >= 0 && newIndex < colors.length) {
setSelectedIndex(newIndex);
return true;
}
return false;
}
@Override
public Number getMinimumAccessibleValue() {
return 0;
}
@Override
public Number getMaximumAccessibleValue() {
return colors.length - 1;
}
@Override
public AccessibleValue getAccessibleValue() {
return this;
}
}
private String getColorName(Color color) {
if (color.equals(Color.RED)) return "๋นจ๊ฐ";
if (color.equals(Color.ORANGE)) return "์ฃผํฉ";
if (color.equals(Color.YELLOW)) return "๋
ธ๋";
if (color.equals(Color.GREEN)) return "์ด๋ก";
if (color.equals(Color.BLUE)) return "ํ๋";
if (color.equals(Color.MAGENTA)) return "์์ฃผ";
return "์ ์ ์์";
}
// ํ
์คํธ์ฉ ๋ฉ์ธ ๋ฉ์๋
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("์ ๊ทผ ๊ฐ๋ฅํ ์์ ์ ํ๊ธฐ");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
AccessibleColorPicker picker = new AccessibleColorPicker();
picker.getAccessibleContext().setAccessibleName("๋ฐฐ๊ฒฝ์ ์ ํ");
picker.getAccessibleContext().setAccessibleDescription(
"๋ฌธ์์ ๋ฐฐ๊ฒฝ์์ ์ ํํฉ๋๋ค. " +
"์ข์ฐ ํ์ดํ ํค๋ก ์์์ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค.");
JLabel label = new JLabel("์ ํ๋ ์์: ๋นจ๊ฐ");
label.setHorizontalAlignment(SwingConstants.CENTER);
picker.addPropertyChangeListener(
AccessibleContext.ACCESSIBLE_VALUE_PROPERTY,
e -> {
Color color = picker.getSelectedColor();
label.setText("์ ํ๋ ์์: " +
picker.getColorName(color));
label.setForeground(color);
}
);
JPanel panel = new JPanel(new BorderLayout(10, 10));
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
panel.add(picker, BorderLayout.CENTER);
panel.add(label, BorderLayout.SOUTH);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
์! ์ฝ๋๊ฐ ๊ฝค ๊ธธ์ง? ํ์ง๋ง ์ด์ ์ฐ๋ฆฌ์ ์ปค์คํ ์์ ์ ํ๊ธฐ๋:
๐ฏ ํค๋ณด๋ ๋ด๋น๊ฒ์ด์ : ํ์ดํ ํค, Home, End ํค๋ก ์์ ํ ์ ์ด
๐ฏ ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ง์: ํ์ฌ ์ ํ๋ ์์์ ์์ฑ์ผ๋ก ์๋ด
๐ฏ ํฌ์ปค์ค ํ์: ํค๋ณด๋ ์ฌ์ฉ์๋ฅผ ์ํ ๋ช ํํ ์๊ฐ์ ํผ๋๋ฐฑ
๐ฏ AccessibleValue ๊ตฌํ: ๋ณด์กฐ ๊ธฐ์ ์ด ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ผ๋ก ๊ฐ ๋ณ๊ฒฝ ๊ฐ๋ฅ
๐ฏ ์ํ ๋ณ๊ฒฝ ์๋ฆผ: PropertyChangeEvent๋ฅผ ํตํด ๋ณ๊ฒฝ์ฌํญ ์ ๋ฌ
์ด๋ฐ ์์ผ๋ก ์ปค์คํ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ๋๋ ์ ๊ทผ์ฑ์ ๊ณ ๋ คํ๋ฉด, ์ ๋ง ๋ชจ๋ ์ฌ์ฉ์๊ฐ ์ฌ์ฉํ ์ ์๋ ์ปดํฌ๋ํธ๊ฐ ๋๋ ๊ฑฐ์ผ! ๐
๐งช ์ ๊ทผ์ฑ ํ ์คํธํ๊ธฐ
์ ๊ทผ์ฑ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ค๋ฉด, ์ด์ ์ ๋๋ก ์๋ํ๋์ง ํ ์คํธํด์ผ ํด. ์ ๊ทผ์ฑ ํ ์คํธ๋ ์ผ๋ฐ์ ์ธ ๊ธฐ๋ฅ ํ ์คํธ๋งํผ์ด๋ ์ค์ํด! ๐ฌ
๐ ๏ธ Java Access Bridge ํ์ฉํ๊ธฐ
Java Access Bridge๋ ์๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ Windows ๋ณด์กฐ ๊ธฐ์ ์ฌ์ด์ ๋ค๋ฆฌ ์ญํ ์ ํด. ์ด๊ฑธ ํ์ฑํํ๋ฉด JAWS, NVDA ๊ฐ์ ์คํฌ๋ฆฐ ๋ฆฌ๋๊ฐ ์๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฝ์ ์ ์๊ฒ ๋ผ.
Windows์์:
1. ๋ช ๋ น ํ๋กฌํํธ๋ฅผ ๊ด๋ฆฌ์ ๊ถํ์ผ๋ก ์คํ
2. JDK bin ๋๋ ํ ๋ฆฌ๋ก ์ด๋
3.
jabswitch -enable ๋ช
๋ น ์คํํ์ธ ๋ฐฉ๋ฒ:
โข Windows ์ ์ดํ โ ์ ๊ทผ์ฑ ์ผํฐ์์ Java Access Bridge๊ฐ ํ์ฑํ๋์๋์ง ํ์ธ
โข ์์คํ ํธ๋ ์ด์ Java Access Bridge ์์ด์ฝ์ด ๋ํ๋๋์ง ํ์ธ
๐ฎ Accessibility Checker ๋ง๋ค๊ธฐ
์ ํ๋ฆฌ์ผ์ด์ ์ ์ ๊ทผ์ฑ์ ์๋์ผ๋ก ๊ฒ์ฌํ๋ ๊ฐ๋จํ ๋๊ตฌ๋ฅผ ๋ง๋ค์ด๋ณด์:
import javax.accessibility.*;
import javax.swing.*;
import java.awt.*;
import java.util.*;
public class AccessibilityChecker {
private java.util.List<String> issues = new ArrayList<>();
public void checkComponent(Component component) {
issues.clear();
checkComponentRecursive(component, "");
printReport();
}
private void checkComponentRecursive(Component comp, String indent) {
if (!(comp instanceof Accessible)) {
issues.add(indent + "โ ๏ธ ์ปดํฌ๋ํธ๊ฐ Accessible์ ๊ตฌํํ์ง ์์: " +
comp.getClass().getSimpleName());
return;
}
Accessible accessible = (Accessible) comp;
AccessibleContext context = accessible.getAccessibleContext();
if (context == null) {
issues.add(indent + "โ AccessibleContext๊ฐ null: " +
comp.getClass().getSimpleName());
return;
}
// ์ด๋ฆ ๊ฒ์ฌ
String name = context.getAccessibleName();
if (name == null || name.trim().isEmpty()) {
if (needsName(context.getAccessibleRole())) {
issues.add(indent + "โ ๏ธ ์ ๊ทผ์ฑ ์ด๋ฆ์ด ์์: " +
comp.getClass().getSimpleName() +
" (์ญํ : " + context.getAccessibleRole() + ")");
}
}
// ์ค๋ช
๊ฒ์ฌ
String description = context.getAccessibleDescription();
if (description == null || description.trim().isEmpty()) {
if (needsDescription(context.getAccessibleRole())) {
issues.add(indent + "โน๏ธ ์ ๊ทผ์ฑ ์ค๋ช
์ด ์์: " +
comp.getClass().getSimpleName());
}
}
// ํค๋ณด๋ ํฌ์ปค์ค ๊ฒ์ฌ
if (comp instanceof JComponent) {
JComponent jcomp = (JComponent) comp;
if (isInteractive(context.getAccessibleRole())) {
if (!jcomp.isFocusable()) {
issues.add(indent + "โ ๏ธ ์ํธ์์ฉ ๊ฐ๋ฅํ ์ปดํฌ๋ํธ๊ฐ " +
"ํฌ์ปค์ค๋ฅผ ๋ฐ์ ์ ์์: " +
comp.getClass().getSimpleName());
}
}
}
// ๋ ์ด๋ธ ์ฐ๊ฒฐ ๊ฒ์ฌ (ํ
์คํธ ํ๋์ ๊ฒฝ์ฐ)
if (comp instanceof JTextField ||
comp instanceof JTextArea ||
comp instanceof JPasswordField) {
AccessibleRelationSet relations =
context.getAccessibleRelationSet();
if (relations == null ||
relations.get(AccessibleRelation.LABELED_BY) == null) {
issues.add(indent + "โ ๏ธ ํ
์คํธ ์
๋ ฅ ํ๋์ " +
"๋ ์ด๋ธ์ด ์ฐ๊ฒฐ๋์ง ์์: " +
comp.getClass().getSimpleName());
}
}
// ์์ ์ปดํฌ๋ํธ ๊ฒ์ฌ
if (comp instanceof Container) {
Container container = (Container) comp;
for (Component child : container.getComponents()) {
checkComponentRecursive(child, indent + " ");
}
}
}
private boolean needsName(AccessibleRole role) {
return role == AccessibleRole.PUSH_BUTTON ||
role == AccessibleRole.TEXT ||
role == AccessibleRole.PASSWORD_TEXT ||
role == AccessibleRole.CHECK_BOX ||
role == AccessibleRole.RADIO_BUTTON ||
role == AccessibleRole.COMBO_BOX ||
role == AccessibleRole.LIST ||
role == AccessibleRole.TABLE;
}
private boolean needsDescription(AccessibleRole role) {
return role == AccessibleRole.PUSH_BUTTON ||
role == AccessibleRole.TEXT ||
role == AccessibleRole.PASSWORD_TEXT;
}
private boolean isInteractive(AccessibleRole role) {
return role == AccessibleRole.PUSH_BUTTON ||
role == AccessibleRole.TEXT ||
role == AccessibleRole.PASSWORD_TEXT ||
role == AccessibleRole.CHECK_BOX ||
role == AccessibleRole.RADIO_BUTTON ||
role == AccessibleRole.COMBO_BOX ||
role == AccessibleRole.SLIDER;
}
private void printReport() {
System.out.println("\n" + "=".repeat(60));
System.out.println("์ ๊ทผ์ฑ ๊ฒ์ฌ ๋ณด๊ณ ์");
System.out.println("=".repeat(60));
if (issues.isEmpty()) {
System.out.println("โ
๋ฐ๊ฒฌ๋ ๋ฌธ์ ์์! ํ๋ฅญํฉ๋๋ค!");
} else {
System.out.println("๋ฐ๊ฒฌ๋ ๋ฌธ์ : " + issues.size() + "๊ฐ\n");
for (String issue : issues) {
System.out.println(issue);
}
}
System.out.println("=".repeat(60) + "\n");
}
// ํ
์คํธ์ฉ ๋ฉ์ธ ๋ฉ์๋
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
// ๋์ ์์ ํ๋ ์
JFrame badFrame = new JFrame("์ ๊ทผ์ฑ ๋ฌธ์ ๊ฐ ์๋ ํผ");
JPanel badPanel = new JPanel();
badPanel.add(new JLabel("์ด๋ฆ"));
badPanel.add(new JTextField(20)); // ๋ ์ด๋ธ ์ฐ๊ฒฐ ์์
JButton badButton = new JButton("โ"); // ์๋ฏธ ์๋ ํ
์คํธ
badButton.getAccessibleContext().setAccessibleName(null);
badPanel.add(badButton);
badFrame.add(badPanel);
badFrame.pack();
// ๊ฒ์ฌ ์คํ
AccessibilityChecker checker = new AccessibilityChecker();
System.out.println("๋์ ์์ ๊ฒ์ฌ:");
checker.checkComponent(badFrame);
// ์ข์ ์์ ํ๋ ์
JFrame goodFrame = new JFrame("์ ๊ทผ์ฑ์ด ์ข์ ํผ");
JPanel goodPanel = new JPanel();
JLabel nameLabel = new JLabel("์ด๋ฆ:");
JTextField nameField = new JTextField(20);
nameLabel.setLabelFor(nameField); // ๋ ์ด๋ธ ์ฐ๊ฒฐ
nameField.getAccessibleContext().setAccessibleName("์ด๋ฆ ์
๋ ฅ");
nameField.getAccessibleContext().setAccessibleDescription(
"์ฌ์ฉ์์ ์ด๋ฆ์ ์
๋ ฅํ์ธ์");
JButton goodButton = new JButton("์ ์ถ");
goodButton.getAccessibleContext().setAccessibleDescription(
"ํผ์ ์ ์ถํฉ๋๋ค");
goodPanel.add(nameLabel);
goodPanel.add(nameField);
goodPanel.add(goodButton);
goodFrame.add(goodPanel);
goodFrame.pack();
System.out.println("\n์ข์ ์์ ๊ฒ์ฌ:");
checker.checkComponent(goodFrame);
});
}
}
์ด ์ฒด์ปค๋ฅผ ์ฌ์ฉํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ๊ทผ์ฑ ๋ฌธ์ ๋ฅผ ์๋์ผ๋ก ์ฐพ์๋ผ ์ ์์ด. ๊ฐ๋ฐ ๊ณผ์ ์์ ์ฃผ๊ธฐ์ ์ผ๋ก ์คํํ๋ฉด ์ ๊ทผ์ฑ ๋ฌธ์ ๋ฅผ ์กฐ๊ธฐ์ ๋ฐ๊ฒฌํ๊ณ ์์ ํ ์ ์์ง! ๐ฏ
๐ ์๋ ํ ์คํธ ์ฒดํฌ๋ฆฌ์คํธ
์๋ํ๋ ํ ์คํธ๋ ์ค์ํ์ง๋ง, ์ค์ ๋ก ์ฌ์ฉํด๋ณด๋ ์๋ ํ ์คํธ๋ ํ์์ผ. ๋ค์ ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ํ์ฉํด๋ด:
โก Tab ํค๋ก ๋ชจ๋ ์ํธ์์ฉ ๊ฐ๋ฅํ ์์์ ์ ๊ทผํ ์ ์๋๊ฐ?
โก Shift+Tab์ผ๋ก ์ญ๋ฐฉํฅ ํ์์ด ๊ฐ๋ฅํ๊ฐ?
โก ํฌ์ปค์ค ์์๊ฐ ๋ ผ๋ฆฌ์ ์ธ๊ฐ?
โก ํฌ์ปค์ค๋ฅผ ๋ฐ์ ์์๊ฐ ์๊ฐ์ ์ผ๋ก ๋ช ํํ๊ฒ ํ์๋๋๊ฐ?
โก Enter/Space ํค๋ก ๋ฒํผ๊ณผ ๋งํฌ๋ฅผ ํ์ฑํํ ์ ์๋๊ฐ?
โก ํ์ดํ ํค๋ก ๋ฆฌ์คํธ, ํธ๋ฆฌ, ํ ์ด๋ธ์ ํ์ํ ์ ์๋๊ฐ?
โก Esc ํค๋ก ๋ค์ด์ผ๋ก๊ทธ๋ฅผ ๋ซ์ ์ ์๋๊ฐ?
โก ๋จ์ถํค(๋๋ชจ๋)๊ฐ ์ ๋๋ก ์๋ํ๋๊ฐ?
โ ์คํฌ๋ฆฐ ๋ฆฌ๋ ํ ์คํธ
โก ๋ชจ๋ ํ ์คํธ๊ฐ ์ฝํ๋๊ฐ?
โก ๋ฒํผ๊ณผ ๋งํฌ์ ๋ชฉ์ ์ด ๋ช ํํ๊ฒ ์ ๋ฌ๋๋๊ฐ?
โก ํผ ํ๋์ ์ ์ ํ ๋ ์ด๋ธ์ด ์๋๊ฐ?
โก ์๋ฌ ๋ฉ์์ง๊ฐ ์ฝํ๋๊ฐ?
โก ๋์ ์ผ๋ก ๋ณ๊ฒฝ๋๋ ์ฝํ ์ธ ๊ฐ ์๋ฆผ๋๋๊ฐ?
โก ์ด๋ฏธ์ง์ ๋์ฒด ํ ์คํธ๊ฐ ์๋๊ฐ?
โ ์๊ฐ์ ์ ๊ทผ์ฑ ํ ์คํธ
โก ์์ ๋๋น๊ฐ ์ถฉ๋ถํ๊ฐ? (์ต์ 4.5:1)
โก ์ ๋ณด๊ฐ ์์์๋ง ์์กดํ์ง ์๋๊ฐ?
โก ํ ์คํธ ํฌ๊ธฐ๋ฅผ 200%๋ก ํ๋ํด๋ ์ฌ์ฉ ๊ฐ๋ฅํ๊ฐ?
โก ๊ณ ๋๋น ๋ชจ๋์์๋ ์ ๋๋ก ๋ณด์ด๋๊ฐ?
๐ ์น๊ณผ์ ์ฐ๋: JavaFX WebView ์ ๊ทผ์ฑ
์์ฆ์ ์๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์น ์ฝํ ์ธ ๋ฅผ ํ์ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์. JavaFX์ WebView๋ฅผ ์ฌ์ฉํ ๋๋ ์ ๊ทผ์ฑ์ ๊ณ ๋ คํด์ผ ํด! ๐
JavaFX WebView๋ WebKit ์์ง์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์, HTML์ ์ ๊ทผ์ฑ ๊ธฐ๋ฅ(ARIA ์์ฑ ๋ฑ)์ ๊ทธ๋๋ก ํ์ฉํ ์ ์์ด. ํ์ง๋ง ์๋ฐ ์ธก์์๋ ๋ช ๊ฐ์ง ์ค์ ์ด ํ์ํด.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class AccessibleWebViewExample extends Application {
@Override
public void start(Stage primaryStage) {
WebView webView = new WebView();
// ์ ๊ทผ์ฑ ์ ๋ณด ์ค์
webView.setAccessibleText("์น ์ฝํ
์ธ ๋ทฐ์ด");
webView.setAccessibleHelp(
"์ด ์์ญ์๋ ์น ํ์ด์ง๊ฐ ํ์๋ฉ๋๋ค. " +
"Tab ํค๋ก ๋งํฌ์ ํผ ์์๋ฅผ ํ์ํ ์ ์์ต๋๋ค.");
// ์ ๊ทผ์ฑ์ด ๊ณ ๋ ค๋ HTML ๋ก๋
String accessibleHTML = """
<a href="#main" class="skip-link">๋ณธ๋ฌธ์ผ๋ก ๊ฑด๋๋ฐ๊ธฐ</a>
<header role="banner">
<h1>์ ๊ทผ ๊ฐ๋ฅํ ์น ์ ํ๋ฆฌ์ผ์ด์
</h1>
</header>
<nav role="navigation" aria-label="์ฃผ ๋ฉ๋ด">
<ul>
<li><a href="#home">ํ</a></li>
<li><a href="#about">์๊ฐ</a></li>
<li><a href="#contact">์ฐ๋ฝ์ฒ</a></li>
</ul>
</nav>
<main id="main" role="main">
<h2>๋ก๊ทธ์ธ</h2>
<form onsubmit="return validateForm()">
<div>
<label for="username">์ฌ์ฉ์ ์ด๋ฆ:</label>
<input type="text" id="username" name="username" aria-required="true" aria-describedby="username-help">
<span id="username-help" class="help-text">
์๋ฌธ๊ณผ ์ซ์๋ง ์ฌ์ฉ ๊ฐ๋ฅํฉ๋๋ค
</span>
</div>
<div>
<label for="password">๋น๋ฐ๋ฒํธ:</label>
<input type="password" id="password" name="password" aria-required="true">
</div>
<div role="alert" id="error-message" class="error" aria-live="assertive" style="display: none;">
</div>
<button type="submit" aria-label="๋ก๊ทธ์ธ ๋ฒํผ">
๋ก๊ทธ์ธ
</button>
</form>
<section aria-labelledby="status-heading">
<h3 id="status-heading">์์คํ
์ํ</h3>
<div role="status" aria-live="polite" id="status-message">
๋ชจ๋ ์์คํ
์ด ์ ์ ์๋ ์ค์
๋๋ค.
</div>
</section>
</main>
<footer role="contentinfo">
<p>ยฉ 2024 ์ ๊ทผ ๊ฐ๋ฅํ ์ ํ๋ฆฌ์ผ์ด์
</p>
</footer>
<script>
function validateForm() {
const username =
document.getElementById('username').value;
const password =
document.getElementById('password').value;
const errorDiv =
document.getElementById('error-message');
if (!username || !password) {
errorDiv.textContent =
'๋ชจ๋ ํ๋๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.';
errorDiv.style.display = 'block';
// ์ฒซ ๋ฒ์งธ ๋น ํ๋์ ํฌ์ปค์ค
if (!username) {
document.getElementById('username').focus();
} else {
document.getElementById('password').focus();
}
return false;
}
errorDiv.style.display = 'none';
return true;
}
// ๋์ ์ฝํ
์ธ ์
๋ฐ์ดํธ ์์
setInterval(() => {
const now = new Date();
const statusDiv =
document.getElementById('status-message');
statusDiv.textContent =
'๋ง์ง๋ง ์
๋ฐ์ดํธ: ' + now.toLocaleTimeString();
}, 30000);
</script>
""";
webView.getEngine().loadContent(accessibleHTML);
Scene scene = new Scene(webView, 900, 700);
primaryStage.setTitle("์ ๊ทผ ๊ฐ๋ฅํ WebView");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
์ ์์ ์์ ์ฌ์ฉ๋ ์น ์ ๊ทผ์ฑ ๊ธฐ๋ฒ๋ค์ ์ดํด๋ณด์:
1. ARIA (Accessible Rich Internet Applications)
โข
role: ์์์ ์ญํ ์ ์ (banner, navigation, main, contentinfo ๋ฑ)โข
aria-label: ์์์ ๋ ์ด๋ธ ์ ๊ณตโข
aria-describedby: ์ถ๊ฐ ์ค๋ช
์ฐ๊ฒฐโข
aria-live: ๋์ ์ฝํ
์ธ ๋ณ๊ฒฝ ์๋ฆผ (polite, assertive)โข
aria-required: ํ์ ์
๋ ฅ ํ๋ ํ์2. ์๋งจํฑ HTML
โข
<header>, <nav>, <main>, <footer> ๊ฐ์ ์๋ฏธ ์๋ ํ๊ทธ ์ฌ์ฉโข ์ ๋ชฉ ํ๊ทธ (
<h1>~<h6>)์ ๊ณ์ธต์ ์ฌ์ฉ3. ํผ ์ ๊ทผ์ฑ
โข
<label>๊ณผ <input>์ ๋ช
์์ ์ฐ๊ฒฐ (for/id)โข ์๋ฌ ๋ฉ์์ง๋ฅผ
role="alert"๋ก ์ฆ์ ์๋ฆผโข ํ๋ ์ค๋ช ์
aria-describedby๋ก ์ฐ๊ฒฐ4. ํค๋ณด๋ ์ ๊ทผ์ฑ
โข ๋ชจ๋ ์ํธ์์ฉ ์์๊ฐ ํค๋ณด๋๋ก ์ ๊ทผ ๊ฐ๋ฅ
โข ๊ฑด๋๋ฐ๊ธฐ ๋งํฌ (Skip Link) ์ ๊ณต
โข ๋ ผ๋ฆฌ์ ์ธ ํญ ์์
๐ผ ์ค๋ฌด์์์ ์ ๊ทผ์ฑ: ๋ชจ๋ฒ ์ฌ๋ก์ ํ
์ด๋ก ๊ณผ ์์ ๋ฅผ ๋ง์ด ๋ดค์ผ๋, ์ด์ ์ค๋ฌด์์ ๋ฐ๋ก ์ ์ฉํ ์ ์๋ ์ค์ฉ์ ์ธ ํ๋ค์ ์ ๋ฆฌํด๋ณผ๊ฒ! ์ด๋ฐ ๊ฒ๋ค์ ๊ฒฝํ์์ ๋์จ ๋ ธํ์ฐ๋ค์ด์ผ. ๐ก
๐ฏ ๊ฐ๋ฐ ์ํฌํ๋ก์ฐ์ ์ ๊ทผ์ฑ ํตํฉํ๊ธฐ
์ ๊ทผ์ฑ์ ํ๋ก์ ํธ ๋ง์ง๋ง์ "์ถ๊ฐ"ํ๋ ๊ฒ ์๋๋ผ, ์ฒ์๋ถํฐ ๊ฐ๋ฐ ํ๋ก์ธ์ค์ ํฌํจ๋์ด์ผ ํด. ๋ค์์ ํจ๊ณผ์ ์ธ ์ํฌํ๋ก์ฐ์ผ:
1. ๊ธฐํ ๋จ๊ณ
โข ์ฌ์ฉ์ ํ๋ฅด์๋์ ์ฅ์ ๊ฐ ์๋ ์ฌ์ฉ์ ํฌํจ
โข ์ ๊ทผ์ฑ ์๊ตฌ์ฌํญ์ ๋ช ์์ ์ผ๋ก ๋ฌธ์ํ
โข WCAG 2.1 ๋ ๋ฒจ AA๋ฅผ ์ต์ ๋ชฉํ๋ก ์ค์
2. ์ค๊ณ ๋จ๊ณ
โข ์์ ๋๋น ๊ฒ์ฌ (์ต์ 4.5:1)
โข ํค๋ณด๋ ๋ด๋น๊ฒ์ด์ ํ๋ก์ฐ ์ค๊ณ
โข ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ฌ์ฉ์๋ฅผ ์ํ ์ ๋ณด ๊ตฌ์กฐ ์ค๊ณ
3. ๊ฐ๋ฐ ๋จ๊ณ
โข ์ปดํฌ๋ํธ ๊ฐ๋ฐ ์ ์ ๊ทผ์ฑ API ๊ตฌํ
โข ์ฝ๋ ๋ฆฌ๋ทฐ์ ์ ๊ทผ์ฑ ์ฒดํฌ๋ฆฌ์คํธ ํฌํจ
โข ๋จ์ ํ ์คํธ์ ์ ๊ทผ์ฑ ๊ฒ์ฆ ์ถ๊ฐ
4. ํ ์คํธ ๋จ๊ณ
โข ์๋ํ๋ ์ ๊ทผ์ฑ ํ ์คํธ ์คํ
โข ์ค์ ๋ณด์กฐ ๊ธฐ์ ๋ก ์๋ ํ ์คํธ
โข ๊ฐ๋ฅํ๋ฉด ์ฅ์ ๊ฐ ์๋ ์ฌ์ฉ์์ ํผ๋๋ฐฑ ์์ง
5. ์ ์ง๋ณด์ ๋จ๊ณ
โข ์๋ก์ด ๊ธฐ๋ฅ ์ถ๊ฐ ์ ์ ๊ทผ์ฑ ์ํฅ ํ๊ฐ
โข ์ ๊ธฐ์ ์ธ ์ ๊ทผ์ฑ ๊ฐ์ฌ
โข ์ฌ์ฉ์ ํผ๋๋ฐฑ ์ง์์ ๋ฐ์
๐ ๏ธ ์ ์ฉํ ๋๊ตฌ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
์ ๊ทผ์ฑ ๊ฐ๋ฐ์ ๋์์ฃผ๋ ๋๊ตฌ๋ค์ ํ์ฉํ๋ฉด ํจ์ฌ ํจ์จ์ ์ผ๋ก ์์ ํ ์ ์์ด:
// ์ ๊ทผ์ฑ ์ ํธ๋ฆฌํฐ ํด๋์ค ์์
public class AccessibilityUtils {
/**
* ์ปดํฌ๋ํธ์ ๊ธฐ๋ณธ ์ ๊ทผ์ฑ ์ ๋ณด๋ฅผ ์ค์ ํฉ๋๋ค.
*/
public static void setupBasicAccessibility(
JComponent component,
String name,
String description) {
AccessibleContext context = component.getAccessibleContext();
if (context != null) {
context.setAccessibleName(name);
context.setAccessibleDescription(description);
}
}
/**
* ๋ ์ด๋ธ๊ณผ ์ปดํฌ๋ํธ๋ฅผ ์ฐ๊ฒฐํ๊ณ ๋๋ชจ๋์ ์ค์ ํฉ๋๋ค.
*/
public static void linkLabelToComponent(
JLabel label,
JComponent component,
char mnemonic) {
label.setLabelFor(component);
label.setDisplayedMnemonic(mnemonic);
}
/**
* ๋ฒํผ์ ์์ ํ ์ ๊ทผ์ฑ ์ ๋ณด๋ฅผ ์ค์ ํฉ๋๋ค.
*/
public static void setupAccessibleButton(
JButton button,
String text,
String description,
char mnemonic) {
button.setText(text);
button.setMnemonic(mnemonic);
AccessibleContext context = button.getAccessibleContext();
context.setAccessibleDescription(description);
}
/**
* ๋ค์ด์ผ๋ก๊ทธ์ ์ด๊ธฐ ํฌ์ปค์ค๋ฅผ ์ค์ ํฉ๋๋ค.
*/
public static void setInitialFocus(
final JDialog dialog,
final JComponent component) {
dialog.addWindowListener(new java.awt.event.WindowAdapter() {
@Override
public void windowOpened(java.awt.event.WindowEvent e) {
component.requestFocusInWindow();
}
});
}
/**
* ์์ ๋๋น๋ฅผ ๊ณ์ฐํฉ๋๋ค.
* WCAG 2.1 ๊ธฐ์ค: ์ผ๋ฐ ํ
์คํธ 4.5:1, ํฐ ํ
์คํธ 3:1
*/
public static double calculateContrastRatio(
Color foreground,
Color background) {
double l1 = getRelativeLuminance(foreground);
double l2 = getRelativeLuminance(background);
double lighter = Math.max(l1, l2);
double darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
private static double getRelativeLuminance(Color color) {
double r = color.getRed() / 255.0;
double g = color.getGreen() / 255.0;
double b = color.getBlue() / 255.0;
r = (r <= 0.03928) ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);
g = (g <= 0.03928) ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4);
b = (b <= 0.03928) ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
/**
* ์์ ๋๋น๊ฐ ์ถฉ๋ถํ์ง ๊ฒ์ฌํฉ๋๋ค.
*/
public static boolean hasGoodContrast(
Color foreground,
Color background,
boolean isLargeText) {
double ratio = calculateContrastRatio(foreground, background);
double requiredRatio = isLargeText ? 3.0 : 4.5;
return ratio >= requiredRatio;
}
/**
* ์ปดํฌ๋ํธ ํธ๋ฆฌ๋ฅผ ์ํํ๋ฉฐ ์ ๊ทผ์ฑ ์ ๋ณด๋ฅผ ์ถ๋ ฅํฉ๋๋ค.
*/
public static void printAccessibilityTree(Component component) {
printAccessibilityTreeRecursive(component, 0);
}
private static void printAccessibilityTreeRecursive(
Component comp,
int level) {
String indent = " ".repeat(level);
if (comp instanceof Accessible) {
Accessible accessible = (Accessible) comp;
AccessibleContext context = accessible.getAccessibleContext();
if (context != null) {
System.out.println(indent + "โโ " +
comp.getClass().getSimpleName());
System.out.println(indent + "โ ์ด๋ฆ: " +
context.getAccessibleName());
System.out.println(indent + "โ ์ญํ : " +
context.getAccessibleRole());
System.out.println(indent + "โ ์ค๋ช
: " +
context.getAccessibleDescription());
}
}
if (comp instanceof Container) {
Container container = (Container) comp;
for (Component child : container.getComponents()) {
printAccessibilityTreeRecursive(child, level + 1);
}
}
}
}
// ์ฌ์ฉ ์์
public class AccessibilityUtilsDemo {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("์ ๊ทผ์ฑ ์ ํธ๋ฆฌํฐ ๋ฐ๋ชจ");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
gbc.fill = GridBagConstraints.HORIZONTAL;
// ๋ ์ด๋ธ๊ณผ ํ
์คํธ ํ๋
JLabel nameLabel = new JLabel("์ด๋ฆ:");
JTextField nameField = new JTextField(20);
AccessibilityUtils.linkLabelToComponent(
nameLabel, nameField, 'N');
AccessibilityUtils.setupBasicAccessibility(
nameField,
"์ด๋ฆ ์
๋ ฅ",
"์ฌ์ฉ์์ ์ด๋ฆ์ ์
๋ ฅํ์ธ์");
gbc.gridx = 0;
gbc.gridy = 0;
panel.add(nameLabel, gbc);
gbc.gridx = 1;
panel.add(nameField, gbc);
// ๋ฒํผ
JButton submitButton = new JButton();
AccessibilityUtils.setupAccessibleButton(
submitButton,
"์ ์ถ",
"์
๋ ฅํ ์ ๋ณด๋ฅผ ์ ์ถํฉ๋๋ค",
'S');
gbc.gridx = 0;
gbc.gridy = 1;
gbc.gridwidth = 2;
panel.add(submitButton, gbc);
// ์์ ๋๋น ํ
์คํธ
Color fg = Color.BLACK;
Color bg = Color.WHITE;
double ratio = AccessibilityUtils.calculateContrastRatio(fg, bg);
boolean goodContrast =
AccessibilityUtils.hasGoodContrast(fg, bg, false);
System.out.println("์์ ๋๋น ๋น์จ: " + ratio);
System.out.println("์ถฉ๋ถํ ๋๋น: " + goodContrast);
// ์ ๊ทผ์ฑ ํธ๋ฆฌ ์ถ๋ ฅ
System.out.println("\n์ ๊ทผ์ฑ ํธ๋ฆฌ:");
AccessibilityUtils.printAccessibilityTree(panel);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
์ด๋ฐ ์ ํธ๋ฆฌํฐ ํด๋์ค๋ฅผ ํ๋ก์ ํธ์ ํฌํจ์ํค๋ฉด, ์ ๊ทผ์ฑ ๊ตฌํ์ด ํจ์ฌ ๊ฐํธํด์ ธ. ๋งค๋ฒ ๋ฐ๋ณต์ ์ธ ์ฝ๋๋ฅผ ์์ฑํ ํ์๊ฐ ์์ด์ง๊ฑฐ๋ ! ๐
๐จ ๋์์ธ ์์คํ ๊ณผ ์ ๊ทผ์ฑ
์ผ๊ด๋ ์ ๊ทผ์ฑ์ ์ ์งํ๋ ค๋ฉด ๋์์ธ ์์คํ ์ ๋ง๋๋ ๊ฒ ์ข์. ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ ๊ทผ ๊ฐ๋ฅํ ์ปดํฌ๋ํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ตฌ์ถํ๋ ๊ฑฐ์ง:
// ์ ๊ทผ ๊ฐ๋ฅํ ์ปค์คํ
๋ฒํผ ์ปดํฌ๋ํธ
public class AccessibleButton extends JButton {
public AccessibleButton(String text) {
super(text);
setupDefaults();
}
public AccessibleButton(String text, String description) {
super(text);
setupDefaults();
getAccessibleContext().setAccessibleDescription(description);
}
public AccessibleButton(String text, String description, char mnemonic) {
super(text);
setupDefaults();
getAccessibleContext().setAccessibleDescription(description);
setMnemonic(mnemonic);
}
private void setupDefaults() {
// ์ต์ ํฌ๊ธฐ ๋ณด์ฅ (ํฐ์น ํ๊ฒ ํฌ๊ธฐ)
setMinimumSize(new Dimension(44, 44));
setPreferredSize(new Dimension(100, 44));
// ํฌ์ปค์ค ํ์ ๊ฐํ
setFocusPainted(true);
// ํดํ์ ์ ๊ทผ์ฑ ์ค๋ช
๊ณผ ๋๊ธฐํ
addPropertyChangeListener(
AccessibleContext.ACCESSIBLE_DESCRIPTION_PROPERTY,
evt -> {
String desc = getAccessibleContext()
.getAccessibleDescription();
if (desc != null && !desc.isEmpty()) {
setToolTipText(desc);
}
}
);
}
/**
* ๋น๋ ํจํด์ผ๋ก ์ ๊ทผ์ฑ ์ค์
*/
public static class Builder {
private String text;
private String description;
private Character mnemonic;
private Icon icon;
private ActionListener action;
public Builder text(String text) {
this.text = text;
return this;
}
public Builder description(String description) {
this.description = description;
return this;
}
public Builder mnemonic(char mnemonic) {
this.mnemonic = mnemonic;
return this;
}
public Builder icon(Icon icon) {
this.icon = icon;
return this;
}
public Builder action(ActionListener action) {
this.action = action;
return this;
}
public AccessibleButton build() {
AccessibleButton button = new AccessibleButton(
text != null ? text : "",
description != null ? description : "",
mnemonic != null ? mnemonic : '\0'
);
if (icon != null) {
button.setIcon(icon);
}
if (action != null) {
button.addActionListener(action);
}
return button;
}
}
}
// ์ฌ์ฉ ์์
AccessibleButton saveButton = new AccessibleButton.Builder()
.text("์ ์ฅ")
.description("ํ์ฌ ๋ฌธ์๋ฅผ ์ ์ฅํฉ๋๋ค")
.mnemonic('S')
.action(e -> saveDocument())
.build();
์ด๋ ๊ฒ ์ ๊ทผ์ฑ์ด ๋ด์ฅ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด๋๋ฉด, ํ ์ ์ฒด๊ฐ ์ผ๊ด๋๊ฒ ์ ๊ทผ ๊ฐ๋ฅํ UI๋ฅผ ๋ง๋ค ์ ์์ด. ๊ฐ๋ฐ์๊ฐ ์ผ์ผ์ด ์ ๊ทผ์ฑ์ ์ ๊ฒฝ ์ฐ์ง ์์๋ ์๋์ผ๋ก ์ ์ฉ๋๋ ๊ฑฐ์ง! ๐
๐ ์ค์ ํ๋ก์ ํธ ์ฌ๋ก: ์ ๊ทผ ๊ฐ๋ฅํ ํ ์คํธ ์๋ํฐ
์ด๋ก ๊ณผ ์์ ์์ ๋ค์ ๋ง์ด ๋ดค์ผ๋, ์ด์ ์ค์ ๋ก ์ฌ์ฉํ ์ ์๋ ์์ ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค์ด๋ณด์. ์ ๊ทผ์ฑ์ด ์๋ฒฝํ๊ฒ ๊ตฌํ๋ ๊ฐ๋จํ ํ ์คํธ ์๋ํฐ์ผ! ๐
์๋ ์ฝ๋๋ ๊ต์ก ๋ชฉ์ ์ ์์ ์ ๋๋ค. ์ค์ ํ๋ก๋์ ํ๊ฒฝ์์๋ ๋ ๋ง์ ์๋ฌ ์ฒ๋ฆฌ์ ์ต์ ํ๊ฐ ํ์ํฉ๋๋ค.
import javax.accessibility.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.undo.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
public class AccessibleTextEditor extends JFrame {
private JTextArea textArea;
private JLabel statusLabel;
private UndoManager undoManager;
private File currentFile;
private boolean isModified = false;
public AccessibleTextEditor() {
super("์ ๊ทผ ๊ฐ๋ฅํ ํ
์คํธ ์๋ํฐ");
initializeComponents();
setupAccessibility();
setupKeyboardShortcuts();
setSize(800, 600);
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
exitApplication();
}
});
setLocationRelativeTo(null);
setVisible(true);
}
private void initializeComponents() {
// ๋ฉ๋ด ๋ฐ
JMenuBar menuBar = createAccessibleMenuBar();
setJMenuBar(menuBar);
// ํ
์คํธ ์์ญ
textArea = new JTextArea();
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.setFont(new Font("Monospaced", Font.PLAIN, 14));
// Undo ๊ธฐ๋ฅ
undoManager = new UndoManager();
textArea.getDocument().addUndoableEditListener(undoManager);
// ๋ฌธ์ ๋ณ๊ฒฝ ๊ฐ์ง
textArea.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) { documentChanged(); }
public void removeUpdate(DocumentEvent e) { documentChanged(); }
public void changedUpdate(DocumentEvent e) { documentChanged(); }
});
JScrollPane scrollPane = new JScrollPane(textArea);
add(scrollPane, BorderLayout.CENTER);
// ์ํ ํ์์ค
statusLabel = new JLabel(" ");
statusLabel.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
add(statusLabel, BorderLayout.SOUTH);
updateStatus();
}
private void setupAccessibility() {
// ํ๋ ์ ์ ๊ทผ์ฑ
getAccessibleContext().setAccessibleName("ํ
์คํธ ์๋ํฐ ๋ฉ์ธ ์ฐฝ");
getAccessibleContext().setAccessibleDescription(
"ํ
์คํธ ํ์ผ์ ํธ์งํ ์ ์๋ ์๋ํฐ์
๋๋ค");
// ํ
์คํธ ์์ญ ์ ๊ทผ์ฑ
textArea.getAccessibleContext().setAccessibleName("๋ฌธ์ ํธ์ง ์์ญ");
textArea.getAccessibleContext().setAccessibleDescription(
"์ฌ๊ธฐ์ ํ
์คํธ๋ฅผ ์
๋ ฅํ๊ณ ํธ์งํ ์ ์์ต๋๋ค. " +
"Ctrl+Z๋ก ์คํ ์ทจ์, Ctrl+Y๋ก ๋ค์ ์คํํ ์ ์์ต๋๋ค.");
// ์ํ ํ์์ค ์ ๊ทผ์ฑ
statusLabel.getAccessibleContext().setAccessibleName("์ํ ํ์์ค");
statusLabel.getAccessibleContext().setAccessibleDescription(
"ํ์ฌ ๋ฌธ์์ ์ํ ์ ๋ณด๋ฅผ ํ์ํฉ๋๋ค");
}
private JMenuBar createAccessibleMenuBar() {
JMenuBar menuBar = new JMenuBar();
// ํ์ผ ๋ฉ๋ด
JMenu fileMenu = new JMenu("ํ์ผ(F)");
fileMenu.setMnemonic('F');
fileMenu.getAccessibleContext().setAccessibleDescription(
"ํ์ผ ๊ด๋ จ ์์
์ ์ํํฉ๋๋ค");
JMenuItem newItem = createMenuItem(
"์ ํ์ผ(N)", 'N',
KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK),
"์ ๋ฌธ์๋ฅผ ๋ง๋ญ๋๋ค",
e -> newFile()
);
fileMenu.add(newItem);
JMenuItem openItem = createMenuItem(
"์ด๊ธฐ(O)", 'O',
KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK),
"ํ์ผ์ ์ฝ๋๋ค",
e -> openFile()
);
fileMenu.add(openItem);
JMenuItem saveItem = createMenuItem(
"์ ์ฅ(S)", 'S',
KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK),
"ํ์ฌ ๋ฌธ์๋ฅผ ์ ์ฅํฉ๋๋ค",
e -> saveFile()
);
fileMenu.add(saveItem);
JMenuItem saveAsItem = createMenuItem(
"๋ค๋ฅธ ์ด๋ฆ์ผ๋ก ์ ์ฅ(A)", 'A',
KeyStroke.getKeyStroke(KeyEvent.VK_S,
InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK),
"ํ์ฌ ๋ฌธ์๋ฅผ ์ ์ด๋ฆ์ผ๋ก ์ ์ฅํฉ๋๋ค",
e -> saveFileAs()
);
fileMenu.add(saveAsItem);
fileMenu.addSeparator();
JMenuItem exitItem = createMenuItem(
"์ข
๋ฃ(X)", 'X',
KeyStroke.getKeyStroke(KeyEvent.VK_Q, InputEvent.CTRL_DOWN_MASK),
"ํ๋ก๊ทธ๋จ์ ์ข
๋ฃํฉ๋๋ค",
e -> exitApplication()
);
fileMenu.add(exitItem);
menuBar.add(fileMenu);
// ํธ์ง ๋ฉ๋ด
JMenu editMenu = new JMenu("ํธ์ง(E)");
editMenu.setMnemonic('E');
editMenu.getAccessibleContext().setAccessibleDescription(
"ํธ์ง ๊ด๋ จ ์์
์ ์ํํฉ๋๋ค");
JMenuItem undoItem = createMenuItem(
"์คํ ์ทจ์(U)", 'U',
KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK),
"๋ง์ง๋ง ์์
์ ์ทจ์ํฉ๋๋ค",
e -> undo()
);
editMenu.add(undoItem);
JMenuItem redoItem = createMenuItem(
"๋ค์ ์คํ(R)", 'R',
KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK),
"์ทจ์ํ ์์
์ ๋ค์ ์คํํฉ๋๋ค",
e -> redo()
);
editMenu.add(redoItem);
editMenu.addSeparator();
JMenuItem selectAllItem = createMenuItem(
"๋ชจ๋ ์ ํ(A)", 'A',
KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK),
"๋ชจ๋ ํ
์คํธ๋ฅผ ์ ํํฉ๋๋ค",
e -> textArea.selectAll()
);
editMenu.add(selectAllItem);
menuBar.add(editMenu);
// ๋์๋ง ๋ฉ๋ด
JMenu helpMenu = new JMenu("๋์๋ง(H)");
helpMenu.setMnemonic('H');
helpMenu.getAccessibleContext().setAccessibleDescription(
"๋์๋ง๊ณผ ์ ๋ณด๋ฅผ ์ ๊ณตํฉ๋๋ค");
JMenuItem aboutItem = createMenuItem(
"์ ๋ณด(A)", 'A',
null,
"ํ๋ก๊ทธ๋จ ์ ๋ณด๋ฅผ ํ์ํฉ๋๋ค",
e -> showAbout()
);
helpMenu.add(aboutItem);
menuBar.add(helpMenu);
return menuBar;
}
private JMenuItem createMenuItem(
String text,
char mnemonic,
KeyStroke accelerator,
String description,
ActionListener action) {
JMenuItem item = new JMenuItem(text);
item.setMnemonic(mnemonic);
if (accelerator != null) {
item.setAccelerator(accelerator);
}
item.getAccessibleContext().setAccessibleDescription(description);
item.addActionListener(action);
return item;
}
private void setupKeyboardShortcuts() {
// ์ถ๊ฐ ํค๋ณด๋ ๋จ์ถํค ์ค์
InputMap inputMap = textArea.getInputMap(
JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = textArea.getActionMap();
// Ctrl+Z: ์คํ ์ทจ์
inputMap.put(
KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK),
"undo");
actionMap.put("undo", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
undo();
}
});
// Ctrl+Y: ๋ค์ ์คํ
inputMap.put(
KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK),
"redo");
actionMap.put("redo", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
redo();
}
});
}
private void newFile() {
if (checkSaveBeforeAction()) {
textArea.setText("");
currentFile = null;
isModified = false;
updateTitle();
updateStatus();
announceToScreenReader("์ ๋ฌธ์๋ฅผ ๋ง๋ค์์ต๋๋ค");
}
}
private void openFile() {
if (!checkSaveBeforeAction()) {
return;
}
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("ํ์ผ ์ด๊ธฐ");
// ํ์ผ ์ ํ๊ธฐ ์ ๊ทผ์ฑ
fileChooser.getAccessibleContext().setAccessibleDescription(
"์ด ํ์ผ์ ์ ํํ์ธ์");
int result = fileChooser.showOpenDialog(this);
if (result == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
try {
StringBuilder content = new StringBuilder();
BufferedReader reader = new BufferedReader(
new FileReader(file));
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
reader.close();
textArea.setText(content.toString());
currentFile = file;
isModified = false;
updateTitle();
updateStatus();
announceToScreenReader("ํ์ผ์ ์ด์์ต๋๋ค: " +
file.getName());
} catch (IOException ex) {
showError("ํ์ผ์ ์ด ์ ์์ต๋๋ค: " + ex.getMessage());
}
}
}
private void saveFile() {
if (currentFile == null) {
saveFileAs();
} else {
try {
BufferedWriter writer = new BufferedWriter(
new FileWriter(currentFile));
writer.write(textArea.getText());
writer.close();
isModified = false;
updateTitle();
updateStatus();
announceToScreenReader("ํ์ผ์ ์ ์ฅํ์ต๋๋ค");
} catch (IOException ex) {
showError("ํ์ผ์ ์ ์ฅํ ์ ์์ต๋๋ค: " + ex.getMessage());
}
}
}
private void saveFileAs() {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("๋ค๋ฅธ ์ด๋ฆ์ผ๋ก ์ ์ฅ");
// ํ์ผ ์ ํ๊ธฐ ์ ๊ทผ์ฑ
fileChooser.getAccessibleContext().setAccessibleDescription(
"์ ์ฅํ ํ์ผ ์ด๋ฆ์ ์
๋ ฅํ์ธ์");
int result = fileChooser.showSaveDialog(this);
if (result == JFileChooser.APPROVE_OPTION) {
currentFile = fileChooser.getSelectedFile();
saveFile();
}
}
private void undo() {
if (undoManager.canUndo()) {
undoManager.undo();
announceToScreenReader("์คํ ์ทจ์ํ์ต๋๋ค");
} else {
announceToScreenReader("๋ ์ด์ ์ทจ์ํ ์ ์์ต๋๋ค");
}
}
private void redo() {
if (undoManager.canRedo()) {
undoManager.redo();
announceToScreenReader("๋ค์ ์คํํ์ต๋๋ค");
} else {
announceToScreenReader("๋ ์ด์ ๋ค์ ์คํํ ์ ์์ต๋๋ค");
}
}
private boolean checkSaveBeforeAction() {
if (isModified) {
int result = JOptionPane.showConfirmDialog(
this,
"๋ณ๊ฒฝ์ฌํญ์ ์ ์ฅํ์๊ฒ ์ต๋๊น?",
"ํ์ธ",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE
);
if (result == JOptionPane.YES_OPTION) {
saveFile();
return !isModified; // ์ ์ฅ ์ฑ๊ณต ์ true
} else if (result == JOptionPane.CANCEL_OPTION) {
return false;
}
}
return true;
}
private void exitApplication() {
if (checkSaveBeforeAction()) {
System.exit(0);
}
}
private void showAbout() {
String message =
"์ ๊ทผ ๊ฐ๋ฅํ ํ
์คํธ ์๋ํฐ v1.0\n\n" +
"๋ชจ๋ ์ฌ์ฉ์๋ฅผ ์ํ ํ
์คํธ ํธ์ง๊ธฐ์
๋๋ค.\n" +
"์์ ํ ํค๋ณด๋ ์ ๊ทผ์ฑ๊ณผ ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ง์์ ์ ๊ณตํฉ๋๋ค.\n\n" +
"๋จ์ถํค:\n" +
"Ctrl+N: ์ ํ์ผ\n" +
"Ctrl+O: ์ด๊ธฐ\n" +
"Ctrl+S: ์ ์ฅ\n" +
"Ctrl+Z: ์คํ ์ทจ์\n" +
"Ctrl+Y: ๋ค์ ์คํ";
JOptionPane.showMessageDialog(
this,
message,
"์ ๋ณด",
JOptionPane.INFORMATION_MESSAGE
);
}
private void showError(String message) {
JOptionPane.showMessageDialog(
this,
message,
"์ค๋ฅ",
JOptionPane.ERROR_MESSAGE
);
announceToScreenReader("์ค๋ฅ: " + message);
}
private void documentChanged() {
isModified = true;
updateTitle();
updateStatus();
}
private void updateTitle() {
String title = "์ ๊ทผ ๊ฐ๋ฅํ ํ
์คํธ ์๋ํฐ";
if (currentFile != null) {
title += " - " + currentFile.getName();
}
if (isModified) {
title += " *";
}
setTitle(title);
}
private void updateStatus() {
int lines = textArea.getLineCount();
int chars = textArea.getText().length();
String status = String.format(
"์ค: %d | ๋ฌธ์: %d%s",
lines,
chars,
isModified ? " | ์์ ๋จ" : ""
);
statusLabel.setText(status);
// ์ํ ๋ณ๊ฒฝ์ ์คํฌ๋ฆฐ ๋ฆฌ๋์ ์๋ฆผ
statusLabel.getAccessibleContext().firePropertyChange(
AccessibleContext.ACCESSIBLE_TEXT_PROPERTY,
null,
status
);
}
/**
* ์คํฌ๋ฆฐ ๋ฆฌ๋์ ๋ฉ์์ง๋ฅผ ์๋ฆฝ๋๋ค.
*/
private void announceToScreenReader(String message) {
// ์์ ๋ ์ด๋ธ์ ์ฌ์ฉํ ์๋ฆผ
JLabel announcement = new JLabel(message);
announcement.getAccessibleContext().setAccessibleName(message);
announcement.getAccessibleContext().firePropertyChange(
AccessibleContext.ACCESSIBLE_NAME_PROPERTY,
null,
message
);
}
public static void main(String[] args) {
// Look and Feel ์ค์
try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(() -> new AccessibleTextEditor());
}
}
์ด ํ ์คํธ ์๋ํฐ๋ ์์ ํ ์ ๊ทผ์ฑ์ ๊ฐ์ถ๊ณ ์์ด:
- โ ๋ชจ๋ ๊ธฐ๋ฅ์ ํค๋ณด๋ ๋จ์ถํค ์ ๊ณต
- โ ๋ฉ๋ด์ ๋ฉ๋ด ํญ๋ชฉ์ ๋๋ชจ๋ ์ค์
- โ ๋ชจ๋ UI ์์์ ๋ช ํํ ์ ๊ทผ์ฑ ์ด๋ฆ๊ณผ ์ค๋ช
- โ ์ํ ๋ณ๊ฒฝ ์ ์คํฌ๋ฆฐ ๋ฆฌ๋์ ์๋ฆผ
- โ ํ์ผ ๋ค์ด์ผ๋ก๊ทธ์ ์ ๊ทผ์ฑ ์ค์
- โ ์คํ ์ทจ์/๋ค์ ์คํ ๊ธฐ๋ฅ
- โ ์ ์ฅ ํ์ธ ๋ค์ด์ผ๋ก๊ทธ
- โ ์ํ ํ์์ค์ ํตํ ์ค์๊ฐ ์ ๋ณด ์ ๊ณต
์ด๋ฐ ์์ ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค ๋๋ ์ ๊ทผ์ฑ์ ์ฒ์๋ถํฐ ๊ณ ๋ คํ๋ฉด, ๋ชจ๋ ์ฌ์ฉ์๊ฐ ๋๋ฑํ๊ฒ ์ฌ์ฉํ ์ ์๋ ์ํํธ์จ์ด๋ฅผ ๋ง๋ค ์ ์์ด! ๐
๐ฎ ๋ฏธ๋์ ์ ๊ทผ์ฑ: ํธ๋ ๋์ ์ ๋ง
์ ๊ทผ์ฑ ๊ธฐ์ ์ ๊ณ์ ๋ฐ์ ํ๊ณ ์์ด. ์์ผ๋ก ์ด๋ค ๋ณํ๊ฐ ์์์ง ์ดํด๋ณด์! ๐
๐ค AI์ ๋จธ์ ๋ฌ๋์ ํ์ฉํ ์ ๊ทผ์ฑ
์ธ๊ณต์ง๋ฅ ๊ธฐ์ ์ด ์ ๊ทผ์ฑ ๋ถ์ผ์๋ ํฐ ๋ณํ๋ฅผ ๊ฐ์ ธ์ค๊ณ ์์ด:
1. ์๋ ๋์ฒด ํ ์คํธ ์์ฑ
์ด๋ฏธ์ง ์ธ์ AI๊ฐ ์๋์ผ๋ก ์ด๋ฏธ์ง์ ๋ด์ฉ์ ์ค๋ช ํ๋ ๋์ฒด ํ ์คํธ๋ฅผ ์์ฑํด์ค. ๊ฐ๋ฐ์๊ฐ ์ผ์ผ์ด ์์ฑํ์ง ์์๋ ๋๋ ๊ฑฐ์ง.
2. ์ค์๊ฐ ์์ฑ ์ธ์๊ณผ ์๋ง
์ค๋์ค ์ฝํ ์ธ ๋ฅผ ์ค์๊ฐ์ผ๋ก ํ ์คํธ๋ก ๋ณํํด์ ์ฒญ๊ฐ ์ฅ์ ์ธ๋ ์ ๊ทผํ ์ ์๊ฒ ํด์ค.
3. ์๋ ์์ ๋๋น ์กฐ์
AI๊ฐ ์์ ์กฐํฉ์ ๋ถ์ํด์ ์๋์ผ๋ก ์ถฉ๋ถํ ๋๋น๋ฅผ ์ ๊ณตํ๋๋ก ์กฐ์ ํด์ค.
4. ์ปจํ ์คํธ ๊ธฐ๋ฐ ๋์๋ง
์ฌ์ฉ์์ ํ๋ ํจํด์ ํ์ตํด์ ํ์ํ ์๊ฐ์ ์ ์ ํ ๋์๋ง์ ์ ๊ณตํด์ค.
๐ ์น๊ณผ ๋ค์ดํฐ๋ธ์ ๊ฒฝ๊ณ ํ๋ฌผ๊ธฐ
Progressive Web Apps (PWA)์ ๊ฐ์ ๊ธฐ์ ์ด ๋ฐ์ ํ๋ฉด์, ์น๊ณผ ๋ค์ดํฐ๋ธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฒฝ๊ณ๊ฐ ํ๋ ค์ง๊ณ ์์ด. ์๋ฐ ๊ฐ๋ฐ์๋ ์ด๋ฐ ํธ๋ ๋๋ฅผ ์ดํดํ๊ณ ์์ด์ผ ํด:
// JavaFX์ ์น ๊ธฐ์ ์ ํตํฉ ์์
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class HybridAccessibleApp extends Application {
@Override
public void start(Stage primaryStage) {
WebView webView = new WebView();
WebEngine engine = webView.getEngine();
// ์๋ฐ์ ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ ๋ธ๋ฆฌ์ง
engine.getLoadWorker().stateProperty().addListener(
(obs, oldState, newState) -> {
if (newState == javafx.concurrent.Worker.State.SUCCEEDED) {
JSObject window = (JSObject) engine.executeScript("window");
window.setMember("javaApp", new JavaBridge());
}
}
);
// ์ ๊ทผ์ฑ์ด ๊ณ ๋ ค๋ ํ์ด๋ธ๋ฆฌ๋ ์ฑ ๋ก๋
String html = """
<meta charset="UTF-8">
<title>ํ์ด๋ธ๋ฆฌ๋ ์ ๊ทผ ๊ฐ๋ฅํ ์ฑ</title>
<h1>ํ์ด๋ธ๋ฆฌ๋ ์ ํ๋ฆฌ์ผ์ด์
</h1>
<button onclick="javaApp.performAction('save')" aria-label="์ ์ฅ">
์ ์ฅ
</button>
<div role="status" aria-live="polite" id="status">
์ค๋น๋จ
</div>
""";
engine.loadContent(html);
Scene scene = new Scene(webView, 800, 600);
primaryStage.setScene(scene);
primaryStage.setTitle("ํ์ด๋ธ๋ฆฌ๋ ์ ๊ทผ ๊ฐ๋ฅํ ์ฑ");
primaryStage.show();
}
// ์๋ฐ์คํฌ๋ฆฝํธ์์ ํธ์ถํ ์ ์๋ ์๋ฐ ํด๋์ค
public class JavaBridge {
public void performAction(String action) {
System.out.println("์๋ฐ์์ ์คํ: " + action);
// ์ค์ ์๋ฐ ๋ก์ง ์คํ
}
}
public static void main(String[] args) {
launch(args);
}
}
๐ฑ ๋ชจ๋ฐ์ผ๊ณผ ํฌ๋ก์ค ํ๋ซํผ
์๋ฐ๋ Android ๊ฐ๋ฐ์์๋ ์ค์ํ ์ญํ ์ ํ๊ณ ์์ด. ๋ชจ๋ฐ์ผ ์ ๊ทผ์ฑ๋ ๊ณ ๋ คํด์ผ ํด:
โข ํฐ์น ํ๊ฒ ํฌ๊ธฐ: ์ต์ 44x44 ํฝ์ (iOS), 48x48dp (Android)
โข ์ ์ค์ฒ ๋์: ๋ณต์กํ ์ ์ค์ฒ์ ๋ํ ๊ฐ๋จํ ๋์ ์ ๊ณต
โข ํ๋ฉด ํ์ : ๊ฐ๋ก/์ธ๋ก ๋ชจ๋ ๋ชจ๋ ์ง์
โข ๋์ ํ์ : ์ฌ์ฉ์๊ฐ ์ค์ ํ ๊ธ๊ผด ํฌ๊ธฐ ์กด์ค
โข ๋คํฌ ๋ชจ๋: ์์คํ ์ค์ ์ ๋ฐ๋ฅธ ํ ๋ง ๋ณ๊ฒฝ
๐ผ ์ฌ๋ฅ๋ท์์ ์ ๊ทผ์ฑ ์ ๋ฌธ๊ฐ ๋๊ธฐ
์ ๊ทผ์ฑ์ ์ด์ ์ ํ์ด ์๋ ํ์์ผ. ๊ทธ๋ฆฌ๊ณ ์ด ๋ถ์ผ์ ์ ๋ฌธ๊ฐ๋ ์์ง ๋ง์ง ์์์, ํฐ ๊ธฐํ๊ฐ ์์ด! ๐
์ฌ๋ฅ๋ท(https://www.jaenung.net)์์๋ ๋ค์ํ ํ๋ก๊ทธ๋๋ฐ ์ฌ๋ฅ์ ๊ฑฐ๋ํ ์ ์๋๋ฐ, ์ ๊ทผ์ฑ ์ปจ์คํ ์ด๋ ์ ๊ทผ ๊ฐ๋ฅํ ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ฉด ์ ๋ง ์ข์ ๋ฐ์์ ์ป์ ์ ์์ ๊ฑฐ์ผ. ๋ง์ ๊ธฐ์ ๋ค์ด ์ ๊ทผ์ฑ ์ค์๋ฅผ ๋ฒ์ ์ผ๋ก ์๊ตฌ๋ฐ๊ณ ์์ง๋ง, ์ด๋ป๊ฒ ๊ตฌํํด์ผ ํ ์ง ๋ชจ๋ฅด๋ ๊ฒฝ์ฐ๊ฐ ๋ง๊ฑฐ๋ .
๐ฏ ์ ๊ทผ์ฑ ๊ฐ์ฌ ๋ฐ ์ปจ์คํ
๊ธฐ์กด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ๊ทผ์ฑ ๋ฌธ์ ๋ฅผ ์ง๋จํ๊ณ ๊ฐ์ ๋ฐฉ์์ ์ ์
๐ฏ ์ ๊ทผ ๊ฐ๋ฅํ UI ์ปดํฌ๋ํธ ๊ฐ๋ฐ
์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ ๊ทผ์ฑ ์ปดํฌ๋ํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ์
๐ฏ ์ ๊ทผ์ฑ ๊ต์ก ๋ฐ ๋ฉํ ๋ง
๊ฐ๋ฐํ์๊ฒ ์ ๊ทผ์ฑ ๊ฐ๋ฐ ๋ฐฉ๋ฒ๋ก ๊ต์ก
๐ฏ ์๋ํ ํ ์คํธ ๋๊ตฌ ๊ฐ๋ฐ
์ ๊ทผ์ฑ ๊ฒ์ฌ๋ฅผ ์๋ํํ๋ ๋๊ตฌ ์ ์
๐ฏ ์ ๊ทผ์ฑ ๋ฌธ์ํ
VPAT(Voluntary Product Accessibility Template) ์์ฑ ์ง์
์ ๊ทผ์ฑ ์ ๋ฌธ ์ง์์ ๊ฐ์ถ ๊ฐ๋ฐ์๋ ์์ฅ์์ ๋์ ๊ฐ์น๋ฅผ ์ธ์ ๋ฐ์. ํนํ ๊ณต๊ณต๊ธฐ๊ด์ด๋ ๋๊ธฐ์ ์์๋ ์ ๊ทผ์ฑ ์ค์๊ฐ ํ์์ด๊ธฐ ๋๋ฌธ์, ์ด ๋ถ์ผ์ ์ ๋ฌธ๊ฐ์ ๋ํ ์์๊ฐ ๊ณ์ ์ฆ๊ฐํ๊ณ ์์ด! ๐
๐ ํ์ต ๋ฆฌ์์ค์ ์ปค๋ฎค๋ํฐ
์ ๊ทผ์ฑ์ ๋ํด ๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด, ๋ค์ ๋ฆฌ์์ค๋ค์ ํ์ฉํด๋ด:
๊ณต์ ๋ฌธ์
โข Oracle Java Accessibility Guide
โข Java Accessibility API Specification
โข WCAG 2.1 Guidelines
๋๊ตฌ
โข Java Access Bridge
โข NVDA (๋ฌด๋ฃ ์คํฌ๋ฆฐ ๋ฆฌ๋)
โข aXe DevTools (์น ์ ๊ทผ์ฑ ํ ์คํธ)
โข Color Contrast Analyzer
์ปค๋ฎค๋ํฐ
โข WebAIM (Web Accessibility In Mind)
โข A11y Project
โข Inclusive Design 24 (์ฐ๋ก ์ปจํผ๋ฐ์ค)
๋ฒ๊ท ๋ฐ ํ์ค
โข ํ๊ตญ ์น ์ ๊ทผ์ฑ ์ธ์ฆ์ ๋
โข ์ฅ์ ์ธ์ฐจ๋ณ๊ธ์ง๋ฒ
โข Section 508 (๋ฏธ๊ตญ)
โข EN 301 549 (์ ๋ฝ)
์ ๊ทผ์ฑ์ ๋จ์ํ ๊ธฐ์ ์ ์ธ ๋ฌธ์ ๊ฐ ์๋๋ผ, ์ฌํ์ ์ฑ ์์ด์ ์ค๋ฆฌ์ ์๋ฌด์ผ. ์ฐ๋ฆฌ๊ฐ ๋ง๋๋ ์ํํธ์จ์ด๊ฐ ์ ๋ง ๋ชจ๋ ์ฌ๋์ ์ํ ๊ฒ์ด ๋๋ ค๋ฉด, ์ ๊ทผ์ฑ์ ํญ์ ์ผ๋์ ๋๊ณ ๊ฐ๋ฐํด์ผ ํด. ๐
๐ฏ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์! ์ ๋ง ๊ธด ์ฌ์ ์ด์์ง? ์ฐ๋ฆฌ๋ ์๋ฐ์ ์ ๊ทผ์ฑ API๋ถํฐ ์์ํด์, ์ค์ ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ, ํ ์คํธ, ๊ทธ๋ฆฌ๊ณ ๋ฏธ๋ ํธ๋ ๋๊น์ง ๋ชจ๋ ์ดํด๋ดค์ด. ๐
์ ๊ทผ์ฑ์ ์ฒ์์๋ ๋ณต์กํ๊ณ ์ด๋ ค์ ๋ณด์ผ ์ ์์ด. ํ์ง๋ง ํ ๋ฒ ์ต์ํด์ง๋ฉด, ์์ฐ์ค๋ฝ๊ฒ ์ ๊ทผ ๊ฐ๋ฅํ ์ฝ๋๋ฅผ ์์ฑํ๊ฒ ๋ผ. ๊ทธ๋ฆฌ๊ณ ๋ฌด์๋ณด๋ค ์ค์ํ ๊ฑด, ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์ํํธ์จ์ด๊ฐ ๋ ๋ง์ ์ฌ๋๋ค์๊ฒ ๋์์ด ๋๋ค๋ ๋ณด๋์ด์ผ. ๐
1๏ธโฃ ์ ๊ทผ์ฑ์ ์ ํ์ด ์๋ ํ์
์ ์ธ๊ณ ์ธ๊ตฌ์ 15%๊ฐ ์ด๋ค ํํ๋ก๋ ์ฅ์ ๋ฅผ ๊ฐ์ง๊ณ ์์ด. ์ด๋ค๋ ์ฐ๋ฆฌ์ ์ฌ์ฉ์์ผ.
2๏ธโฃ Java Accessibility API ํ์ฉ
Accessible, AccessibleContext, AccessibleRole ๋ฑ์ ์ธํฐํ์ด์ค๋ฅผ ์ดํดํ๊ณ ํ์ฉํ์.
3๏ธโฃ ํค๋ณด๋ ์ ๊ทผ์ฑ
๋ชจ๋ ๊ธฐ๋ฅ์ด ํค๋ณด๋๋ง์ผ๋ก๋ ์ฌ์ฉ ๊ฐ๋ฅํด์ผ ํด. ๋ง์ฐ์ค๋ ์ ํ์ฌํญ์ด์ผ.
4๏ธโฃ ๋ช ํํ ๋ ์ด๋ธ๊ณผ ์ค๋ช
๋ชจ๋ UI ์์์ ์๋ฏธ ์๋ ์ด๋ฆ๊ณผ ์ค๋ช ์ ์ ๊ณตํ์.
5๏ธโฃ ํ ์คํธ, ํ ์คํธ, ํ ์คํธ
์๋ํ ํ ์คํธ์ ์ค์ ๋ณด์กฐ ๊ธฐ์ ์ ์ฌ์ฉํ ์๋ ํ ์คํธ๋ฅผ ๋ณํํ์.
6๏ธโฃ ์ฒ์๋ถํฐ ๊ณ ๋ คํ๊ธฐ
์ ๊ทผ์ฑ์ ๋์ค์ ์ถ๊ฐํ๋ ๊ฒ ์๋๋ผ, ์ฒ์๋ถํฐ ์ค๊ณ์ ํฌํจ๋์ด์ผ ํด.
7๏ธโฃ ์ง์์ ์ธ ํ์ต
์ ๊ทผ์ฑ ๊ธฐ์ ๊ณผ ํ์ค์ ๊ณ์ ๋ฐ์ ํ๊ณ ์์ด. ์ต์ ํธ๋ ๋๋ฅผ ๋ฐ๋ผ๊ฐ์.
์ด์ ์ฌ๋ฌ๋ถ์ ์ ๊ทผ ๊ฐ๋ฅํ ์๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค ์ ์๋ ์ง์๊ณผ ๋๊ตฌ๋ฅผ ๊ฐ์ถ์์ด! ๐
๋ค์ ํ๋ก์ ํธ๋ฅผ ์์ํ ๋, ์ด ๊ธ์์ ๋ฐฐ์ด ๋ด์ฉ๋ค์ ์ ์ฉํด๋ด. ์ฒ์์๋ ์๊ฐ์ด ์ข ๋ ๊ฑธ๋ฆด ์ ์์ง๋ง, ๊ณง ์์ฐ์ค๋ฌ์ด ์ต๊ด์ด ๋ ๊ฑฐ์ผ. ๊ทธ๋ฆฌ๊ณ ์ฌ๋ฌ๋ถ์ด ๋ง๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ชจ๋ ์ฌ๋์ด ์ฌ์ฉํ ์ ์๋ค๋ ์ฌ์ค์ ์๋ถ์ฌ์ ๋๋ ์ ์์ ๊ฑฐ์ผ! ๐ช
ํน์ ์ ๊ทผ์ฑ ๊ด๋ จํด์ ๋์์ด ํ์ํ๊ฑฐ๋, ๋ ๊น์ด ์๋ ์ง์์ ์ป๊ณ ์ถ๋ค๋ฉด ์ฌ๋ฅ๋ท์์ ์ ๋ฌธ๊ฐ๋ค์ ๋์์ ๋ฐ์ ์๋ ์์ด. ํจ๊ป ๋ฐฐ์ฐ๊ณ ์ฑ์ฅํ๋ ์ปค๋ฎค๋ํฐ์ ํ์ ํ์ฉํ์! ๐ค
์, ์ด์ ๋๊ฐ์ ๋ชจ๋๋ฅผ ์ํ ๋ฉ์ง ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค์ด๋ณด์! ํ์ดํ ! ๐โจ
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.