본문 바로가기
2023년 이전/Android

custom 숫자 키 입력 - 보안 숫자 keyboard

by JeongUPark 2019. 9. 6.
반응형

은행 App을 사용할 때보면 숫자입력 키 패드가 나올때마다 숫자의 위치가 변경되어 나타나는 것을 알 수 있습니다.

오늘은 그 기능을 구현해 보려합니다.

 

작성 Code는 kotlin 입니다.

 

우선 xml 부터 작성해 보겠습니다.

 

구성은 아래와 같습니다.

맨 위에 edit 와 총 12개의 숫자 버튼이 있고 비어있는 부분에는 숫자들이 랜덤으로 들어 갑니다. 그리고 중간에는 Textview가 있는데 이곳에 결과를 노출할 것입니다.

 

우선 버튼에 대한 drawable 관련 code를 보겠습니다. 

숫자버튼

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape>
            <gradient
                android:startColor="#1B2631"
                android:endColor="#34495E "
                android:angle="270" />
            <stroke
                android:width="1dp"
                android:color="#00000000" />
            <corners
                android:radius="1dp" />
            <padding
                android:left="3dp"
                android:top="3dp"
                android:right="3dp"
                android:bottom="3dp" />
        </shape>

    </item>
    <item android:state_pressed="false">
        <shape>
            <gradient
                android:startColor="#34495E"
                android:endColor="#EBEDEF"
                    android:angle="270" />
            <stroke
                android:width="1dp"
                android:color="#00000000" />
            <corners
                android:radius="1dp" />
            <padding
                android:left="3dp"
                android:top="3dp"
                android:right="3dp"
                android:bottom="3dp" />
        </shape>
    </item>

</selector>

확인 지움 버튼 

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape>
            <gradient
                android:startColor="#424949"
                android:endColor="#F2F4F4"
                android:angle="270" />
            <stroke
                android:width="1dp"
                android:color="#00000000" />
            <corners
                android:radius="1dp" />
            <padding
                android:left="3dp"
                android:top="3dp"
                android:right="3dp"
                android:bottom="3dp" />
        </shape>

    </item>
    <item android:state_pressed="false">
        <shape>
            <gradient
                android:startColor="#626567"
                android:endColor="#F8F9F9"
                    android:angle="270" />
            <stroke
                android:width="1dp"
                android:color="#00000000" />
            <corners
                android:radius="1dp" />
            <padding
                android:left="3dp"
                android:top="3dp"
                android:right="3dp"
                android:bottom="3dp" />
        </shape>
    </item>
</selector>

사실 확인 지움 버튼은 숫자 버튼에서 그라데이션 색만 변경되었습니다.

 

그리고 layout xml은 다음과 같습니다.

 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/edit_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:cursorVisible="true" />
    <TextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/edit_text"
        android:layout_above="@+id/viewFlipper"/>
    <ViewFlipper
        android:id="@+id/viewFlipper"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true">

        <LinearLayout
            android:id="@+id/firstViewFlipper"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#c0c0c0"
            android:orientation="vertical">

            <TableLayout
                android:id="@+id/tableLayout"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:stretchColumns="0,1,2">

                <TableRow>

                    <Button
                        android:layout_height="100dp"
                        android:background="@drawable/key_button_style"
                        android:textSize="30sp"/>

                    <Button
                        android:layout_height="100dp"
                        android:background="@drawable/key_button_style"
                        android:textSize="30sp"/>

                    <Button
                        android:layout_height="100dp"
                        android:background="@drawable/key_button_style"
                        android:textSize="30sp"/>
                </TableRow>

                <TableRow>

                    <Button
                        android:layout_height="100dp"
                        android:background="@drawable/key_button_style"
                        android:textSize="30sp"/>

                    <Button
                        android:layout_height="100dp"
                        android:background="@drawable/key_button_style"
                        android:textSize="30sp"/>
                    <Button
                        android:layout_height="100dp"
                        android:background="@drawable/key_button_style"
                        android:textSize="30sp"/>
                </TableRow>

                <TableRow>

                    <Button
                        android:layout_height="100dp"
                        android:background="@drawable/key_button_style"
                        android:textSize="30sp"/>

                    <Button
                        android:layout_height="100dp"
                        android:background="@drawable/key_button_style"
                        android:textSize="30sp"/>

                    <Button
                        android:layout_height="100dp"
                        android:background="@drawable/key_button_style"
                        android:textSize="30sp"/>
                </TableRow>

                <TableRow>

                    <Button
                        android:id="@+id/key_ok"
                        android:layout_height="match_parent"
                        android:background="@drawable/key_spec_button_style"
                        android:textSize="20sp"
                        android:text="확 인" />

                    <Button
                        android:layout_height="100dp"
                        android:background="@drawable/key_button_style"
                        android:textSize="30sp"/>

                    <Button
                        android:id="@+id/key_cancel"
                        android:layout_height="match_parent"
                        android:background="@drawable/key_spec_button_style"
                        android:textSize="20sp"
                        android:text="지 움" />
                </TableRow>
            </TableLayout>
        </LinearLayout>
    </ViewFlipper>


