๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๊ณณ๊ฐ„์—์„œ ์ธ์‹ฌ๋‚œ๋‹ค/์‹ค๋ฌด

[Android ์‚ฝ์งˆ ๊ตํ›ˆ] ์ด๋ชจ์ง€ ํ•˜๋‚˜๋ฅผ ํ•œ ๊ธ€์ž๋กœ ์„ธ๊ธฐ ์œ„ํ•ด ์ปค์Šคํ…€ InputFilter๋ฅผ ๋งŒ๋“ค์—ˆ์–ด์š”.

์š”์•ฝ

์•ˆ๋“œ๋กœ์ด๋“œ ์ž…๋ ฅ์ฐฝ์—์„œ ์ด๋ชจ์ง€ ํ•˜๋‚˜๋ฅผ ํ•œ ๊ธ€์ž๋กœ ์ธ์‹ํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์–ด์š”.

๊ทธ๋ž˜์„œ ์ด๋ชจ์ง€ ํ•˜๋‚˜๋ฅผ ํ•œ ๊ธ€์ž๋กœ ์—ฌ๊ธฐ๊ณ  ๊ธ€์ž ์ˆ˜๋ฅผ ์„ธ๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ž‘์—…์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ €๋Š” ์ปค์Šคํ…€ InputFilter๋ฅผ ๋งŒ๋“ค์—ˆ์–ด์š”.


๊ธฐํš์ƒ ์ž…๋ ฅ์ฐฝ์— ๊ธ€์ž์ˆ˜ ์ œํ•œ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜ ๊ธ€์ž, ์ˆซ์ž, ๊ธฐํ˜ธ, ์ด๋ชจ์ง€๋ฅผ ํฌํ•จํ•ด์„œ ๊ธ€์ž์ˆ˜ ์ œํ•œ์ด ์žˆ์—ˆ๊ณ , ๋‹น์—ฐํžˆ ์ง๊ด€์ ์œผ๋กœ ์ด๋ชจ์ง€ ํ•˜๋‚˜๋Š” ํ•œ ๊ธ€์ž ์•„๋‹ˆ๊ฒ ์Šต๋‹ˆ๊นŒ. ๋„ˆ๋ฌด๋‚˜ ์‰ฝ๊ฒŒ ์ƒ๊ฐํ•˜๊ณ  ๊ธฐํš๋Œ€๋กœ ์ž‘์—…ํ–ˆ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋Š”๋ฐ, ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด๋‹ˆ ์ด๋ชจ์ง€๋ฅผ ๊ธ€์ž ํ•˜๋‚˜ ์ด์ƒ์œผ๋กœ ์„ธ๋”๊ตฐ์š”.๐Ÿ˜ณ ๋Œ€๊ฐœ ์ œ๊ฐ€ ํ…Œ์ŠคํŠธํ–ˆ๋˜ ์ด๋ชจ์ง€๋“ค์€ ํ•œ ์ด๋ชจ์ง€๋‹น 2๊ธ€์ž ์ •๋„๋กœ ์ธ์‹ํ–ˆ์Šต๋‹ˆ๋‹ค.

iOS์—์„œ๋Š” ์ด๋ชจ์ง€ ํ•˜๋‚˜๋ฅผ ํ•œ ๊ธ€์ž๋กœ ์ธ์‹ํ•ด์„œ ์ด๊ฒƒ๊ณผ ๊ด€๋ จํ•ด ๋ฌธ์ œ๊ฐ€ ์—†์ด ์‰ฝ๊ฒŒ ๊ฐ€๋Š”๋ฐ, ์•ˆ๋“œ๋กœ์ด๋“œ๋Š” ๊ทธ๋ ‡์ง€ ๋ชปํ•ด์„œ ์†์œผ๋กœ ํˆดํˆด๊ฑฐ๋ฆฌ๊ณ ๐Ÿคจ๐Ÿคจ์™œ ๊ทธ๋Ÿฐ์ง€, ๊ทธ๋ฆฌ๊ณ  ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์ด ์žˆ๋Š”์ง€ ๊ตฌ๊ธ€์—๊ฒŒ ๋ฌผ์–ด๋ดค์Šต๋‹ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ์ด๋ ‡๊ฒŒ ์ข‹์€ ๊ธ€์„ ์ฐพ์•„๋ƒˆ์Šต๋‹ˆ๋‹ค!!

 

 

