어플을 만들 때 카메라를 사용하는 경우가 많다. 당장 채팅어플만(카카오, 페매 등) 보더라도 갤러리에서 사진을 가져오거나, 직접 카메라로 사진을 찍어서 올리곤 한다.
그래서 카메라를 사용하는 법을 예제를 통해서 알아보고자 한다. 단순 Intent를 이용해서 카메라를 키고 사진을 받을 수 있지만 그렇게 되면 사진이 섬네일 형식으로 오기 때문에 파일에 사진을 저장하고, 그 파일을 불러오는 식으로 원본형태의 사진을 이미지뷰에 넣는 방식이다.
권한 설정
안드로이드 마시멜로우 버전(6.0) 이상부터는 앱에서 해당 권한이 필요할 때마다 사용자로부터 권한을 허가받도록 변경되었다. 그래서 카메라를 사용할 때도 권한을 확인해줘야 한다.
원래는 굉장히 복잡한 코드를 이용해서 권한 설정을 해줘야 하는데, 라이브러리를 이용해서 간단하게 권한 설정을 해줄 수 있다. 우리는 '테드 박'님이 만드신 TedPermission을 사용할 것이다. TedPermission이 무엇인지는 여기서 확인하면 된다.
https://gun0912.tistory.com/61
AndroidManifest.xml
<manifest ...>
<uses-feature android:name="android.hardware.camera"
android:required="true"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
...
</manifest>
먼저 manifest에 권한을 설정해 준다. Android Developer에서는 WRITE_EXTERNAL_STORAGE 권한만 허가하면 암시적으로 읽기를 허용하기 때문에 READ_EXTERNAL_STORAGE는 요청할 필요가 없다고 하지만 혹시 모르니 추가해줬다.
<application>
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="org.techtown.capturepicture.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
</provider>
...
</application>
카메라로 사진을 찍고 원본 형태를 유지하기 위해서 필요한 FileProvider를 구성하기 위해 manifest에 추가해준다.
주의해야 할 점은 "android:authorities"에는 본인의 패키지명이 들어가야하며, "fileprovider"는 그대로 써주면 된다.
그 다음 "android:resource"에 적혀있는 경로를 구성해준다.
res/xml/file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path = "Android/data/org.techtown.capturepicture/files/Pictures"/>
</paths>
res 폴더에 'xml'이라는 이름의 폴더를 하나 생성한 후 그 안에 'file_paths'라는 이름의 xml 파일을 생성한다.
그 다음 전체 코드를 다음과 같이 설정해준다. 여기서 "org.techtown.capturepicture"부분은 본인의 패키지명을 입력해주면 된다.
TedPermission Gradle 설정
implementation 'gun0912.ted:tedpermission:2.2.3'
build.gradle에 TedPermission을 사용하기 위해 다음과 같이 추가해준다.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/img_picture"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="100dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_picture"
tools:srcCompat="@tools:sample/avatars"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
간단하게 버튼을 누르면 그 아래 ImageView에 내가 찍은 사진이 들어가는 화면이다.
MainActivity.kt
fun settingPermission(){
var permis = object : PermissionListener{
// 어떠한 형식을 상속받는 익명 클래스의 객체를 생성하기 위해 다음과 같이 작성
override fun onPermissionGranted() {
Toast.makeText(this@MainActivity, "권한 허가", Toast.LENGTH_SHORT)
.show()
}
override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
Toast.makeText(this@MainActivity, "권한 거부", Toast.LENGTH_SHORT)
.show()
ActivityCompat.finishAffinity(this@MainActivity) // 권한 거부시 앱 종료
}
}
TedPermission.with(this)
.setPermissionListener(permis)
.setRationaleMessage("카메라 사진 권한 필요")
.setDeniedMessage("카메라 권한 요청 거부")
.setPermissions(
android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
// android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.CAMERA)
.check()
}
권한 설정을 하는 함수다. TedPermission을 이용하면 다음처럼 간단하게 구성할 수 있다.
자바와는 다르게 코틀린은 PermissionListener를 생성할 때 object를 이용해야 한다.
@Throws(IOException::class)
private fun createImageFile() : File{
val timeStamp : String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val storageDir : File? = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(
"JPEG_${timeStamp}_",
".jpg",
storageDir
).apply{
currentPhotoPath = absolutePath
}
}
사진을 찍고 나서 이미지를 파일로 저장해주는 함수다. 파일의 이름은 서로 충돌하지 않는 파일 이름으로 만들어야 하기 때문에 현재 날짜 시간을 받아 고유한 파일 이름으로 설정해준다.
fun startCapture(){
Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
takePictureIntent.resolveActivity(packageManager)?.also {
val photoFile: File? = try{
createImageFile()
}catch(ex:IOException){
null
}
photoFile?.also{
val photoURI : Uri = FileProvider.getUriForFile(
this,
"org.techtown.capturepicture.fileprovider",
it
)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
}
}
}
}
다음 함수는 Intent를 이용해서 카메라를 호출하는 함수다. 이 함수는 사진 촬영 버튼을 누를 때 실행 된다.
startActivityForResult() 함수에 takePictureIntent라는 Intent를 넘겨주는 걸로 카메라를 실행하고 사진을 찍을 수 있다.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK){
val file = File(currentPhotoPath)
if (Build.VERSION.SDK_INT < 28) {
val bitmap = MediaStore.Images.Media
.getBitmap(contentResolver, Uri.fromFile(file))
img_picture.setImageBitmap(bitmap)
}
else{
val decode = ImageDecoder.createSource(this.contentResolver,
Uri.fromFile(file))
val bitmap = ImageDecoder.decodeBitmap(decode)
img_picture.setImageBitmap(bitmap)
}
}
}
마지막으로 onAcitivityResult()함수를 오버라이딩 해서 이미지뷰에 내가 찍은 사진을 표시해준다.
여기서 getBitmap()이 자꾸 Deprecated돼서 구글링을 해본 결과, stackoverflow에서 sdk 28 버전 기준으로 나누어서
이미지 뷰에 뿌려주는 방법이 해결방안으로 제시 되었다. 그래도 deprecated되는건 마찬가지지만 요즘 핸드폰 대부분은 Android 9.0 (파이)버전 이상이기 때문에 크게 문제는 안될 것 같다. (나만 그런가..?)
실행하면 다음과 같이 실행된다.
사진이 썸네일 형식이 아닌 깔끔한 원본 이미지 상태로 잘 들어간다. 사진을 찍으면 사진의 회전방향이 제멋대로 들어가는데 다음에는 화면을 회전시켜서 넣는 법을 알아봐야겠다.
마지막으로 MainAcitivity 전체코드와 github주소를 올리면서 마무리 하겠다.
전체 코드
package org.techtown.capturepicture
...
class MainActivity : AppCompatActivity() {
val REQUEST_IMAGE_CAPTURE = 1
lateinit var currentPhotoPath : String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
settingPermission() // 권한체크 시작
btn_picture.setOnClickListener {
startCapture()
}
}
fun settingPermission(){
var permis = object : PermissionListener{
// 어떠한 형식을 상속받는 익명 클래스의 객체를 생성하기 위해 다음과 같이 작성
override fun onPermissionGranted() {
Toast.makeText(this@MainActivity, "권한 허가", Toast.LENGTH_SHORT)
.show()
}
override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
Toast.makeText(this@MainActivity, "권한 거부", Toast.LENGTH_SHORT)
.show()
ActivityCompat.finishAffinity(this@MainActivity) // 권한 거부시 앱 종료
}
}
TedPermission.with(this)
.setPermissionListener(permis)
.setRationaleMessage("카메라 사진 권한 필요")
.setDeniedMessage("카메라 권한 요청 거부")
.setPermissions(
android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
// android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.CAMERA)
.check()
}
fun startCapture(){
Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
takePictureIntent.resolveActivity(packageManager)?.also {
val photoFile: File? = try{
createImageFile()
}catch(ex:IOException){
null
}
photoFile?.also{
val photoURI : Uri = FileProvider.getUriForFile(
this,
"org.techtown.capturepicture.fileprovider",
it
)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
}
}
}
}
@Throws(IOException::class)
private fun createImageFile() : File{
val timeStamp : String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val storageDir : File? = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(
"JPEG_${timeStamp}_",
".jpg",
storageDir
).apply{
currentPhotoPath = absolutePath
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK){
val file = File(currentPhotoPath)
if (Build.VERSION.SDK_INT < 28) {
val bitmap = MediaStore.Images.Media
.getBitmap(contentResolver, Uri.fromFile(file))
img_picture.setImageBitmap(bitmap)
}
else{
val decode = ImageDecoder.createSource(this.contentResolver,
Uri.fromFile(file))
val bitmap = ImageDecoder.decodeBitmap(decode)
img_picture.setImageBitmap(bitmap)
}
}
}
}
( UseCamera 브랜치 참조 )
https://github.com/kangmin1012/AndroidExcercise.git
'안드로이드' 카테고리의 다른 글
[Kotlin] ARCore를 이용한 AR 구현 (1) - 세팅 (3) | 2020.04.01 |
---|---|
[Android/Kotlin] 재생/멈춤 아이콘 애니메이션 (VectorDrawable) (0) | 2020.03.05 |
[Android/Kotiln] DataBinding(데이터바인딩)3 - Observable(실시간 데이터) (1) | 2020.02.06 |
[Android/Kotlin] DataBinding(데이터바인딩) 2 - BindingAdapter(바인딩 어댑터) (0) | 2020.01.31 |
[Android/Kotlin] DataBinding(데이터바인딩) 1 - 기초 (2) | 2020.01.24 |