</RelativeLayout>

EditText Textview  ViewFlipper를 사용하였고, ViewFlipper 안에 Table을 너어서 숫자 패트를 만들었습니다.

 

ViewFlipper를 사용한 이유는 일반 ime 처럼 나타났다 사라졌다 할 수 있도록 하기 위해서 입니다.

 

다음은 android code 입니다.

package com.anapass.random_num_keyboard

import android.app.Activity
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.os.Message
import android.util.DisplayMetrics
import android.view.KeyEvent
import android.view.View
import android.view.animation.*
import android.view.inputmethod.InputMethodManager
import android.widget.*
import java.lang.StringBuilder
import kotlin.random.Random


class MainActivity : AppCompatActivity() {

    private lateinit var editText: EditText
    private lateinit var flipper: ViewFlipper
    private lateinit var tableLayout: TableLayout
    private lateinit var textView : TextView
    private val mActivity: Activity? = this
    private var currentText : StringBuilder = StringBuilder()
    private lateinit var imm: InputMethodManager
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        val btnWidth: Int = getBtnWidth()
        flipper =  findViewById<ViewFlipper>(R.id.viewFlipper).apply {
            visibility = View.VISIBLE
            var inAni = AnimationUtils.loadAnimation(mActivity, R.anim.in_animation)
            inAni.interpolator = AccelerateInterpolator()
            setInAnimation(inAni)
            var outAni = AnimationUtils.loadAnimation(mActivity, R.anim.out_animation)
            outAni.interpolator = DecelerateInterpolator()
            setOutAnimation(outAni)
        }

        tableLayout =  findViewById<TableLayout>(R.id.tableLayout)
        textView = findViewById<TextView>(R.id.text_view)
        val emptyView = TextView(this)
        flipper.addView(emptyView, 0)