๊ธ€์ž์ˆ˜๋ฅผ ์„ธ๋Š” 7๊ฐ€์ง€ ๋ฐฉ๋ฒ• - LINE ENGINEERING

์•ˆ๋…•ํ•˜์„ธ์š”. ๋ผ์ธํ”Œ๋Ÿฌ์Šค ๊ฐœ๋ฐœ์‹ค์—์„œ ์ผํ•˜๊ณ  ์žˆ๋Š” ๋ฐ•์ƒ์ง„์ž…๋‹ˆ๋‹ค. ์ด ๋ธ”๋กœ๊ทธ์—์„œ๋Š” ๊ธ€์ž ์ˆ˜๋ฅผ ์„ธ๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์–˜๊ธฐํ•ด๋ณด๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค. ๋ผ์ธ ์„œ๋น„์Šค์—์„œ๋Š” ํ”„๋กœํ•„์ด๋ฆ„, ๊ทธ๋ฃน์ด๋ฆ„, ์ƒํƒœ๋ฉ”์‹œ์ง€ ๋“ฑ

engineering.linecorp.com

๋ผ์ธ ๊ฐœ๋ฐœ์ž๋ถ„๋“ค ๋ฉ‹์ ธ์š”!๐Ÿ‘ ์ € ๊ธ€์„ ์ž‘์„ฑํ•˜์‹  ๋ฐ•์ƒ์ง„๋‹˜ ๋„ˆ๋ฌด๋„ˆ๋ฌด ๋ฉ‹์ ธ์š”!๐Ÿ˜ ๋„ˆ๋ฌด ๋ฉ‹์ ธ์š”!!!!๐Ÿ‘๐Ÿ‘๐Ÿ‘๐Ÿ‘

 

๊ธ€์ž์˜ ์ •์˜, ๊ธ€์ž ์ˆ˜์˜ ์ •์˜์— ๋”ฐ๋ผ ๊ธ€์ž ์นด์šดํŠธํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ๋‹ค๋ฅด๋‹ค๋Š” ๊ฒŒ ์‹ ๊ธฐํ–ˆ์–ด์š”.

 

์ œ๊ฐ€ ์„ธ๊ณ ์ž ํ•˜๋Š” ์ด๋ชจ์ง€๋Š” Grapheme์œผ๋กœ, ๋ฌธ์ž ์ฒด๊ณ„์—์„œ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธ€์ž์˜ ์ตœ์†Œ ๋‹จ์œ„๋“ค๋กœ ์„ธ์•ผ ํ•˜๋Š”๊ตฌ๋‚˜๋ฅผ ์ € ๊ธ€๋ฅผ ๋ณด๊ณ  ์•Œ์•˜์–ด์š”. ๋˜ํ•œ BreakIterator์˜ ์กด์žฌ์— ๋Œ€ํ•ด์„œ๋„ ์•Œ๊ฒŒ ๋˜์—ˆ์–ด์š”.

 

 

BreakIterator (Java 2 Platform SE 5.0)

java.text ํด๋ž˜์Šค BreakIterator java.lang.Object java.text.BreakIterator ๋ชจ๋“  ๊ตฌํ˜„๋œ ์ธํ„ฐํŽ˜์ด์Šค: Cloneable public abstract class BreakIteratorextends Object implements Cloneable BreakIterator ํด๋ž˜์Šค๋Š” ํ…์ŠคํŠธ๋‚ด์˜ ๊ฒฝ๊ณ„์˜ ์œ„์น˜๋ฅผ ์ฐพ

cris.joongbu.ac.kr

 

