안드로이드 스튜디오에서 avd에 사진을 불러와서 보여주는 프로그램을 만들려고 한다.
우선 xml 코드를 구현해보자
1) mainActivity
<?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">
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
2) fragment_photo.xml을 만든다
참고로 File -> new -> fragment를 생성하면 코틀린 코드와,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=".PhotoFragment" >
<ImageView
android:id="@+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
</androidx.constraintlayout.widget.ConstraintLayout>
3. avd에 갤러리 이미지를 추가!
Device explore(위치가 잘 안 보여서 shiftx2 한 뒤 검색해서 찾았다!)
이런식으로 sdCard 픽쳐스에 원하는 사진을 넣으면 된다! 나는 아무 사진이나 가져와서 넣었다!
+ 이렇게 넣는다고 바로 avd 갤러리에 추가되면... 좋겠지만, avd를 다시 껐다가 켜야 보인다. 이를 꼭 참고해야한다.
다음 코드를 보기 전, 프로바이더에 대해 알아보자
프로바이터란, 앱의 데이터 접근을 다른 앱에 허용하는 컴포넌트를 말한다. 프로바이터를 이용하여 사진 정보를 가지고 오는 순서를 정한다. 안드로이드에는 1) 액티비티 2) 콘텐츠 프로바이터 3)브로드캐스트 리시터 4)서비스가 있따.
오늘은 액티비티를 이용한다!
스마트폰 사진 경로를 찾기 위해선
1) 첫 번째 인자 : 어떤 데이터를 가져올 것인지 url 형태로 지정함.
2) 두 번째 인자 : 어떤 항목의 데이터를 가져올 것인지 String 배열로 지정
3) 세 번째 인자 : 데이터를 가져올 조건을 지정. 전체 데이터는 null로 설정
4) 네 번째 인자 : 세 번째 인자에 추가 조건을 지정, 사용하지 않는다면 null로 설정
5) 다섯 번째 인자 : 정렬 방법을 지정, 사진이 찍힌 날짜의 내림차순을 정렬한다.
예시 코드는 이렇게 된다. 나는 오늘 이 부분을 코딩을 MainActivity에 했다!
val cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
null,
null,
null,
MediaStore.Images.ImagesColumns.DATE_TAKEN + "DESC")
위의 코드를 통해 사진 정보를 가져온 뒤, 이후 커서 객체에 연결한다. 이는 최종적인 MainActivity를 참고하자.
이렇게만 한다고 사진정보를 가져올 수 없다 매니페스트에 외부 저장소 읽기 권한을 앱에 추가하는 코드를 넣어야 한다.
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
이후 권한을 추가해야 한다. 실행 중 위험 권한이 필요한 작업을 수행할 때마다 권한이 있는지 확인해야 한다.
if문에 해당 내용이 있으니 참고하자. 참고로 이 역할은 앱이 외부 저장소 읽기 권한이 있는지 확인하는 코드다.
오늘 나는 avd에 갤러리에 있는 사진을 읽어올 것이니 이를 유념하여 살펴보자. 권한에 대한 코드는 밑에 있다.
// 권한 확인
if (ContextCompat.checkSelfPermission(this, readImagePermission) != PackageManager.PERMISSION_GRANTED) {
// 권한 미허용 상태
// 권한이 허용되지 않음
if (ActivityCompat.shouldShowRequestPermissionRationale(
this,
readImagePermission)
) {
사용자가 권한을 요청하면, 시스템은 응답을 전달하여 사진 정보를 가져오거나 권한이 거부되었음을 알린다.
사진정보를 가져오는 함수는 getAllPhotos 앞에 커서, url 가져오는 부분이 된다.
그러면 최종적인 MainActivity 코드는 밑에서 처럼 나온다.
import android.Manifest
import android.app.AlertDialog
import android.content.ContentResolver
import android.content.pm.PackageManager
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager
class MainActivity : AppCompatActivity() {
private val REQUEST_READ_EXTERNAL_STORAGE = 1000
private lateinit var viewPager: ViewPager
private lateinit var adapter: MyPagerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val readImagePermission = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) Manifest.permission.READ_MEDIA_IMAGES
else Manifest.permission.READ_EXTERNAL_STORAGE
viewPager = findViewById(R.id.viewPager)
adapter = MyPagerAdapter(supportFragmentManager)
viewPager.adapter = adapter
val readPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
// 권한 확인
if (ContextCompat.checkSelfPermission(this, readImagePermission) != PackageManager.PERMISSION_GRANTED) {
// 권한 미허용 상태
// 권한이 허용되지 않음
if (ActivityCompat.shouldShowRequestPermissionRationale(
this,
readImagePermission)
) {
// 이전에 거부한 적이 있으면 설명(경고)
val dlg = AlertDialog.Builder(this)
dlg.setTitle("권한이 필요한 이유")
dlg.setMessage("사진 정보를 얻기 위해서는 외부 저장소 권한이 필수로 필요함")
dlg.setPositiveButton("확인") { _, _ ->
ActivityCompat.requestPermissions(
this@MainActivity,
arrayOf(readImagePermission),REQUEST_READ_EXTERNAL_STORAGE
)
}
dlg.setNegativeButton("취소", null)
dlg.show()
} else {
// 허용 권한 요청
ActivityCompat.requestPermissions(
this,
arrayOf(readImagePermission),REQUEST_READ_EXTERNAL_STORAGE
)
}
} else {
//권한이 이미 제대로 허용됨
getAllPhotos()
}
}
private fun getAllPhotos() {
// ContentResolver를 통해 사진 정보 가져오기
val contentResolver: ContentResolver = contentResolver
val collection: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DATA
)
val sortOrder = "${MediaStore.Images.ImageColumns.DATE_TAKEN} DESC"
val cursor: Cursor? = contentResolver.query(
collection,
projection,
null,
null,
sortOrder
)
val fragments = ArrayList<Fragment>()
if (cursor != null) {
val dataColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
while (cursor.moveToNext()) {
val imagePath = cursor.getString(dataColumn)
Log.d("MainActivity", "Image path: $imagePath")
fragments.add(PhotoFragment.newInstance(imagePath))
}
cursor.close()
}
// 어댑터에 데이터 업데이트
adapter.updateFragments(fragments)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_READ_EXTERNAL_STORAGE) {
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
// 권한 허용됨
getAllPhotos()
} else {
// 권한 거부됨
AlertDialog.Builder(this)
.setTitle("권한 거부됨")
.setMessage("외부 저장소 접근 권한이 거부되어 사진을 불러올 수 없습니다.")
.setPositiveButton("확인", null)
.show()
}
}
}
}
마지막으로 프래그먼트 개념에 알아보자
프래그먼트는 표시할 뷰를 레이아웃 파일로부터 읽어오는 부분은 onCreateView()이고, 액티비티의 onCreate()와 동일하다. onCreate()는 프래그먼트를 생성할 때 인자가 함께 넘어온다면 onCreate()메서드에서 받아서 변수에 담는다.
onCreateView() 메서드에서 완성된 레이아웃 뷰는 생명주기에는 포함되지 않는 onViewCreated()메서드로 전달되며 이쪽에서 뷰가 완성된 이후에 이벤트 처리 등을 수행한다.
요약하면, 프래그먼트 추가됨 -> onCreate() -> onCreateView() -> onViewCreate() -> 프래그먼트 활성화 이후 프래그먼트 사용이 끝나면 종료된다. 자세한건 다음 복습 때 더 자세히 알아보자!
PhotoFragment.kt 코드
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.fragment.app.Fragment
import com.bumptech.glide.Glide
class PhotoFragment : Fragment() {
private var imageUrl: String? = null
companion object {
private const val ARG_IMAGE_URL = "image_url"
fun newInstance(imageUrl: String): PhotoFragment {
val fragment = PhotoFragment()
val args = Bundle()
args.putString(ARG_IMAGE_URL, imageUrl)
fragment.arguments = args
return fragment
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
imageUrl = arguments?.getString(ARG_IMAGE_URL)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_photo, container, false)
val imageView = view.findViewById<ImageView>(R.id.imageView)
imageUrl?.let {
Glide.with(this)
.load(it) // `imageUrl`에서 이미지를 로드
.into(imageView)
}
return view
}
}
UI 요소들을 데이터와 바인딩하는 역할을 하는 어덥터가 필요해기에, MypageAdapter.kt도 추가했다.
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
class MyPagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
private val items = ArrayList<Fragment>()
override fun getItem(position: Int): Fragment {
return items[position]
}
override fun getCount(): Int {
return items.size
}
// 프래그먼트 목록을 업데이트하는 함수
fun updateFragments(newItems: List<Fragment>) {
items.clear()
items.addAll(newItems)
notifyDataSetChanged() // 데이터 변경을 알림
}
}
이러면 안드로이드에 갤러리에 추가한 사진이 잘 뜨게 된다!
'안드로이드 프로그래밍📱 > Projects📲' 카테고리의 다른 글
[Android Studio]배달의 민족 하단바 구현하기 with splash page, fade out (0) | 2025.03.19 |
---|---|
[AndroidStudio] 네비게이션을 활용하는 방법 with kotlin (0) | 2025.03.12 |
[AndroidStudio] 축구 구단 역사 검색하기(easy.version) (0) | 2025.02.18 |