        editText = findViewById<EditText>(R.id.edit_text).apply {
            setOnClickListener { v: View ->
                imm.hideSoftInputFromWindow(editText.windowToken, 0)
                if (flipper.currentView.id != R.id.firstViewFlipper) {
                    reOrderKeyboard(btnWidth)
                    flipper.visibility = View.VISIBLE
                    flipper.displayedChild = 1
                }
            }
        }
        imm.hideSoftInputFromWindow(editText.windowToken, 0)
        editText.requestFocus()
       findViewById<Button>(R.id.key_ok).apply {
            setOnClickListener{ v : View ->
                if (flipper.currentView.id == R.id.firstViewFlipper) {
                    flipper.displayedChild = 0
                    var showtxt = StringBuilder(textView.text)
                    showtxt.append("\n"+currentText)
                    textView.text = showtxt.toString()
                    currentText.clear()
                    editText.text.clear()
                }
            }
        }
        findViewById<Button>(R.id.key_cancel).apply {
            setOnClickListener{ v : View ->
                val curIndex = editText.selectionStart
                var passwordStr = editText.text.toString()
                var passWordLength = passwordStr.length
                if (curIndex == 0 || passWordLength == 0) {
                    return@setOnClickListener
                }
                passwordStr.apply {
                    passwordStr = substring(0,curIndex-1)+substring(curIndex,passWordLength)
                }
                currentText.apply{
                    currentText = StringBuilder(toString().substring(0,curIndex-1)+toString().substring(curIndex,passWordLength))
                }
                passWordLength = passwordStr.length
                editText.setText("")
                for( i in 1 .. passWordLength){
                    editText.append("*")
                }
                editText.setSelection(curIndex-1);
            }
        }
        reOrderKeyboard(btnWidth)
    }

    private fun reOrderKeyboard(btnWidth: Int) {

        val keyNumberArr = ArrayList<String>()
        for (i in 0..9) {
            keyNumberArr.add(i.toString())
        }

        var tr: TableRow? = null
        var btn: Button? = null
        var randIndx: Int = 0;
        var random = Random

        for (i in 0..(tableLayout.childCount-1)) {
            tr = tableLayout.getChildAt(i) as TableRow
            for (i in 0..(tr.childCount-1)) {
                btn = tr.getChildAt(i) as Button
                if (btn.id == -1) {
                    randIndx = random.nextInt(keyNumberArr.size)
                    btn.text = keyNumberArr[randIndx]
                    btn.width = btnWidth/3
                    val keyTxt = btn.text
                    keyNumberArr.removeIf { x -> x == keyNumberArr[randIndx] }
                    btn.setOnClickListener { v: View ->
                        val curIndex = editText.selectionStart
                        var passwordStr = editText.text.toString()
                        val passWordLength = passwordStr.length
                        passwordStr.apply {
                            substring(0, curIndex) + keyTxt + substring(curIndex,passWordLength)
                        }
                        currentText.append(keyTxt)

                        editText.setText("")
                        for (i in 0 until curIndex) {
                            editText.append("*")
                        }
                        editText.append(keyTxt)
                        for (i in curIndex + 1 until passWordLength + 1) {
                            editText.append("*")
                        }
                        editText.setSelection(curIndex + 1)
                        mHandler.sendEmptyMessageDelayed(0,1000)
                    }
                }
            }
        }
    }
    private var mHandler = Handler(){
        msg: Message? ->
        val curIndex = editText.selectionStart
        editText.setText("")
        for (i in 0 until curIndex) {
            editText.append("*")
        }
        editText.setSelection(curIndex)
        false
    }

    private fun getBtnWidth(): Int {
        val displaymetrics = DisplayMetrics()
        windowManager.defaultDisplay.getMetrics(displaymetrics)
        return displaymetrics.widthPixels
    }

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        if(keyCode == KeyEvent.KEYCODE_BACK){
            if(flipper.currentView.id == R.id.firstViewFlipper){
                flipper.setDisplayedChild(0)
            }else{
                return super.onKeyDown(keyCode, event)
            }
        }
        return true
    }
}

 

보시면 code는 그리 길지 않습니다.

 

숫자 view를 누르면 edittext에 값이 추가 되고 edittext에는 *로 보이지만 따로 입력한 숫자를 저장하여 최종적으로 확인을 눌렸을 경우 그 값을 사용합니다. 그리고 keydown시 back 키가 들어올경우 custom keyboard가 올라와 있으면 custom keyboard를 내리고, 확인 버튼을 눌렸을 때는 Textview에 입력한 숫자를 노출하면 custom keyboard를 내립니다. 지 움 버튼을 누르면 입력한 값이 지워 집니다.

 

 이때 중간 중간에 InputMethodManager를 사용하여 softkeyboard를 hide하고 있는데, 이러지 않으면 custom keyboard위로 ime가 나타나기 떄문입니다. (지금 만든건 keyboard 같지만 사실 view를 사용여 만든 UI일 뿐입니다. ime가 아닙니다.)

 

그리고 animation을 사용하여 custom keyboard가 나타나고 사라지는 animaiton을 추가하였습니다. 

 

animation은 res 밑에 anim라는 Diratory를 만들고 그 안에

 

나타나는 animation은 

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="0%p"
        android:toXDelta="0%p"
        android:fromYDelta="100%p"
        android:toYDelta="0%p"
        android:duration="500"
        />
</set>

사라지는 animation은 

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="0%p"
        android:toXDelta="0%p"
        android:fromYDelta="0%p"
        android:toYDelta="100%p"
        android:duration="500"
        />
</set>

사용하시면 됩니다.

 

그렇게 완성된 app 화면은 다음과 같습니다.

 

반응형

'2023년 이전 > Android' 카테고리의 다른 글

Android - WorkManager(1)  (1) 2019.10.16
Android -LiveData  (0) 2019.10.15
Android - ViewModel  (0) 2019.10.15
Android - Kotlin을 사용하여 Listener 등록  (0) 2019.08.29
Android 잠금화면 위에 Activity 보여주기  (0) 2019.08.28