Slide Gallery에 들어간 클래스/위젯
class, component on the Slide Gallery
- Fragment
- ViewPager
- FragmentStatePagerAdapter
- contentResolver (provider)
- Glide
- Anko
- ArrayList
- Alert, ImageView
Activity & Fragment & class
-MainActivity
: Alert(사용자에게 권한 요청)로 (외부 접근)권한이 허락되면 provider를 통해 이미지 파일들을 가져온다. 모든 파일을 순회해가며 이미지를 uri형태로 가져오고, uri를 보내며 프래그먼트들을 만든다. 만든 프래그먼트들은 ArrayList에 담고 Adapter(아이템 배열 객체)에 ArrayList를 넘긴다. 마지막으로 ViewPager에 Adapter를 연결해서 페이지 아이템을 갖게한다. 이 아이템들을 3초에 한 번씩 바꾸면 슬라이드쇼가 완성된다!
-photoFragment
: 이미지뷰만 위젯으로 가지는 프래그먼트(임시 액티비티)이다. 클래스 개념이라 객체로 여러 개를 만들 수 있다. Main에서 uri가 넘어오면 뷰를 다 만들고 , Glide클래스를 통해 uri에 맞는 이미지를 로딩(메모리에 적재)한다. 로딩된 이미지는 이미지뷰로 넘어간다.
-MyPagerAdapter.class (extends FragmentStatePagerAdapter)
: 뷰 페이저를 만드려면 어댑터(아이템 배열 객체)가 필요하다. 뷰 페이저니 어댑터의 아이템은 페이지(액티비티나 프래그먼트)가 된다. 이 클래스에서는 ArrayList(프래그먼트 객체 배열)를 메소드로 받아서 items라고 하는 리스트에 모든 요소를 넣는다. getItem이나 getCount같은 메소드도 items에 맞게 재정의한다. 어댑터 객체는 메모리 관리를 효율적으로 해줘서, 많은 데이터를 필요로하는 뷰 페이저랑 짝궁이 된다. 뷰 페이저에 이 어댑터가 연결이 되면, 재정의한 메소드를 알아서 호출하고 화면에 현재 아이템(이미지뷰가 담긴 페이지)을 나타낸다!
Process
권한 부여 -> 파일들 가져옴 -> 파일 순회하며 uri가져옴 -> uri개수만큼 프래그먼트 생성 -> (프래그먼트 개수 만큼) 이미지 로딩 후 이미지뷰에 적용 -> 프래그먼트들을 리스트에 저장 -> 리스트를 어댑터에 넘김 -> 뷰페이저에 어댑터 연결 -> 타이머를 통해 슬라이드쇼 제작
Result
Source
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <!-- 위험 권한이라서 사용자가 권한 허용을 해줘야 적용됨-->
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <androidx.viewpager.widget.ViewPager android:layout_width="0dp" android:layout_height="0dp" android:id="@+id/viewPager" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
package com.example.mygallery import android.content.pm.PackageManager import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.provider.MediaStore import androidx.core.content.ContextCompat import android.Manifest import androidx.core.app.ActivityCompat import androidx.fragment.app.Fragment import kotlinx.android.synthetic.main.activity_main.* import org.jetbrains.anko.alert import org.jetbrains.anko.noButton import org.jetbrains.anko.toast import org.jetbrains.anko.yesButton import kotlin.concurrent.timer class MainActivity : AppCompatActivity() { private val request_read_external=1000 // 이 코드로 (외부 저장소 접근에 대한 사용자의 요청)을 인식함 fun getAllPhotos(){ // 모든 사진 정보 가져오기 // contentResolver객체는 프로바이더를 사용하고 데이터를 가져온다. // <query메소드를 통해 무엇을 가져올지, 정렬은 어떻게할지 등 결정> // 프로바이더: 앱 과 앱 사이에 데이터를 공유하는 환경을 만듬 // <앱과 앱 사이에서 데이터를 공유하는 역할을 하는 컴포넌트임> // cursor는 가져온 데이터를 관리하는 객체를 가리킴 val cursor=contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, // 미디어 관련 외부 데이터를 가져옴 null, // 어떤 항목을 가져올 것인지 지정 (null-> 다 가져옴) null, // 데이터를 가져올 조건 지정 (null-> 사용 안 함) null, // 마찬가지로 조건 지정 (null-> 사용 안 함) MediaStore.Images.ImageColumns.DATE_TAKEN+" DESC") // 사진이 찍힌 날짜의 내림차순으로 정렬 // 프레그먼트를 요소로 하는 프레그먼트 리스트 val fragments=ArrayList<Fragment>() if(cursor !=null){ // moveToNext로 데이터를 하나씩 이동하면서 처리함 , 없으면 null이 오면서 while문을 빠져 나옴 while(cursor.moveToNext()){ // (현재 데이터에 해당하는 이미지)의 uri가져오기 val uri=cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)) // uri를 보내서 프래그먼트 만들고 프래그먼트 가져와서 리스트에 저장 fragments.add(photoFragment.newInstance(uri)) } cursor.close() // 관리 객체 해제 // 어댑터(아이템을 담는 객체: 아이템은 무엇이든 될 수 있다) 생성 val adapter= MyPagerAdapter(supportFragmentManager) adapter.updateFragments(fragments) // fragments리스트를 어댑터에 넘김 viewPager.adapter=adapter // 뷰페이저에 어댑터 연결 // 뷰페이저는 현재 인덱스에 맞는 페이지(프래그먼트)를 화면에 나타낸다. // 3초마다 자동 슬라이드 timer(period=3000){ runOnUiThread{ // 현재 페이지가 마지막 페이지일 때 if(viewPager.currentItem == adapter.count-1) viewPager.currentItem=0 // 아니면 페이지 인덱스를 1씩 올림 else viewPager.currentItem=viewPager.currentItem+1 } } } } // 권한 요청 클래스 inner class user_req(main:MainActivity){ init{ ActivityCompat.requestPermissions(main, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),request_read_external) } } // 사용자가 권한 요청<허용,비허용>한 후에 이 메소드가 호출됨 override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) when(requestCode){ // (외부 저장소 접근에 대한 사용자의 요청)일 때 request_read_external->{ // 요청이 허용일 때 if(grantResults.isNotEmpty() && grantResults[0]==PackageManager.PERMISSION_GRANTED) getAllPhotos() // 요청이 비허용일 때 else{ toast("권한 거부 됨") finish() } } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 앱에 외부 데이터를 접근할 권한이 없을 때 // <앱을 처음 실행하거나, 사용자가 이전에 권한 허용을 안 했을 떼 성립> if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED){ // <PERMISSION_DENIED가 반환됨> // 이전에 사용자가 앱 권한을 허용하지 않았을 때 -> 왜 허용해야되는지 알람을 띄움 // shouldShowRequestPermissionRationale메소드는 이전에 사용자가 앱 권한을 허용하지 않았을 때 ture를 반환함 if(ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)){ alert("사진 정보를 얻으려면 외부 저장소 권한이 필수로 필요합니다",""){ yesButton{ // 권한 요청 user_req(this@MainActivity) } noButton { finish() } }.show() } // 앱 처음 실행했을 때 else user_req(this) // 권한 요청 } // 앱에 권한이 허용됨 else getAllPhotos() } }
fragment_photo.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <ImageView android:layout_width="0dp" android:layout_height="0dp" tools:src="@tools:sample/avatars" android:id="@+id/imageView" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
photo_fragment.kt
package com.example.mygallery import android.os.Bundle import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.bumptech.glide.Glide import kotlinx.android.synthetic.main.fragment_photo.* // const키워드를 써서 전역변수 만들기 private const val ARG_URI="uri" class photoFragment : Fragment() { private var uri:String?=null // (1) 제일 먼저 호출됨 companion object { @JvmStatic // 프래그먼트 생성 <그리고 uri를 받음> fun newInstance(uri: String) = // apply 호출이 n번되면 n번 photoFragment객체를 만든다. // <이미지뷰를 n번 만들 수는 없어도 이미지뷰를 가진 프래그먼트를 n번 만들 수는 있다!!> photoFragment().apply { // arguments생성 후 uri 문자열 저장 arguments = Bundle().apply { putString(ARG_URI,uri) } } } // 프래그먼트 생성 후 호출되는 메소드 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // arguments에 담긴 스트링을 빼내서 프로퍼티 uri에 저장 arguments?.let { uri=it.getString(ARG_URI) } } // 프래그먼트에 표시될 뷰를 생성 // 액티비티가 아닌 것에서 레리아웃 리소스를 가져오기 위해 inflater를 사용함 override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_photo, container, false) } // 뷰 생성이 완료되면 호출되는 메소드 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // glide에 uri를 통해 이미지를 로딩하고 이미지뷰로 옮긴다. Glide.with(this).load(uri).into(imageView) } }
MyPagerAdapter.kt
package com.example.mygallery import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentStatePagerAdapter // 아이템 배열이라고 생각하면 된다. FragmentStatePagerAdapter는 지나간 아이템은 메모리에서 없애는 객체이다. class MyPagerAdapter(fm:FragmentManager?): FragmentStatePagerAdapter(fm) { // 뷰페이저가 표시할 프래그먼트 목록 private val items=ArrayList<Fragment>() // position 위치의 프래그먼트 override fun getItem(position: Int): Fragment { return items[position] } // 프래그먼트 개수 override fun getCount(): Int { return items.size } // 프래그먼트 갱신 fun updateFragments(items:List<Fragment>){ this.items.addAll(items) } }