์ €๋Š” ์ž…๋ ฅ์ฐฝ์— ๊ธ€์ž์ˆ˜ ์ œํ•œ์ด ๊ฑธ๋ฆฌ๋„๋ก ํ•„ํ„ฐ๋ฅผ ๋„ฃ๊ณ ์ž ํ–ˆ๊ณ , ์œ„์—์„œ ์ฐพ์€ ๊ฒƒ๋“ค์„ ํ†ตํ•ด์„œ ์š”๋ ‡๊ฒŒ ์ฝ”๋“œ๋ฅผ ์งฐ์–ด์š”.

๊ธฐ์กด InputFilter.LengthFilter๋ฅผ ์ฐธ๊ณ ํ–ˆ๋‹ต๋‹ˆ๋‹ค. 

class TextEmojiLengthFilter(private val maxLength: Int) : InputFilter.LengthFilter(maxLength) {

    override fun filter(
        source: CharSequence?,
        start: Int,
        end: Int,
        dest: Spanned?,
        dstart: Int,
        dend: Int
    ): CharSequence? {

        Timber.d("source : $source, start: $start, end: $end")
        Timber.d("dest : $dest, dstart: $dstart, dend: $dend")

        val destLength = getLength(dest)


        var keep = maxLength - (destLength - (dend - dstart))
        Timber.d("์ž…๋ ฅ ๊ฐ€๋Šฅ ๋ฒ”์œ„ keep : $keep")
        val lengthAndBoundary = getLengthAndLastBoundary(source, keep)
        val sourceLength = lengthAndBoundary.first
        Timber.d("์ž…๋ ฅ๋œ ๋ฌธ์ž ๊ธธ์ด : $sourceLength")
        if(keep <= 0) {
            return ""
        } else if(keep >= sourceLength) {
            return null
        } else {
            val boundary = lengthAndBoundary.second
            Timber.d("๋งˆ์ง€๋ง‰ ๋ฒ”์œ„ : $boundary")
            val result = source!!.subSequence(start, boundary)
            Timber.d("๊ฒฐ๊ณผ : $result")
            return result
        }
    }

    private fun getLength(text: CharSequence?): Int {
        var count = 0
        text?.let { s ->
            val it = BreakIterator.getCharacterInstance()
            it.setText(s.toString())
            while (it.next() != BreakIterator.DONE) {
                count++
            }
            Timber.d("getLength $s > ์ด๋ชจํ‹ฐ์ฝ˜ ํฌํ•จ ๊ธ€์ž ์ˆ˜ : $count")
        }
        return count
    }

    private fun getLengthAndLastBoundary(text: CharSequence?, keepLength : Int): Pair<Int, Boundary> {
        var count = 0
        var boundary = 0
        text?.let { s ->
            val it = BreakIterator.getCharacterInstance()
            it.setText(s.toString())
            while (it.next() != BreakIterator.DONE) {
                count++
                if (count == keepLength) {
                    boundary = it.current()
                }
            }
            Timber.d("getLengthAndLastBoundary $s > ์ด๋ชจํ‹ฐ์ฝ˜ ํฌํ•จ ๊ธ€์ž ์ˆ˜ : $count")
        }
        return Pair(count, boundary)
    }
}
typealias Boundary = Int

 

์ œ๊ฐ€ ์ง  ์ฝ”๋“œ์—์„œ ์ˆ˜์ • ์‚ฌํ•ญ์ด๋‚˜ ๊ฐœ์„ ์‚ฌํ•ญ์ด ์žˆ์œผ๋ฉด ๊ผญ๊ผญ ๋Œ“๊ธ€๋กœ ์•Œ๋ ค์ฃผ์„ธ์š”! ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค!๐Ÿ˜Š

InputFilter ๋ง๊ณ ๋„ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ์ž…๋ ฅ์ฐฝ(EditText)์˜ ์ด๋ชจ์ง€ ํฌํ•จ ๊ธ€์ž์ˆ˜๋ฅผ ์ œํ•œํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค๋ฉด ๊ณต์œ ๋ถ€ํƒ๋“œ๋ ค์š”๐Ÿ˜Š๐Ÿ˜Š

 

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!. ๋.