GDSC HUFS 4기/Kotlin Team #3

[3팀] Android-12-Kotlin : 키즈 드로잉 앱 (3)

홓옇 2022. 11. 18. 00:42

 

이 글은 유데미 강의 Android 12 및 Kotlin 개발 완전 정복을 참고하여 작성하였습니다.

작성자 : 정호영

개발환경은 Windows, Android Studio입니다. 

 

126~131 강의 내용 정리입니다.

 

1. 권한 데모

1. AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
	package="eu.tutorials.permissiondemo"> 
<uses-permission android:name="android.permission.CAMERA"/> 
	<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> 
	<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> 
	<application 
    	android:allowBackup="true" 
		android:icon="@mipmap/ic_launcher" 
		android:label="@string/app_name" 
		android:roundIcon="@mipmap/ic_launcher_round" 
		android:supportsRtl="true" 
		android:theme="@style/Theme.PermissionDemo"> 
		<activity android:name=".MainActivity" 
			android:exported="true"> 
			<intent-filter> 
			<action android:name="android.intent.action.MAIN" /> 
			<category android:name="android.intent.category.LAUNCHER" />
			</intent-filter> 
		</activity> 
	</application>
</manifest>

 

package="eu.tutorials.permissiondemo"> 
<uses-permission 
android:name="android.permission.CAMERA"/> 카메라 권한 요청
<uses-permission 
android:name="android.permission.ACCESS_COARSE_LOCATION"/> 
<uses-permission 
android:name="android.permission.ACCESS_FINE_LOCATION"/>  위치권한 요청

 

 

2. MainActivity.kt

package eu.tutorials.permissiondemo

import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat

class MainActivity : AppCompatActivity() {

	private val cameraAndLocationResultLauncher:ActivityResultLauncher<Array<String>> =
        registerForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions()
        ){permissions->
               Log.d("MainActivity","Permissions $permissions")
                permissions.entries.forEach {
                    val permissionName = it.key
                    val isGranted = it.value
                    if (isGranted) {
                        if ( permissionName == Manifest.permission.ACCESS_FINE_LOCATION) {
                            Toast.makeText(
                                this,
                                "Permission granted for location",
                                Toast.LENGTH_LONG
                            )
                                .show()
                        }else{                          
                            Toast.makeText(
                                this,
                                "Permission granted for Camera",
                                Toast.LENGTH_LONG
                            )
                                .show()
                        }
                    } else {
                        if ( permissionName == Manifest.permission.ACCESS_FINE_LOCATION) {
                            Toast.makeText(
                                this,
                                "Permission denied for location",
                                Toast.LENGTH_LONG
                            )
                                .show()
                        }else{
                            Toast.makeText(
                                this,
                                "Permission denied for Camera",
                                Toast.LENGTH_LONG
                            )
                                .show()
                        }
                    }
                }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val btnCameraPermission: Button = findViewById(R.id.btnCameraPermission)

        btnCameraPermission.setOnClickListener {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && shouldShowRequestPermissionRationale(
                    Manifest.permission.CAMERA
                )
            ) {
               showRationaleDialog(" Permission Demo requires camera access",
                   "Camera cannot be used because Camera access is denied")
            } else {
               cameraAndLocationResultLauncher.launch(
                    arrayOf(Manifest.permission.CAMERA,Manifest.permission.ACCESS_FINE_LOCATION)
                )

            }
        }

    }

    private fun showRationaleDialog(
        title: String,
        message: String,
    ) {
        val builder: AlertDialog.Builder = AlertDialog.Builder(this)
        builder.setTitle(title)
            .setMessage(message)
            .setPositiveButton("Cancel") { dialog, _ ->
                dialog.dismiss()
            }
        builder.create().show()
    }

}

 

2-1

