IT 프로그래밍-Android

[Android]커스텀 키보드 만들기(4/4) - 천지인 키보드 만들기

godsangin 2019. 11. 12. 11:15
반응형

드디어 마지막 시간입니다..! 오늘은 지난번에 정의했던 HangulMaker를 이용하여 ChunjiinMaker를 만들어보도록 하겠습니다.

우선 이전 키보드들과 마찬가지로 KeyboardChunjiin을 작성합니다.

val firstLineText = listOf<String>("ㅣ", "·", "ㅡ","DEL")
val secondLineText = listOf<String>("ㄱㅋ", "ㄴㄹ", "ㄷㅌ", "Enter")
val thirdLineText = listOf<String>("ㅂㅍ","ㅅㅎ","ㅈㅊ",".,?!")
val fourthLineText = listOf<String>("한/영", "ㅇㅁ", "space", "!#1")

지금까지 잘 따라오셨다면 KeyboardEnglish 또는 KeyboardKorean을 참고하여 작성하실 수 있으리라 믿습니다..!

그런 뒤에 getMyOnclickListener를 통하여 다음과 같이 ChunjiinMaker를 사용할 수 있습니다.

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))
                    chunjiinMaker.clear()
                }
                when (actionButton.text.toString()) {
                    "한/영" -> {
                        keyboardInterationListener.modechange(0)
                        chunjiinMaker.clear()
                        chunjiinMaker.clearChunjiin()
                    }
                    "!#1" -> {
                        keyboardInterationListener.modechange(2)
                        chunjiinMaker.clear()
                        chunjiinMaker.clearChunjiin()
                    }
                    ".,?!" -> {
                        chunjiinMaker.commonKeywordCommit()
                    }
                    else -> {
                        playClick(actionButton.text.toString().toCharArray().get(0).toInt())
                        try{
                            val myText = Integer.parseInt(actionButton.text.toString())
                            chunjiinMaker.directlyCommit()
                            inputConnection.commitText(actionButton.text.toString(), 1)
                        }catch (e: NumberFormatException){
                            chunjiinMaker.commit(actionButton.text.toString().toCharArray().get(0))
                        }
                    }
                }
            })
            actionButton.setOnClickListener(clickListener)
            return clickListener
        }

물론 chunjiinMaker를 newInstance에서 초기화해야겠죠 ??

여기서 중요한 점은 천지인 키보드의 첫번째 문자만을 commit한다는 점입니다. 그리고 나머지 처리는 모두 ChunjiinMaker가 담당하도록 합니다.

ChunjiinMaker는 다음과 같이 HangulMaker를 상속받을 수 있도록 작성합니다.

class ChunjiinMaker(val inputConnection: InputConnection): HangulMaker(inputConnection)

그리고 필드는 다음과 같이 구상중인 문자를 담을 testChar와 각각 관련있는 리스트, 모든 리스트를 포함하는 리스트, 그리고 현재 입력중인 상태의 리스트 크게 네가지로 구분지을 수 있습니다.

var testChar:Char = '\u0000'
var isComposingMoum = false
var onlyMoum = false
var gkList = listOf<Char>('ㄱ','ㅋ','ㄲ')
var nlList = listOf<Char>('ㄴ','ㄹ')
var dtList = listOf<Char>('ㄷ','ㅌ','ㄸ')
var bpList = listOf<Char>('ㅂ', 'ㅍ', 'ㅃ')
var shList = listOf<Char>('ㅅ', 'ㅎ', 'ㅆ')
var jchList = listOf<Char>('ㅈ','ㅊ','ㅉ')
var aiueomList = listOf<Char>('ㅇ','ㅁ')
val commonKeywords = listOf<String>(".",",","?","!")
var wholeList:List<List<Char>> = listOf(gkList, nlList, dtList, bpList, shList, jchList, aiueomList)
var myList:List<Char>? = null
var listIndex = 0
var junFlagChunjiin = '\u0000'
var keywordIndex = 0
var keywordExpect = false
var stateThreeDot = false

가장 중요한 commit함수입니다.

