일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- MVP Architecture
- 안드로이드 fcm
- Android
- Android Navigation
- 안드로이드 카카오 로그인
- Android WebView
- android recyclerview
- 프로그래머스 알고리즘
- 카카오 알고리즘
- scope function
- 안드로이드 갤러리 접근
- Android 12 대응
- 안드로이드
- DataBinding
- Android ViewPager2
- 습관만들기
- Android 12
- 알고리즘 자바
- Java
- 영어독립365
- 영어공부
- Kotlin
- WebView
- Android Interceptor
- Android ProgressBar
- Kotlin FCM
- Android DataBinding
- Android Jetpack
- OkHttp Interceptor
- 66챌린지
- Today
- Total
Developer Geek
Android 복수 사진 첨부 From Activity In Kotlin 본문
개요
시나리오
Activity에서 갤러리 접근 버튼을 클릭 하면 접근 권한 확인 후 디바이스 갤러리에 접근한다. 사진 복수 개 선택 시, 해당 사진들을 RecyclerView에 보여준다. (단, 4개를 초과해서 선택 시 Toast 메시지를 통해 최대 4개임을 명시한다.)
실행 화면
프로젝트 구조
ViewBinding, Coil 사용 - In build.gradle(:app)
ViewBinding을 사용하기 위해 viewBinding { enabled = true }
를 build.gradle(:app)에 추가했다.
ImageView에 이미지 첨부를 위해 Coil을 사용했고 의존성으로 implementation "io.coil-kt:coil:2.0.0-rc03"
를 build.gradle(:app)에 추가했다.
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdk 31
defaultConfig {
applicationId "com.example.showgalleryapplication"
minSdk 26
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
viewBinding{
enabled = true
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// Coil
implementation "io.coil-kt:coil:2.0.0-rc03"
}
Code
AndroidManifest.xml
갤러리 접근 권한을 얻기 위해 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
을 추가한다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.showgalleryapplication">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<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.ShowGalleryApplication">
<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>
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"
android:padding="20dp"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="0dp"
android:layout_height="500dp"
android:background="#ffececec"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="2"
tools:listitem="@layout/list_item" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_show_gallery"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#6492EF"
android:text="사진첨부"
android:textColor="@color/white"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
list_item.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="180dp"
android:layout_height="180dp"
android:padding="10dp">
<ImageView
android:id="@+id/image_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="fitXY"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@mipmap/ic_launcher" />
</androidx.constraintlayout.widget.ConstraintLayout>
MyListAdapter.kt - 리사이클러뷰 어댑터(ListAdapter 사용)
import android.net.Uri
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import coil.load
import com.example.showgalleryapplication.databinding.ListItemBinding
class MyListAdapter:
ListAdapter<Uri, MyListAdapter.ViewHolder>(diffUtil) {
inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(uri: Uri) {
binding.imageView.load(uri)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(ListItemBinding.inflate(LayoutInflater.from(parent.context),
parent,
false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(currentList[position])
}
companion object {
val diffUtil = object : DiffUtil.ItemCallback<Uri>() {
override fun areItemsTheSame(oldItem: Uri, newItem: Uri): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Uri, newItem: Uri): Boolean {
return oldItem == newItem
}
}
}
}
MainActivity.kt
showGallery(this@MainActivity, Intent.EXTRA_ALLOW_MULTIPLE)
private fun showGallery(activity: Activity, extra: String) {
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
intent.putExtra(extra, true)
activity.startActivityForResult(intent, PICK_IMAGE_FROM_GALLERY)
}
복수 개의 이미지 선택을 위해, 갤러리 Intent에 "Intent.EXTRA_ALLOW_MULTIPLE" : true
데이터를 담아준다.
import android.app.Activity
import android.app.AlertDialog
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.core.content.ContextCompat
import com.example.showgalleryapplication.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val PICK_IMAGE_FROM_GALLERY = 1000
private val PICK_IMAGE_FROM_GALLERY_PERMISSION = 1010
private val mAdapter = MyListAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 이미지를 보여줄 리사이클러 뷰 Init
binding.recyclerView.adapter = mAdapter
// 사진첨부 버튼 클릭 이벤트 구현
binding.btnShowGallery.setOnClickListener {
when {
// 갤러리 접근 권한이 있는 겨우
ContextCompat.checkSelfPermission(this,
android.Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED -> showGallery(this@MainActivity,
Intent.EXTRA_ALLOW_MULTIPLE)
// 갤러리 접근 권한이 없는 경우 && 교육용 팝업을 보여줘야 하는 경우
shouldShowRequestPermissionRationale(android.Manifest.permission.READ_EXTERNAL_STORAGE)
-> showPermissionContextPopup()
// 권한 요청 하기
else -> requestPermissions(arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE),
PICK_IMAGE_FROM_GALLERY_PERMISSION)
}
}
}
private fun showGallery(activity: Activity, extra: String) {
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
intent.putExtra(extra, true)
activity.startActivityForResult(intent, PICK_IMAGE_FROM_GALLERY)
}
private fun showPermissionContextPopup() {
AlertDialog.Builder(this)
.setTitle("권한이 필요합니다.")
.setMessage("갤러리 접근 권한이 필요합니다.")
.setPositiveButton("동의하기") { _, _ ->
requestPermissions(arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE),
PICK_IMAGE_FROM_GALLERY_PERMISSION)
}
.setNegativeButton("취소하기") { _, _ -> }
.create()
.show()
}
// 사진 선택(갤러리에서 나온) 이후 실행되는 함수
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == PICK_IMAGE_FROM_GALLERY && resultCode == Activity.RESULT_OK) {
val list = ArrayList<Uri>()
data?.let { it ->
if (it.clipData != null) { // 사진을 여러개 선택한 경우
val count = it.clipData!!.itemCount
if (count > 4) {
Toast.makeText(this@MainActivity, "사진은 4장까지 선택 가능합니다.", Toast.LENGTH_SHORT)
.show()
return
}
for (i in 0 until count) {
val imageUri = it.clipData!!.getItemAt(i).uri
list.add(imageUri)
}
} else { // 1장 선택한 경우
val imageUri = it.data!!
list.add(imageUri)
}
}
// adapter 에 Image 저장
mAdapter.submitList(list)
}
}
// 권한 요청 승인 이후 실행되는 함수
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray,
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
PICK_IMAGE_FROM_GALLERY_PERMISSION -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)
showGallery(this@MainActivity, Intent.EXTRA_ALLOW_MULTIPLE)
else
Toast.makeText(this, "권한을 거부하셨습니다.", Toast.LENGTH_SHORT).show()
}
}
}
}
개념 학습 - "접근 권한"
권한 요청 워크플로우
ContextCompat.checkSelfPermission(): 앱이 이미 권한이 부여되었는지 확인
사용자가 이미 앱에 특정 권한을 부여했는지 확인하려면 checkSelfPermission(Context context, String permission)
메서드에 권한을 전달한다. 이 메서드는 앱에 권한이 있는지에 따라서 PERMISSION_GRANTED 또는 PERMISSION_DENIED를 반환한다.
ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)
shouldShowRequestPermissionRationale(): 권한 요청 전에 권한이 필요한 이유를 사용자에게 설명해야 하는 지에 대한 여부 반환
requestPermissions()를 호출하기 전에 앱에서 권한을 요청하는 이유를 사용자에게 설명하는 것이 좋다. 연구에 따르면 사용자가 앱에서 권한이 필요한 이유를 알고 있을 때 권한 요청을 훨씬 더 편안하게 느낀다고 한다.
ContextCompap.checkSelfPermission() 메서드가 PERMISSION_DENIED를 반환하면 shouldShowRequestPermissionRationale()
을 호출해라. 이 메서드가 true를 반환하면 교육용 UI를 사용자에게 표시한다. 이 UI에서 사용자가 사용 설정하려는 기능에 특정 권한이 필요한 이유를 설명해야 한다.
requestPermissions(@NonNull String[] permissions, int requestCode): 권한 요청
사용자에게 교육용 UI 가 표시되었거나, shouldShowRequestPermissionRationale()
의 반환 값에서 이번에는 교육용 UI를 표시하지 않아도 된다고(false) 나타내면 권한을 요청한다.
permissions에 해당하는 권한들을 요청한다. 단, permissions에 있는 권한들은 manifest에 꼭 추가 되어있어야 한다.
권한 요청이 완료되면 onRequestPermissionResult()
함수를 호출한다.
'안드로이드 > Service' 카테고리의 다른 글
Android FCM and Message Types (0) | 2022.06.10 |
---|---|
Android 배송추적 WebView in Kotlin (0) | 2022.05.01 |
Android 사진 첨부 From Activity In Kotlin (0) | 2022.04.27 |
안드로이드 FCM 예제 - Part2 (앱 매니페스트 수정, FCM Token 등록, Notification 송수신) (0) | 2021.10.07 |
안드로이드 FCM 예제 - Part1 (Firebase 프로젝트 만들기, 앱 등록) (0) | 2021.10.04 |