본문 바로가기
🖥 Programming/📱 Android (Kotlin)

[Android][kotlin] 싸인패드(SignPad), 시그니처패드 (Signaturepad) 구현

by MinChan-Youn 2022. 4. 15.

안녕하세요~ 챠니입니다! :)

오늘은 사인패드, 시그니처패드 (Signaturepad)에 대해서 알아보겠습니다.

해당 기능은 네이티브의 기본으로 내장되어 있는 기능은 아니고, 똑똑한 사람이 Github에 자신이 작업한 내용을 올린것을 활용하여 구현하는 방법입니다 :)

 

    Signaturepad

     

    먼저 오늘 기능을 먼저 구현하여 공유를 하고 있는 사람의 GitHub의 주소는 다음과 같습니다.

    보다더 자세히 알아보고 싶은 분은 아래의 링크의 Readme를 읽어 보시길 바랍니다.

     

    https://github.com/zahid-ali-shah/SignatureView

     

    GitHub - zahid-ali-shah/SignatureView: SignatureView is an open source Android library which allow developers to produce pen and

    SignatureView is an open source Android library which allow developers to produce pen and paper like effect for creating signatures on Android - GitHub - zahid-ali-shah/SignatureView: SignatureView...

    github.com

     

     

    구현방법

     

    1. build.gradle(:app) 에 dependencies를 추가합니다.

    implementation 'com.kyanogen.signatureview:signature-view:1.2'

     

    사인패드 기능을 지원하는 아래 2개가 더 있지만, 제가 사용하려고하니 잘 안되더라구요... 그래서 사용하지 못했습니다 :)

    'com.github.gcacace:signature-pad:1.3.1'

    'com.github.nsmarinro.SignatureView:librarySignature:0.1.2'

     

    2.  layout파일에 사인패드를 추가합니다.

        <com.kyanogen.signatureview.SignatureView
            xmlns:sign="http://schemas.android.com/apk/res-auto"
            android:id="@+id/signature_pad"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:background="#FFFFFF"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHeight_percent="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:penColor="@android:color/black"
            sign:enableSignature="true" />

     

    여기까지 따라 오셨다면 가장 기초적인 사인패드 기능을 구현 할 수 있습니다.

    하지만 사인패드를 구현하는것에서 끝나는 것이 아니라, 사인패드에 그린 후 지워주는 Clear기능과 Save저장기능 까지 구현을 해보겠습니다.

     

    ---------------------------------------------------------------------------------------------------------------------

    여기서부터는 전체 코드를 보여드리겠습니다.

    사인패드를 구현하는 부분은 쉽지만 지우기 및 갤러리 저장 기능이 kt파일의 대부분이라고 생각하시면 되겠습니다.

     

    [MainActivity.kt]

    /** MainActivity.kt*/
    class MainActivity : AppCompatActivity() {
        lateinit var binding: ActivityMainBinding
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(binding.root)
    
            //화면모드 설정 (세로-SCREEN_ORIENTATION_PORTRAIT, 가로-SCREEN_ORIENTATION_LANDSCAPE)
            requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
    
            initSignaturePad()
        }
    
        private fun initSignaturePad(){
            /** 초기화 */
            binding.bClear.setOnClickListener {
                binding.signaturePad.clearCanvas()
            }
    
            /** 저장 */
            binding.bSave.setOnClickListener {
                if(!binding.signaturePad.isBitmapEmpty){
                    /** 권한 체크 */
                    if(!checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE) || !checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                        return@setOnClickListener
                    }
    
                    /** 그림 저장 */
                    if(!imageExternalSave(binding.signaturePad.signatureBitmap, this.getString(R.string.app_name))){
                        Toast.makeText(this, "사인패드 저장에 실패하였습니다", Toast.LENGTH_SHORT).show()
                    }else{
                        Toast.makeText(this, "사인패드를 갤러리에 저장하였습니다.", Toast.LENGTH_SHORT).show()
                    }
                }else {
                    Toast.makeText(this, "사인패드가 비어있습니다.", Toast.LENGTH_SHORT).show()
                }
            }
        }
    
        /** 이미지 저장 */
        private fun imageExternalSave(bitmap: Bitmap, path: String): Boolean {
            val state = Environment.getExternalStorageState()
            if (Environment.MEDIA_MOUNTED == state) {
    
                val rootPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString()
                val dirName = "/" + path
                val fileName = System.currentTimeMillis().toString() + ".png"
                val savePath = File(rootPath + dirName)
                savePath.mkdirs()
    
                val file = File(savePath, fileName)
                if (file.exists()) file.delete()
    
                try {
                    val out = FileOutputStream(file)
                    bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
                    out.flush()
                    out.close()
    
                    //갤러리 갱신
                    galleyAddPic(file)
                    return true
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
            return false
        }
    
        /** 갤러리 갱신 */
        private fun galleyAddPic(file: File){
            Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent ->
                mediaScanIntent.data = Uri.fromFile(file)
                sendBroadcast(mediaScanIntent)
            }
        }
    
        /** 권한 체크 */
        private fun checkPermission(permission: String): Boolean {
            val permissionChecker = ContextCompat.checkSelfPermission(applicationContext, permission)
    
            //권한이 없으면 권한 요청
            if (permissionChecker == PackageManager.PERMISSION_GRANTED){
                return true
            }
    
            ActivityCompat.requestPermissions(this, arrayOf(permission), 100)
            return false
        }
    }

     

    [activity_main.xml]

    <?xml version="1.0" encoding="utf-8"?>
    <!-- activity_main.xml-->
    <androidx.constraintlayout.widget.ConstraintLayout
        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"
        android:background="#000000"
        tools:context=".MainActivity">
    
        <com.kyanogen.signatureview.SignatureView
            xmlns:sign="http://schemas.android.com/apk/res-auto"
            android:id="@+id/signature_pad"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:background="#FFFFFF"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHeight_percent="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:penColor="@android:color/black"
            sign:enableSignature="true" />
    
        <Button
            android:id="@+id/b_clear"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:background="#333333"
            android:enabled="true"
            android:gravity="center"
            android:padding="20dp"
            android:text="Clear"
            android:textColor="#FFFFFF"
            app:layout_constraintEnd_toStartOf="@+id/b_save"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/signature_pad" />
    
        <Button
            android:id="@+id/b_save"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:background="#333333"
            android:enabled="true"
            android:gravity="center"
            android:padding="20dp"
            android:text="Save"
            android:textColor="#FFFFFF"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/b_clear"
            app:layout_constraintTop_toBottomOf="@+id/signature_pad" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>

     

    완성된 앱 모습

    CLEAR, SAVE버튼 eventListener를 통해서 사인패드 클리어 및 갤러리 저장기능을 구현하였습니다.

     

    여기가지 사인패드에 대해서 알아보았습니다.

    소스코드

    [소스코드]

    싸인패드, 시그니처패드(SignaturePad):

    https://github.com/younminchan/kotlin-study/tree/main/Signaturepad_kotlin

     

    GitHub - younminchan/kotlin-study: kotlin 다양한 예제코드

    kotlin 다양한 예제코드. Contribute to younminchan/kotlin-study development by creating an account on GitHub.

    github.com

     

     

     

    질문 또는 궁굼한 부분은 댓글을 남겨주세요! 친절하게 답변드리겠습니다!

    응원의 댓글은 저에게 큰 힘이 된답니다! :)

    즐거운 하루되세요!

     

    깃허브 보러 놀러오세요 👇 (맞팔환영)

    https://github.com/younminchan

     

    younminchan - Overview

    안드로이드 2년차 개발자 •⚽️/🎤/🥁/🖥/🏃‍♂️/🚴‍♂️/🤟 TechBlog⬇️ minchanyoun.tistory.com - younminchan

    github.com