Take Photo with Intent
화면 작업 👉 FileProvider 등록 👉 저장 데이터 정의 와 파일 저장 경로 설정
👉 사진 찍기 기능 추가 👉
ImageButton and ImageView 만들기
원하는 프래그먼트나 액티비티의 xml 에 ImageButton 과 ImageView를 만든다.
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/crime_photo"
android:layout_width="80dp"
android:layout_height="80dp"
android:scaleType="centerInside"
android:cropToPadding="true"
android:background="@android:color/darker_gray"
/>
<ImageButton
android:id="@+id/crime_camera"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@android:drawable/ic_menu_camera"
/>
</LinearLayout>
위의 예시 처럼 원하는 형태로 이미지뷰와 이미지 버튼을 만든다.
파일 저장소
사진 파일은 용량이 크다.
SQLite DB 에 넣기도 어렵다. 그러므로, 폰의 저장소에 저장하는 것이 바람직하다.
(물론, DB 용량이 크다면 가능하다.)
개인 저장소에 저장하려면,
Context.getFileStreamPath(String) or Context.getFilesDir() 함수들을 이용한다.
Context 클래스에 있는 기본적인 파일과 디렉터리 함수는 다음과 같다.
1. getFilesDir() : File > 앱 전용 파일들의 디렉토리 핸들을 반환한다.2. openFileInput(name:String) : FileInputStream> 데이터를 읽기 위해 파일 디렉토리의 기존 파일을 연다.3. openFileOutput(name: String, mode: Int) : FileOutputStream> 데이터를 쓰기 위해 파일 디렉터리의 파일을 연다.( 생성도 가능) 4. getDir(name:String, mode: Int) :File > 파일 디렉터리 내부의 서블 디렉터리를 알아낸다. 5. fileLIst(...) : Array<String> > 파일 디렉터리의 파일 이름들을 알아낸다. 예를 들면, openFileInput(String) 과 함께 사용한다. 6. getCacheDir() : File > 캐시 파일저장에 사용할 수 있는 디렉토리의 핸들을 반환한다. 단, 이 디렉터리는 가능한 작은 용량을 사용하도록 주의한다.
다른 앱에서 이 사진파일에 접근하여 사용하도록 하려면 위의 함수들로는 부족하다. 외부의 카메라 앱에서 개인 저장소 영역의 파일로 사진을 저장해야 하기 때문이다. (원래, 공용의 외부 저장소를 사용하여 파일을 전송할 수 있었지만, 이제는 보안상의 이유로 금지 되었다.)
따라서, 다른 방법을 써야 한다.
ContentProvider 가 그 해결책이 된다.
Content Provider ? File Provider !
컨텐츠 제공자로 파일을 콘텐츠 URI로 다른 앱에 노출하면 다른 앱에서는 해당 URI로부터 파일을 다운로드하거나 쓸 수 있다.
그리고 제어할 수 있으며, 읽거나, 쓰는 것을 거부할 수 있다.
다른 앱에서 파일을 받는 것이 전부라면, 굳이 ContentProvider 전체를 구현할 필요 없다.
File Provider 사용하기 이러한 상황에 맞게 구글에서 FileProvider을 제공하고 있다.
Manifest에 FileProvider 선언하기
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="패키지 이름">
<application
...>
<activity android:name=".MainActivity">
...
</activity>
<provider
android:authorities="패키지 이름.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true"
>
</provider>
</application>
</manifest>
- android : authorities
이 FileProvider 의 파일이 저장되는 위치를 뜻한다.
시스템 전체에서 고유한 문자열이어야 한다. 따라서 패키지 이름을 문자열에 포함하는 것이 좋다. - android : exported
false : 자신 및 내가 권한을 부여한 사람 외에는 FileProvider를 사용할 수 없다.
true : 사용하게 하겠다는 것인데, 그럴일은 없을 듯 하다. - android : grantUriPermissions
인텐트로 위의 android : authorities 의 URI 를 전송할 때, 전송된 URI 에 다른 앱이 쓸 수 있는 권한을 부여할 수 있다.
안드로이드 OS 에 FileProvider가 어디에 있는지 알려주었다.
어떤 경로의 파일을 노출할 것인지도 별도의 XML 리소스 파일을 통해 FileProvider에게 알려준다.
(ex) res/xml/files.xml 의 경로로 생성할 수 있다.
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="이름"
path="."/>
</paths>
이제는 다시 Manifest파일에서 이 경로를 provider의 정의와 연결해준다.
<provider
android:authorities="패키지명.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true"
>
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/files"
/>
</provider>
사진 위치 지정하기
데이터 클래스에 파일 이름 속성을 추가한다.
@Entity
data class ExampleData(
@PrimaryKey var id: UUID = UUID.randomUUID(),
var title : String = "",
var date : Date = Date(),
var isSolved: Boolean = false,
var person : String = "") {
// 사진 파일 이름 속성 추가
// 테이블의 속성으로는 추가하지 않고 따로 객체화하여 저장할 수 있게 로직을 만들어준다.
// 파일 이름을 얻는 연산 속성(computed property) 을 데이터 클래스에 추가하는 것이다.
// (kotlin에서는 연산 속성은 다른 속성의 값으로 자신의 값을 산출하므로, 값을 저장하는 필드(Backing field 라 한다.)를 갖지 않는다.)
val photoFileName
get() = "IMG_$id.jpg"
}
DB Repository 클래스를 통해 사진 파일의 위치를 찾고, 가져올 수 있게 함수를 정의한다.
class ExampleRepository private constructor(context:Context) {
...
private val filesDir = context.applicationContext.filesDir
// 이 함수는 Crime데이터 클래스의 photofileName을 참조하여 사진 파일을 제공한다.
fun getPhotoFile(crime: Crime) : File = File(filesDir, crime.photoFileName)
...
}
Viewmodel을 통해 사진 파일 정보를 제공한다.
class ExampleDetailViewModel : ViewModel() {
private val exampleRepository = ExampleRepository.get()
...
// 사진파일 정보를 Crimefragment에 제공하는 함수 추가해준다.
fun getPhotoFile(crime: Crime) : File {
return exampleRepository.getPhotoFile(crime)
}
}
사진 찍기
인텐트를 통해 사진 찍기 요청하기
MediaStore
미디어스토어에는 이미지, 비디오, 음악 등의 미디어를 처리하는 안드로이드에서 사용되는 public 인터페이스가 정의되어 있다.
또한, 카메라 앱을 시작시키는 이미지 캡쳐 인텐트 상수도 포함하고 있다. (ACTION_IMAGE_CAPTURE)
이 인텐트 상수로 사진을 찍으면 낮은 해상도의 썸네일 사진정도이다.
그리고 찍은 사진은 onActivityResult()에서 반환하는 Intent객체에 포함되어 온다.
찍은 사진을 가져와서 보여주기
비트맵의 크기 조정과 보여주기
사진 파일을 읽어서 로드한 후 사용자에게 보여주려면,적합한 크기의 Bitmap 객체로 로드해줘야 한다.
BitmapFactory 사용하기
val bitmap =BitmapFactory.decodeFile(photoFile.getPaht())
적합한 크기?Bitmap 은 화소 데이터를 저장하는 간단한 객체이다. (pixel = 화소) 원래 압축되었더라도, Bitmap자체는 압축되지 않는다. (1600만 화소 24bit 카메라 이미지는 5MB 크기의 JPG로 압축될 수 있다. Bitmap객체로는 48MB크기로 커진다.)
따라서 컨트롤을 해야한다.
직접 비트맵의 크기를 줄여야 한다. 1. 파일의 크기를 먼저 확인한다. 2. 얼마나 줄일지 파악해본다. 3. 읽어온 파일의 크기를 줄인 bitmap 객체를 생성하면 된다.
kotlin file 형태로 PictureUtils 함수를 만들어서 사용한다.
package ...
import android.graphics.Bitmap
import android.graphics.BitmapFactory
fun getScaledBitmap(path: String, destWidth : Int, destHeight: Int) : Bitmap {
// 이미지 파일의 크기를 읽는다.
var options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(path, options)
val srcWidth = options.outWidth.toFloat()
val srcHeight = options.outHeight.toFloat()
// 크기를 얼마나 줄일지 파악한다.
var inSampleSize = 1
if (srcHeight > destHeight || srcWidth >destWidth) {
val heightScale = srcHeight / destHeight
val widthScale = srcWidth / destWidth
val sampleScale = if (heightScale > widthScale) {
heightScale
}else{
widthScale
}
inSampleSize = Math.round(sampleScale)
}
options = BitmapFactory.Options()
options.inSampleSize = inSampleSize
// 최종 Bitmap 을 생성해준다.
return BitmapFactory.decodeFile(path, options)
}
Bitmap Factory 클래스를 사용하여 만든다. 옵션을 통해inSampleSize가 중요하다. 각 화소에 대해 각 '샘플'이 얼마나 큰지를 결정한다. 예를들어, inSampleSize 가 1이면, 원래 파일의 각 수평화소당 하나의 최종수평화소를 갖는다. 그리고 2이면, 원래 파일의 두 개의 수평화소마다 하나의 수평화소를 갖는다. 즉, 원래 이미지의 1/4 에 해당하는 화소 갯수를 갖는 이미지가 된다. Fragment 에서 사진의 크기를 미리 알수 없는 상황이 발생한다.이러한 경우,
- 레이아웃 뷰 객체로 생성될 때까지 기다리거나
- 사진 뷰이 크기가 어느 정도 될지 추정하는 방법이 있다. (효율성이 떨어진다. 그러나 간단하게 구현가능)
'Programming > Android' 카테고리의 다른 글
Localization (0) | 2021.08.04 |
---|---|
DialogFrag to Frag with Data (0) | 2021.07.29 |
Frag to Frag with Data (0) | 2021.07.28 |