private val cameraAndLocationResultLauncher:ActivityResultLauncher<Array<String>> =
        registerForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions()
        ){permissions->
               Log.d("MainActivity","Permissions $permissions")
                permissions.entries.forEach {
                    val permissionName = it.key
                    val isGranted = it.value

forEach문을 통해 어떤 권한을 승인했는지 반복해서 체크하여 isGranted 에 담는다.

 

2-2

 if (isGranted) {
     if ( permissionName == Manifest.permission.ACCESS_FINE_LOCATION) {
         Toast.makeText(
             this,
             "Permission granted for location",
              Toast.LENGTH_LONG
          ).show()
      }
      else{                          
         Toast.makeText(
             this,
             "Permission granted for Camera",
             Toast.LENGTH_LONG
          ).show()
      }
}

만약 사용자로부터 권한요청이 수락된다면 각각의 요청에 대해서 Toast로 알림을 띄어준다.

 

 else {
       if ( permissionName == Manifest.permission.ACCESS_FINE_LOCATION) {
       Toast.makeText(
            this,
            "Permission denied for location",
            Toast.LENGTH_LONG).show()
        }
        else{
		Toast.makeText(
			this,
			"Permission denied for Camera",
			Toast.LENGTH_LONG).show()
         }
     }
}

만약 사용자로부터 권한요청이 허용되지 않는다면 거부됬다는 알림을 띄운다.

 

private fun showRationaleDialog(title: String, message: String) 
{
	val builder: AlertDialog.Builder = AlertDialog.Builder(this)
	builder.setTitle(title)
		.setMessage(message)
		.setPositiveButton("Cancel") { dialog, _ ->
                dialog.dismiss()
         }
        builder.create().show()
}

사용자가 권한요청을 거부하였을 때 권한이 필요한 이유를 표시하는 대화 상자를 만들어서 표시해준다.

 

 

 imageButton.setOnClickListener { view ->
     Snackbar.make(view, "You have clicked image button.", Snackbar.LENGTH_LONG).show()

이미지버튼에 스낵바 다이얼로그를 추가한다.

Snackbar.make(뷰, 메시지, 알림시간)

스낵바는 경고, 주의 메시지에 적합하며 액티비티에서만 보인다는 특징이 있다.

 

 

 

2. AlertDialog, CustomDialog

[AlertDialog]

val btnAlertDialog: Button = findViewById(R.id.btn_alert_dialog)
    btnAlertDialog.setOnClickListener { view ->
         alertDialogFunction()
     }

 

alert 메시지는 아이콘이 표시되며 사용자로부터 Yes, No 를 입력받을 수 있다는 특징이 있다.

 private fun alertDialogFunction() {
        val builder = AlertDialog.Builder(this)
        builder.setTitle("Alert")
        builder.setMessage("This is Alert Dialog. Which is used to show alerts in our app.")
        builder.setIcon(android.R.drawable.ic_dialog_alert)
        builder.setPositiveButton("Yes") { dialogInterface, which ->
            Toast.makeText(applicationContext, "clicked yes", Toast.LENGTH_LONG).show()
            dialogInterface.dismiss()
        }
        builder.setNeutralButton("Cancel") { dialogInterface, which ->
            Toast.makeText(
                applicationContext,
                "clicked cancel\n operation cancel",
                Toast.LENGTH_LONG
            ).show()
            dialogInterface.dismiss() 
        }
        builder.setNegativeButton("No") { dialogInterface, which ->
            Toast.makeText(applicationContext, "clicked No", Toast.LENGTH_LONG).show()
            dialogInterface.dismiss() 
        }  
        val alertDialog: AlertDialog = builder.create()
        alertDialog.setCancelable(false)
        alertDialog.show()
    }

Alert.Builder를 사용하여 builder 속성(Title, Message, Icon, PositiveButton, NegativeButton 등)을 추가해준다.

Yes 버튼을 눌렀을 때 "clicked yes" 문구를 띄우고,

No 버튼을 눌렀을 때 "clicked no" 문구를 띄우며

종료버튼(cancle)을 눌렀을때 "clicked cancle opertaion cancel" 문구를 출력한다.

각 버튼에 대해 dissmiss()를 사용하여 AlertDialog를 종료한다.

 

[Custom Dialog]

private fun customDialogFunction() {
	val customDialog = Dialog(this)
	customDialog.setContentView(R.layout.dialog_custom)
	customDialog.findViewById<TextView>(R.id.tv_submit).setOnClickListener {
		Toast.makeText(applicationContext, "clicked submit", Toast.LENGTH_LONG).show()
		customDialog.dismiss()
	}
	customDialog.findViewById<TextView>(R.id.tv_cancel).setOnClickListener {
		Toast.makeText(applicationContext, "clicked cancel", Toast.LENGTH_LONG).show()
		customDialog.dismiss()
	}
	customDialog.show()
}

layout 밑에 TextView 에서 id값을 추가하여 대응되는 dialog를 만들 수 있다.

tv_submit 을 누르게 되면 "clicked submit" 을 출력하고

tv_cancel 을 누르게 되면 "clicked cancle" 을 출력한다.

마찬가지로 dismiss() 를 통해 해당 다이얼로그를 종료한다.

 

<TextView
	android:id="@+id/tv_submit"
	android:layout_width="0dp"
	android:layout_height="match_parent"
	android:layout_weight="1"
	android:foreground="?attr/selectableItemBackground"
	android:gravity="center"
	android:padding="10dp"
	android:text="SUBMIT"
	android:textColor="@android:color/holo_red_dark"
/>
<TextView
	android:id="@+id/tv_cancel"
	android:layout_width="0dp"
	android:layout_height="match_parent"
	android:layout_weight="1"
	android:foreground="?attr/selectableItemBackground"
	android:gravity="center"
	android:padding="10dp"
	android:text="CANCEL"
	android:textColor="@android:color/black"
/>

 

val btnCustomProgress:Button = findViewById(R.id. btn_custom_progress_dialog)
	btnCustomProgress.setOnClickListener {
	customProgressDialogFunction()
}

btn_custom_progress_dialog에 대한 xml 파일

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="horizontal"
    android:padding="10dp">

    <ProgressBar
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginEnd="10dp" 
        />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Please Wait..."
        android:textColor="@android:color/black"
        android:textSize="16sp" 
        />

</LinearLayout>

customProgressDialogFunction() 함수

private fun customProgressDialogFunction() {
	val customProgressDialog = Dialog(this)
	customProgressDialog.setContentView(R.layout.dialog_custom_progress)
	customProgressDialog.show()
}

 

3. 드로잉앱에 권한요청 추가

MainActivity.kt

val requestPermission: ActivityResultLauncher<Array<String>> =
		registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) 
        { permissions ->
		permissions.entries.forEach {
			val perMissionName = it.key
			val isGranted = it.value
            if (isGranted ) {
               Toast.makeText(
                   this@MainActivity,
                   "Permission granted now you can read the storage files.",
                   Toast.LENGTH_LONG).show()
                } 
            else {
                if (perMissionName == Manifest.permission.READ_EXTERNAL_STORAGE)
                    Toast.makeText(
                        this@MainActivity,
                        "Oops you just denied the permission.",
                        Toast.LENGTH_LONG
                    ).show()
       }
    }
}

126번 강의에서 했었던 내용과 동일하다. 사용자로부터 권한요청이 승인되면 Toast를 통해 문구를 출력한다.

 

 private fun requestStoragePermission(){
        if (
            ActivityCompat.shouldShowRequestPermissionRationale(
                this, Manifest.permission.READ_EXTERNAL_STORAGE)
        )
        {
			showRationaleDialog("Kids Drawing App","Kids Drawing App " +
                    "needs to Access Your External Storage")
        }
        else {
			 requestPermission.launch(
                arrayOf(
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
                )
            )
        }

    }

사용자에게 이 기능을 왜 액세스 해야되는지에 대해 showRationalDialog() 함수를 호출하여 알려준다.

만약 권한요청을 확인하지 못했다면 else 문에서 다시한번 권한을 요청한다.

arrayOf 안에 필요한 권한들을 계속 추가해줄 수 있다.

 

4. 갤러리에서 이미지 받아오기

val pickIntent = Intent(Intent.ACTION_PICK,MediaStore.Images.Media.EXTERNAL_CONTENT_URI)

isGranted 조건문안에 pickIntent 변수를 만들어 미디어 스토어로부터 값을 받아오고 이미지를 선택하기 위해 런처를 만든다.

val openGalleryLauncher:ActivityResultLauncher<Intent> = registerForActivityResult(ActivityResultContracts.StartActivityForResult())
{result->
    if (result.resultCode == RESULT_OK && result.data != null){
         val imageBackground:ImageView = findViewById(R.id.iv_background)
            imageBackground.setImageURI(result.data?.data)
        }
    }

result 에 데이터가 비어있는지를 확인하고 result에 있는 데이터를 imageBackground  이미지뷰 변수에 추가한다.

 

 <ImageView
	android:id="@+id/iv_background"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:contentDescription="image"
	android:scaleType="centerCrop" />

 

val pickIntent = Intent(Intent.ACTION_PICK,MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
openGalleryLauncher.launch(pickIntent)

런처를 통해 Intent를 개시해 엑세스할 수 있게 한다.