override fun commit(c:Char){
        if(keywordExpect){
            inputConnection.finishComposingText()
            keywordExpect = false
        }
        if(c == 'ㅣ' || c == '·' || c == 'ㅡ'){//모음구성
            if(super.state == 0){//모음만으로 구성된 글자 ex) ㅠㅠㅠㅠㅠㅠㅠ
                onlyMoum = true
                if(testChar == '\u0000'){
                    testChar = c
                    inputConnection.setComposingText(testChar.toString(), 1)
                }
                else if(combination(c)){
                    inputConnection.setComposingText(testChar.toString(), 1)
                }
                else{
                    inputConnection.commitText(testChar.toString(), 1)
                    testChar = c
                    inputConnection.setComposingText(testChar.toString(), 1)
                }
                junFlagChunjiin = '\u0000'
            }
            else if(!isComposingMoum){
                onlyMoum = false
                testChar = c
                if(c == '·'){
                    if(super.state == 3){
                        //종성까지 추가된 상태
                        inputConnection.setComposingText(super.makeHan().toString() + testChar, 2)
                        stateThreeDot = true
                    }
                    else if(!super.junAvailable()){//더이상 추가될 수 없는 모음일 경우 ex) 왜 + ·
                        super.directlyCommit()
                        inputConnection.setComposingText(testChar.toString(), 1)
                    }
                    else{
                        inputConnection.setComposingText(super.makeHan().toString() + testChar, 2)
                        super.state = 2
                    }
                }
                else{
                    super.commit(testChar)
                }
                listIndex = 0
                isComposingMoum = true
            }
            else{
                if(combination(c)){//이중모음으로 선언 가능한 경우
                    if(testChar == '‥'){
                        inputConnection.setComposingText(super.makeHan().toString() + testChar, 2)
                    }
                    else if(super.state == 2){//모음이 기대되는 상태
                        if(super.isDoubleJun()){//이중모음인 경우 두 모음을 모두 지운다.
                            super.delete()
                            super.delete()
                        }
                        else{//이중모음이 아닌 경우
                            super.delete()
                        }
                        super.commit(testChar)
                        super.junFlag = junFlagChunjiin//이중 모음일 경우 이전 모음을 설정한다.
                        junFlagChunjiin = '\u0000'//초기화
                    }
                    else{
                        super.commit(testChar)
//                        isComposingMoum = false
                    }
                }
                else{//이 + ㅣ와 같은 경우 이전 글자를 commit하고 모음으로만 구성된다고 설정한다.
                    super.directlyCommit()
                    testChar = c
                    inputConnection.setComposingText(testChar.toString(), 1)
                    isComposingMoum = false
                    onlyMoum = true
                }
            }

        }
        else if(myList == null){//첫입력
            if(onlyMoum){//모음으로만 구성된 문자를 작성중이었다면 commit한다.
                inputConnection.commitText(testChar.toString(), 1)
            }
            onlyMoum = false
            testChar = c
            for(list in wholeList){//전체 리스트를 순회하며 현재 텍스트를 포함하는 리스트를 찾는다.
                if(list.indexOf(testChar) >= 0){
                    myList = list
                    listIndex = 1
                }
            }
            super.commit(testChar)
            isComposingMoum = false
        }
        else if(myList?.indexOf(c)!! >= 0){//현재 작성중인 문자에서 파생될수 있는 문자를 출력 ex) ㄱ -> ㅋ
            if(onlyMoum){
                inputConnection.commitText(testChar.toString(), 1)
            }
            onlyMoum = false
            if(listIndex == myList?.size){//더이상 파생할 수 없을 경우 첫번째 문자로 돌아간다. ex) ㄲ -> ㄱ
                listIndex = 0
            }
            testChar = myList?.get(listIndex)!!
            listIndex++
            if(super.state == 1 || super.state == 3){//단어를 대체하기 위하여 delete한 뒤 새로운 문자를 commit한다.
                super.delete()
                super.commit(testChar)
            }
            else{
                super.commit(testChar)
            }
            isComposingMoum = false
        }
        else{
            onlyMoum = false
            testChar = c
            for(list in wholeList){
                if(list.indexOf(testChar) >= 0){
                    myList = list
                    listIndex = 1
                }
            }
            super.commit(c)
            isComposingMoum = false
        }
    }
  

모든 상태와 무관하게 작동할 수 있도록 코드를 작성하였지만, 아직은 계속해서 오류를 발견하고 수정해 나가는 단계입니다. 크게 모음구성, 자음의 첫입력, 한 곳을 두 번 이상 클릭하여 발생할 수 있는 자음 입력, 비슷한 자음이 아닌 다른 자음을 입력하는(myList가 변경되는)입력의 네가지로 구성되어 있습니다. 자세한 코드는 주석을 확인해주세요.

다음은 이중모음을 나타낼 수 있는 조합인지 여부를 판단하는 combination함수 입니다.

