앞서 말씀드렸듯이 KeyboardView와 Keyboard를 사용하지 않기 위하여 새로운 레이아웃을 정의하고 바인드하는 작업이 필요합니다.
첫번째로 Layout을 작성해야합니다. 키 패드는 재활용 가능성이 다분하기 때문에 키패드 레이아웃을 따로 작성하였습니다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:padding="8dp"
android:text="."/>
<Button
android:id="@+id/key_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="4dp"
android:layout_marginBottom="2dp"
android:layout_marginRight="4dp"
android:layout_marginLeft="2dp"
android:duplicateParentState="true"
android:longClickable="true"
android:background="@drawable/key_background"/>
<ImageView
android:id="@+id/spacial_key"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="4dp"
android:layout_marginBottom="2dp"
android:layout_marginRight="4dp"
android:layout_marginLeft="2dp"
android:duplicateParentState="true"
android:longClickable="true"
android:scaleType="centerInside"
android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<keyboard_item.xml>
키 패드는 실제로 키 버튼이 들어갈 Button과 Button 오른쪽 위로 롱 클릭 시 작성될 특수문자, DELETE 버튼에 사용될 ImageView로 구성되어있습니다.(레이아웃 이름은 keyboard_item입니다)
그리고 이 키 패드를 include하는 실제 keyboardView를 작성합니다.(레이아웃 이름은 keyboard_action입니다)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/numpad_line"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:orientation="horizontal">
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_3"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_4"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_5"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_6"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_7"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_8"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_9"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_0"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
</LinearLayout>
<LinearLayout
android:id="@+id/first_line"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:orientation="horizontal">
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_q"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_w"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_e"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_r"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_t"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_y"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_u"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_i"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_o"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</include>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_p"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
<LinearLayout
android:id="@+id/second_line"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:orientation="horizontal">
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_a"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_s"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_d"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_f"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_g"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_h"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_j"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_k"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_l"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
<LinearLayout
android:id="@+id/third_line"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:orientation="horizontal">
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_caps"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.5"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_z"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_x"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_c"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_v"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_b"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_n"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_m"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_del"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.7"/>
</LinearLayout>
<LinearLayout
android:id="@+id/fourth_line"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:orientation="horizontal">
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_simbols"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_mode_change"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_rest"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_space"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="4"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_dot"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<include
layout="@layout/keyboard_item"
android:id="@+id/action_key_enter"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"/>
</LinearLayout>
</LinearLayout>
<keyboard_action.xml>
위와 같이 4개의 큰 LinearLayout으로 정의되어 있습니다.
다음으로 코드를 작성해 보겠습니다. 각각의 키 패드를 바인드 하기 위한 일반적인 키 패드의 문자열을 정의합니다.
val numpadText = listOf<String>("1","2","3","4","5","6","7","8","9","0")
val firstLineText = listOf<String>("q","w","e","r","t","y","u","i","o","p")
val secondLineText = listOf<String>("a","s","d","f","g","h","j","k","l")
val thirdLineText = listOf<String>("CAPS","z","x","c","v","b","n","m","DEL")
val fourthLineText = listOf<String>("!#1","한/영",",","space",".","Enter")
val myKeysText = ArrayList<List<String>>()
val layoutLines = ArrayList<LinearLayout>()
<KeyboardEnglish.kt>
그런 뒤 싱글톤으로 만들기 위한 newInstance메소드를 작성합니다.
(수정)싱글톤으로 제작한 키보드는 modechange를 실행할 때마다 뷰에 대한 이벤트를 새로 정의하기 때문에 속도 면에서 성능이 크게 저하되는 것을 느낄 수 있었습니다. 그래서 소스가 변경되었습니다.
lateinit var englishLayout: LinearLayout
var inputConnection:InputConnection? = null
set(inputConnection){
field = inputConnection
}
fun init() {
englishLayout = layoutInflater.inflate(R.layout.keyboard_action, null) as LinearLayout
vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
val config = context.getResources().configuration
sharedPreferences = context.getSharedPreferences("setting", Context.MODE_PRIVATE)
val height = sharedPreferences.getInt("keyboardHeight", 150)
sound = sharedPreferences.getInt("keyboardSound", -1)
vibrate = sharedPreferences.getInt("keyboardVibrate", -1)
val numpadLine = englishLayout.findViewById<LinearLayout>(
R.id.numpad_line
)
val firstLine = englishLayout.findViewById<LinearLayout>(
R.id.first_line
)
val secondLine = englishLayout.findViewById<LinearLayout>(
R.id.second_line
)
val thirdLine = englishLayout.findViewById<LinearLayout>(
R.id.third_line
)
val fourthLine = englishLayout.findViewById<LinearLayout>(
R.id.fourth_line
)
if(config.orientation == Configuration.ORIENTATION_LANDSCAPE){
firstLine.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, (height*0.7).toInt())
secondLine.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, (height*0.7).toInt())
thirdLine.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, (height*0.7).toInt())
}else{
firstLine.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, height)
secondLine.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, height)
thirdLine.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, height)
}
myKeysText.clear()
myKeysText.add(numpadText)
myKeysText.add(firstLineText)
myKeysText.add(secondLineText)
myKeysText.add(thirdLineText)
myKeysText.add(fourthLineText)
myLongClickKeysText.clear()
myLongClickKeysText.add(firstLongClickText)
myLongClickKeysText.add(secondLongClickText)
myLongClickKeysText.add(thirdLongClickText)
layoutLines.clear()
layoutLines.add(numpadLine)
layoutLines.add(firstLine)
layoutLines.add(secondLine)
layoutLines.add(thirdLine)
layoutLines.add(fourthLine)
setLayoutComponents()
}
fun getLayout():LinearLayout{
return englishLayout
}
<KeyboardEnglish.kt>
여기서 주의할 점은 위의 keyboard_action레이아웃과 코드 상의 line들의 문자열의 개수가 같아야한다는 점입니다(ArrayIndexOutofBound Exception이 발생합니다). 키 패드의 높이는 우선 150으로 통일하도록 하겠습니다. 본인의 개발 방향에 맞게 수정하시면 될 것 같습니다.
다음으로 setLayoutComponents 메소드입니다.
private fun setLayoutComponents(){
for(line in layoutLines.indices){
val children = layoutLines[line].children.toList()
val myText = myKeysText[line]
for(item in children.indices){
val actionButton = children[item].findViewById<Button>(R.id.key_button)
val spacialKey = children[item].findViewById<ImageView>(R.id.spacial_key)
var myOnClickListener:View.OnClickListener? = null
when(myText[item]){
"space" -> {
spacialKey.setImageResource(R.drawable.ic_space_bar)
spacialKey.visibility = View.VISIBLE
actionButton.visibility = View.GONE
myOnClickListener = getSpaceAction()
spacialKey.setOnClickListener(myOnClickListener)
spacialKey.setOnTouchListener(getOnTouchListener(myOnClickListener))
spacialKey.setBackgroundResource(R.drawable.key_background)
}
"DEL" -> {
spacialKey.setImageResource(R.drawable.del)
spacialKey.visibility = View.VISIBLE
actionButton.visibility = View.GONE
myOnClickListener = getDeleteAction()
spacialKey.setOnClickListener(myOnClickListener)
spacialKey.setOnTouchListener(getOnTouchListener(myOnClickListener))
}
"CAPS" -> {
spacialKey.setImageResource(R.drawable.ic_caps_unlock)
spacialKey.visibility = View.VISIBLE
actionButton.visibility = View.GONE
capsView = spacialKey
myOnClickListener = getCapsAction()
spacialKey.setOnClickListener(myOnClickListener)
spacialKey.setOnTouchListener(getOnTouchListener(myOnClickListener))
spacialKey.setBackgroundResource(R.drawable.key_background)
}
"Enter" -> {
spacialKey.setImageResource(R.drawable.ic_enter)
spacialKey.visibility = View.VISIBLE
actionButton.visibility = View.GONE
myOnClickListener = getEnterAction()
spacialKey.setOnClickListener(myOnClickListener)
spacialKey.setOnTouchListener(getOnTouchListener(myOnClickListener))
spacialKey.setBackgroundResource(R.drawable.key_background)
}
else -> {
actionButton.text = myText[item]
buttons.add(actionButton)
myOnClickListener = getMyClickListener(actionButton)
actionButton.setOnTouchListener(getOnTouchListener(myOnClickListener))
}
}
children[item].setOnClickListener(myOnClickListener)
}
}
}
<KeyboardEnglish.kt>
layout레벨의 모든 LinearLayout을 순회하며 문자열을 button에 바인드해줍니다. 이 때 특수한 키워드는 필요한 방향에 맞게 수정합니다. (UI작업) 여기서 else문은 일반적인 키 패드 작업을 의미합니다.
마지막으로 각 버튼을 눌렀을 때 발생하는 이벤트를 반환하는 getMyClickListener를 작성합니다.
private fun getMyClickListener(actionButton:Button):View.OnClickListener{
val clickListener = (View.OnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
inputConnection.requestCursorUpdates(InputConnection.CURSOR_UPDATE_IMMEDIATE)
}
playVibrate()
val cursorcs:CharSequence? = inputConnection.getSelectedText(InputConnection.GET_TEXT_WITH_STYLES)
if(cursorcs != null && cursorcs.length >= 2){
val eventTime = SystemClock.uptimeMillis()
inputConnection.finishComposingText()
inputConnection.sendKeyEvent(KeyEvent(eventTime, eventTime,
KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL, 0, 0, 0, 0,
KeyEvent.FLAG_SOFT_KEYBOARD))
inputConnection.sendKeyEvent(KeyEvent(SystemClock.uptimeMillis(), eventTime,
KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL, 0, 0, 0, 0,
KeyEvent.FLAG_SOFT_KEYBOARD))
inputConnection.sendKeyEvent(KeyEvent(eventTime, eventTime,
KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT, 0, 0, 0, 0,
KeyEvent.FLAG_SOFT_KEYBOARD))
inputConnection.sendKeyEvent(KeyEvent(SystemClock.uptimeMillis(), eventTime,
KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_LEFT, 0, 0, 0, 0,
KeyEvent.FLAG_SOFT_KEYBOARD))
}
else{
when (actionButton.text.toString()) {
"한/영" -> {
keyboardInterationListener.modechange(1)
}
"!#1" -> {
keyboardInterationListener.modechange(2)
}
else -> {
playClick(
actionButton.text.toString().toCharArray().get(
0
).toInt()
)
inputConnection.commitText(actionButton.text,1)
}
}
}
})
actionButton.setOnClickListener(clickListener)
return clickListener
}
<KeyboardEnglish.kt>
여기서 만약 현재 선택중인 블럭이 존재한다면 해당 블럭을 삭제하고 텍스트를 입력할 수 있도록 합니다. button의 text가 한/영 또는 특수문자일 경우에는 이전에 작성했던 keyboardInteractionListener의 modechange를 호출합니다.
그 이외의 경우에는 button의 텍스트 자체를 inputConnection을 통하여 commit합니다.
영어 키보드의 핵심 기능은 여기까지이고 특수기능, 전체 코드를 확인하고 싶으시다면 댓글을 남겨주시기 바랍니다(여기있는 코드만 복사/붙혀넣기 하면 오류가 많을거예요 ㅠㅠ). 회사측이랑 이야기해보고 깃허브 URL을 첨부하도록 하겠습니다..!
영어 키보드의 경우에는 버튼의 텍스트를 바로 commit하면 되겠지만 여러분이 알고 계시는 한글 키보드는 어떨까요 ??
한글은 text가 완성이 되려면 몇 번이 될지 모르는 클릭을 해야합니다. 다음시간에는 한글 키보드 작성을 위한 오토마타 생성 편으로 찾아뵙겠습니다.
'IT 프로그래밍-Android' 카테고리의 다른 글
[Android]커스텀 키보드 만들기(4/4) - 천지인 키보드 만들기 (0) | 2019.11.12 |
---|---|
[Android] 커스텀 키보드 만들기(3/4) 외전 - HangulMaker (2) | 2019.11.08 |
[Android] 커스텀 키보드 만들기(3/4) - 쿼티 키보드 만들기 (7) | 2019.11.08 |
[Android]커스텀 키보드 만들기(1/4) - InputMethodService (7) | 2019.11.05 |
[Android] 키보드 앱 만들기 (9) | 2019.10.25 |