fun combination(c:Char):Boolean{//모음을 구성하기 위한 조합 성공시 true리턴
        when(testChar){
            'ㅣ' -> {
                if(c == '·'){
                    testChar = 'ㅏ'
                    return true
                }
                else{
                    return false
                }
            }
            '·' -> {
                if(c == 'ㅡ'){
                    testChar = 'ㅗ'
                    return true
                }
                else if(c == 'ㅣ'){
                    testChar = 'ㅓ'
                    return true
                }
                else if(c == '·'){
                    testChar = '‥'
                    return true
                }
                else{
                    return false
                }
            }
            '‥' -> {
                if(c == 'ㅣ'){
                    testChar = 'ㅕ'
                    return true
                }
                else if(c == 'ㅡ'){
                    testChar = 'ㅛ'
                    return true
                }
                else{
                    return false
                }
            }
            'ㅡ' -> {
                if(c == 'ㅣ'){
                    testChar = 'ㅢ'
                    junFlagChunjiin = 'ㅡ'
                    return true
                }
                else if(c == '·'){
                    testChar = 'ㅜ'
                    return true
                }
                else{
                    return false
                }
            }
            'ㅏ' -> {
                if(c == '·'){
                    testChar = 'ㅑ'
                    return true
                }
                else if(c == 'ㅣ'){
                    testChar = 'ㅐ'
                    return true
                }
                else {
                    return false
                }
            }
            'ㅓ' -> {
                if(c == 'ㅣ'){
                    testChar = 'ㅔ'
                    return true
                }
                else{
                    return false
                }
            }
            'ㅑ' -> {
                if(c == 'ㅣ'){
                    testChar = 'ㅒ'
                    return true
                }
                else{
                    return false
                }
            }
            'ㅕ' -> {
                if(c == 'ㅣ'){
                    testChar = 'ㅖ'
                    return true
                }
                else{
                    return false
                }
            }
            'ㅜ' -> {
                if(c == '·'){
                    testChar = 'ㅠ'
                    return true
                }
                if(c == 'ㅣ'){
                    testChar = 'ㅟ'
                    junFlagChunjiin = 'ㅜ'
                    return true
                }
                else{
                    return false
                }
            }
            'ㅠ' -> {
                if(c == 'ㅣ'){
                    testChar = 'ㅝ'
                    junFlagChunjiin = 'ㅜ'
                    return true
                }
                else{
                    return false
                }
            }
            'ㅗ' -> {
                if(c == 'ㅣ'){
                    testChar = 'ㅚ'
                    junFlagChunjiin = 'ㅗ'
                    return true
                }
                else{
                    return false
                }
            }
            'ㅚ' -> {
                if(c == '·'){
                    testChar ='ㅘ'
                    junFlagChunjiin = 'ㅗ'
                    return true
                }
                return false
            }
            'ㅘ' -> {
                if(c == 'ㅣ'){
                    testChar = 'ㅙ'
                    junFlagChunjiin = 'ㅗ'
                    return true
                }
                return false
            }
            'ㅝ' -> {
                if(c == 'ㅣ'){
                    testChar = 'ㅞ'
                    junFlagChunjiin = 'ㅜ'
                    return true
                }
                return false
            }
            else -> {
                return false
            }
        }
    }

각각의 문자를 하드매핑하여 나타낼 수 있는 문자가 나온다면 true를 리턴할 수 있도록 하였습니다. 여기서 주의할 점은 이중모음으로 나타내는 문자라면 이전 문자를 따로 분류하기 위하여 junFlagChunjiin이라는 필드에 앞의 모음을 저장하는 것을 볼 수 있습니다. 이는 commit시에 hangulMaker에게 Flag를 전달하기 위함입니다.

다음은 directlyCommit함수입니다.

override fun directlyCommit(){
        super.directlyCommit()
        inputConnection.finishComposingText()
        clearChunjiin()
}

HangulMaker의 directlyCommit을 실행하며 자기 자신도 초기화할 수 있도록 작성합니다.

그런 뒤에 delete함수를 작성합니다.

    override fun delete(){
        if(onlyMoum){//현재 커서가 모음으로만 구성된 문자일 경우
            inputConnection.setComposingText("", 1)
            clearChunjiin()
        }
        else if(stateThreeDot){//HangulMaker의 3번상태(자음+모음+자음)상태에서 .기호가 들어와 있는 상태
            inputConnection.setComposingText(super.makeHan().toString(), 1)
            clearChunjiin()
            stateThreeDot = false
        }
        else if(super.state == 2 && super.isDoubleJun()){//상태 2이면서 이중모음이 들어와 있는 상태
            super.delete()
            setTestCharBefore()
        }
        else {
            clearChunjiin()
            super.delete()
        }
        listIndex = 0
    }

마지막으로 특수문자 입력 함수, 초기화함수, isEmpty함수, 이중모음 이전의 상태를 반환하는 함수를 작성하면 ChunjiinMaker의 작성이 완료됩니다.

    fun commonKeywordCommit(){//특수문자 입력 시
        directlyCommit()
        if(keywordIndex == commonKeywords.size){
            keywordIndex = 0
        }
        inputConnection.setComposingText(commonKeywords[keywordIndex++], 1)
        keywordExpect = true
    }

    fun clearChunjiin(){
        testChar = '\u0000'
        isComposingMoum = false
        myList = null
        listIndex = 0
        onlyMoum = false
    }

    fun isEmpty():Boolean{
        if(super.state == 0 && testChar == '\u0000'){
            return true
        }
        return false
    }
    fun setTestCharBefore(){//이중모음 이전의 상태를 반환
        if(testChar == 'ㅚ' || testChar == 'ㅘ' || testChar == 'ㅙ'){
            testChar = 'ㅗ'
        }
        else if(testChar == 'ㅟ' || testChar == 'ㅝ' || testChar == 'ㅞ'){
            testChar = 'ㅜ'
        }
        else if(testChar == 'ㅢ'){
            testChar = 'ㅡ'
        }
    }

 

그동안 긴 글 읽어주셔서 감사드립니다. 궁금하신 점이나 틀린점이 있다면 언제든지 알려주시면 감사하겠습니다. 글이 도움이 되셨다면 하단의 공감버튼을 눌러주시면 감사하겠습니다.