๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

Study/Android

[์•ˆ๋“œ๋กœ์ด๋“œ ์ฝ”ํ‹€๋ฆฐ] 10. ์‹ค์ „ํ”„๋กœ์ ํŠธ(3) - ์ „์ž์•ก์ž

์‹ค์ „ ํ”„๋กœ์ ํŠธ

์‹ค์Šต - ์ „์ž ์•ก์ž

์ฃผ์š”๊ตฌ์„ฑ

- ํ”„๋ž˜๊ทธ๋จผํŠธ๋ผ๋Š” UI ์กฐ๊ฐ์œผ๋กœ ๊ตฌ์„ฑํ•˜๊ณ  ํ”„๋ž˜๊ทธ๋จผํŠธ๋“ค์„ ์ขŒ์šฐ๋กœ ์Šฌ๋ผ์ด๋“œ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ทฐํŽ˜์ด์ €ViewPager๋ฅผ ์‚ฌ์šฉ

- ์‚ฌ์ง„์ด๋ฏธ์ง€ ๋กœ๋”ฉ์— Glide ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉ

- timer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž๋™์œผ๋กœ ์Šฌ๋ผ์ด๋“œ ๋˜๊ฒŒ ํ•จ

 

1. ํ”„๋กœ๋ฐ”์ด๋” ์‚ฌ์šฉ

์ฝ˜ํ…์ธ  ํ”„๋กœ๋ฐ”์ด๋”

- ์•ฑ์˜ ๋ฐ์ดํ„ฐ ์ ‘๊ทผ์„ ๋‹ค๋ฅธ ์•ฑ์— ํ—ˆ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ

- ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์ด์šฉํ•˜์—ฌ ์‚ฌ์ง„ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์˜ค๋Š” ์ˆœ์„œ

โ‘  ์‚ฌ์ง„ ๋ฐ์ดํ„ฐ๋Š” ์™ธ๋ถ€ ์ €์žฅ์†Œ์— ์ €์žฅ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ์™ธ๋ถ€ ์ €์žฅ์†Œ ์ฝ๊ธฐ ๊ถŒํ•œ์„ ์•ฑ์— ๋ถ€์—ฌ

โ‘ก ์™ธ๋ถ€ ์ €์žฅ์†Œ ์ฝ๊ธฐ ๊ถŒํ•œ์€ ์œ„ํ—˜ ๊ถŒํ•œ์œผ๋กœ ์‹คํ–‰ ์ค‘์— ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ถŒํ•œ์„ ํ—ˆ์šฉํ•˜๋„๋ก ํ•จ

โ‘ข contentResolver ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ Cursor ๊ฐ์ฒด๋กœ ๊ฐ€์ง€๊ณ  ์˜ด

์•ˆ๋“œ๋กœ์ด๋“œ 4๋Œ€ ์ปดํฌ๋„ŒํŠธ
- ์•กํ‹ฐ๋น„ํ‹ฐ: ํ™”๋ฉด์„ ๊ตฌ์„ฑ
- ์ฝ˜ํ…์ธ  ํ”„๋กœ๋ฐ”์ด๋”: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ํŒŒ์ผ, ๋„คํŠธ์›Œํฌ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฅธ ์•ฑ์— ๊ณต์œ 
- ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๋ฆฌ์‹œ๋ฒ„: ์•ฑ์ด๋‚˜ ๊ธฐ๊ธฐ๊ฐ€ ๋ฐœ์†กํ•˜๋Š” ๋ฐฉ์†ก์„ ์ˆ˜์‹ ํ•จ
- ์„œ๋น„์Šค: ํ™”๋ฉด์ด ์—†๊ณ  ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์— ์šฉ์ด

 

์Šค๋งˆํŠธํฐ์˜ ์‚ฌ์ง„ ๊ฒฝ๋กœ ์ฐพ๊ธฐ

์™ธ๋ถ€ ์ €์žฅ์†Œ์— ์ €์žฅ๋œ ๋ชจ๋“  ์‚ฌ์ง„์„ ์ตœ์‹ ์ˆœ์œผ๋กœ ์ •๋ ฌํ•˜์—ฌ Cursor๊ฐ์ฒด์— ์—ฐ๊ฒฐ

โ‘  ์ฒซ ๋ฒˆ์งธ ์ธ์ž : ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š๋ƒ๋ฅผ URI ํ˜•ํƒœ๋กœ ์ง€์ •

โ‘ก ๋‘ ๋ฒˆ์งธ ์ธ์ž : ์–ด๋–ค ํ•ญ๋ชฉ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๊ฒƒ์ธ์ง€ String ๋ฐฐ์—ด๋กœ ์ง€์ •

โ‘ข ์„ธ ๋ฒˆ์งธ ์ธ์ž : ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์กฐ๊ฑด์„ ์ง€์ •. ์ „์ฒด ๋ฐ์ดํ„ฐ๋Š” null๋กœ ์„ค์ •

โ‘ฃ ๋„ค ๋ฒˆ์งธ ์ธ์ž : ์„ธ ๋ฒˆ์งธ ์ธ์ž์— ์ถ”๊ฐ€ ์กฐ๊ฑด์„ ์ง€์ •, ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด null๋กœ ์„ค์ •

โ‘ค ๋‹ค์„ฏ ๋ฒˆ์งธ ์ธ์ž : ์ •๋ ฌ ๋ฐฉ๋ฒ•์„ ์ง€์ •, ์‚ฌ์ง„์ด ์ฐํžŒ ๋‚ ์งœ์˜ ๋‚ด๋ฆผ์ฐจ์ˆœ ์ •๋ ฌ

// ๋ชจ๋“  ์‚ฌ์ง„ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
val cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URIm	//โ‘ 
    null,	// โ‘ก ๊ฐ€์ ธ์˜ฌ ํ•ญ๋ชฉ ๋ฐฐ์—ด
    null,	// โ‘ข ์กฐ๊ฑด
    null,	// โ‘ฃ ์กฐ๊ฑด
    MediaStore.Images.ImageColumns.DATE_TAKEN + "DESC")	// โ‘ค ์ดฌ์˜ ๋‚ ์งœ ๋‚ด๋ฆผ์ฐจ์ˆœ

โ€ป ์•ฑ ์‹คํ–‰ํ•˜๋ฉด ์•ฑ์ด ์ข…๋ฃŒ๋˜๋ฉด์„œ ์—๋Ÿฌ๊ฐ€ ๋œธ → ๋กœ๊ทธ์บฃ ์ฐฝ์— ํ‘œ์‹œ (→ ์™ธ๋ถ€ ์ €์žฅ์†Œ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์œผ๋ ค ํ–ˆ๋Š”๋ฐ ๊ถŒํ•œ์ด ์—†๋‹ค๋Š” ์—๋Ÿฌ ๋ฐœ์ƒ)

 

๋งค๋‹ˆํŽ˜์ŠคํŠธ์— ์™ธ๋ถ€ ์ €์žฅ์†Œ ์ฝ๊ธฐ ๊ถŒํ•œ ์ถ”๊ฐ€

- AndroidManifest.xml ํŒŒ์ผ์„ ์—ด๊ณ  READ_EXTERNAL_STORAGE ์™ธ๋ถ€ ์ €์žฅ์†Œ ์ฝ๊ธฐ ๊ถŒํ•œ์„ ์•ฑ์— ์ถ”๊ฐ€

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    
    <application
        ...
    </application>

</manifest>
๊ถŒํ•œ ๊ทธ๋ฃน ๊ถŒํ•œ
STORAGE - READ_EXTERNAL_STORAGE
- WRITE_EXTERNAL_STORAGE
LOCATION - ACCESS_FINE_LOCATION
- ACCESS_COARSE_LOCATION
SMS - SEND_SMS
- RECEIVE_SMS
CAMERA - CAMERA

 

๊ถŒํ•œ ์ถ”๊ฐ€

- ์‹คํ–‰ ์ค‘์— ์œ„ํ—˜ ๊ถŒํ•œ์ด ํ•„์š”ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ๋•Œ๋งˆ๋‹ค ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•จ

- ์•ฑ์ด ์™ธ๋ถ€ ์ €์žฅ์†Œ ์ฝ๊ธฐ ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์ฝ”๋“œ

๋”๋ณด๊ธฐ

API 33 ์ด์ƒ๋ถ€ํ„ฐ READ_EXTERNAL_STORAGE ๊ถŒํ•œ์ด “READ_MEDIA_IMAGES”, ”READ_MEDIA_VIDEO”, ”READ_MEDIA_AUDIO”๋กœ ์„ธ๋ถ„ํ™” ๋จ

→ ์‚ฌ์ง„์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํ•ด๋‹น์ด๋ฆ„์˜ ๊ถŒํ•œ์œผ๋กœ ๊ถŒํ•œ ์ฒดํฌ ๋ฐ ๊ถŒํ•œ์š”์ฒญ์„ ์ง„ํ–‰ํ•ด์•ผ ํ•จ

// ๊ถŒํ•œ์ด ๋ถ€์—ฌ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ
if (ContextCompat.checkSelfPermission(this, readIMagePermission) != PackageManager.PERMISSION_GRANTED)

    - Compat Class : ๋งค๋…„ ์ƒˆ๋กœ ๋ฐœํ‘œ๋˜๋Š” SDK์˜ API ๋ฒ„์ „๋ณ„ ์ฐจ์ด์˜ ํ˜ธํ™˜์„ฑ ์ง€์›

    - ContextCompat : ์•ฑ๊ณผ ์•ฑ ์ €์žฅ์†Œ ์‚ฌ์ด์— ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ์ง€์›

- Manifest ํด๋ž˜์Šค๋Š” ์—ฌ๋Ÿฌ ํŒจํ‚ค์ง€์— ์กด์žฌํ•˜๋Š”๋ฐ ์ฝ”๋“œ ์ž‘์„ฑ ์ค‘ ์–ด๋Š ๊ฒƒ์„ ์ž„ํฌํŠธํ• ์ง€ ๋ฌผ์–ด๋ณด๋ฉด android๋ฅผ ์ž„ํฌํŠธํ•จ

 

๊ถŒํ•œ ์š”์ฒญ(MainActivity.kt)

โ‘  ์•ฑ์— ๊ถŒํ•œ์ด ๋ถ€์—ฌ ์—ฌ๋ถ€ ํ™•์ธ

โ‘ก shoudShowRequestPermissionRationale( ) : ์ด์ „์— ๊ถŒํ•œ ์š”์ฒญ์„ ๊ฑฐ๋ถ€ ์—ฌ๋ถ€ ๋ฐ˜ํ™˜

โ‘ข๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ๊ฑฐ๋ถ€ํ–ˆ๋‹ค๋ฉด ๊ถŒํ•œ์ด ์™œ ํ•„์š”ํ•œ์ง€์— ๋Œ€ํ•ด์„œ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•˜๊ณ  ๋‹ค์‹œ ๊ถŒํ•œ์„ ์š”์ฒญ

โ‘ฃ ๊ถŒํ•œ์ด ๋ถ€์—ฌ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด requestPermissions( ) ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ถŒํ•œ ์š”์ฒญ

โ‘ค ๊ถŒํ•œ ํ—ˆ์šฉ์‹œ์—๋Š” ์ด๋ฏธ์ง€ ์ฝ์–ด ์˜ค๊ธฐ ์ˆ˜ํ–‰

class MainActivity : AppCompatActivity() {

    private val REQUEST_READ_EXTERNAL_STORAGE = 1000

    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

        // ๊ถŒํ•œ์ด ๋ถ€์—ฌ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ
        if (ContextCompat.checkSelfPermission(this, readIMagePermission) != PackageManager.PERMISSION_GRANTED)

            // ๊ถŒํ•œ์ด ํ—ˆ์šฉ๋˜์ง€ ์•Š์Œ
            if(ActivityCompat.shouldShowRequestPermissionRationale(this, readIMagePermission)){
                // ์ด์ „์— ๊ถŒํ•œ ๊ฑฐ๋ถ€ํ•œ ์  ์žˆ์œผ๋ฉด ์„ค๋ช…(๊ฒฝ๊ณ )
                var dlg = AlertDialog.Builder(this)
                dlg.setTitle("๊ถŒํ•œ์ด ํ•„์š”ํ•œ ์ด์œ ")
                dlg.setMessage("์‚ฌ์ง„ ์ •๋ณด๋ฅผ ์–ป๊ธฐ ์œ„ํ•ด์„œ๋Š” ์™ธ๋ถ€ ์ €์žฅ์†Œ ๊ถŒํ•œ์ด ํ•„์ˆ˜๋กœ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.")
                dlg.setPositiveButton("ํ™•์ธ"){dialog, which-> ActivityCompat.requestPermissions(this@MainActivity,
                                    arrayOf(readIMagePermission), REQUEST_READ_EXTERNAL_STORAGE)}
                dlg.setNegativeButton("์ทจ์†Œ", null)
                dlg.show()
            } else{
                // ์ฒ˜์Œ ๊ถŒํ•œ ์š”์ฒญ
                ActivityCompat.requestPermissions(this@MainActivity,
                    arrayOf(readIMagePermission), REQUEST_READ_EXTERNAL_STORAGE)
            } else{
                // ๊ถŒํ•œ์ด ์ด๋ฏธ ์ œ๋Œ€๋กœ ํ—ˆ์šฉ๋จ
                getAllPhotos()
        }
    }

 

์‚ฌ์šฉ๊ถŒํ•œ ์š”์ฒญ ์‘๋‹ต ์ฒ˜๋ฆฌ

- ์‚ฌ์šฉ์ž๊ฐ€ ๊ถŒํ•œ์„ ์š”์ฒญํ•˜๋ฉด ์‹œ์Šคํ…œ์€ onRequestPermissionsResult ( ) ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์‚ฌ์šฉ์ž์˜ ์‘๋‹ต์„ ์ „๋‹ฌ

- ์‘๋‹ต ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•˜์—ฌ ์‚ฌ์ง„ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜ ๊ถŒํ•œ์ด ๊ฑฐ๋ถ€๋๋‹ค๋Š” ํ† ์ŠคํŠธ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑ

private fun getAllPhotos(){
    ...
    if(cursor != null){
        while(cursor.moveToNext()){
            // ์‚ฌ์ง„ ๊ฒฝ๋กœ Uri ๊ฐ€์ ธ์˜ค๊ธฐ
            val uri = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))
            Log.d("MainActivity", uri)
        }
        cursor.close()
    }
}

 

- Device File Explorer ํด๋ฆญ > sdcard > Pictures ์šฐํด๋ฆญ > Upload ํด๋ฆญํ•ด์„œ ์ด๋ฏธ์ง€ ์ถ”๊ฐ€ > Synchronize

- AVD ์žฌ์‹œ์ž‘ ํ•„์š” (์‚ฌ์ง„ ์•ฑ์— ์ด๋ฏธ์ง€ ์•ˆ๋“ค์–ด๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—)

 

2. ์ „์ž์•ก์ž ๊ตฌํ˜„ํ•˜๊ธฐ

ํ”„๋ž˜๊ทธ๋จผํŠธ(Fragment): ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋ชจ์Œ

 

ํ”„๋ž˜๊ทธ๋จผํŠธ์˜ ๊ฐœ๋…

- ํ”„๋ž˜๊ทธ๋จผํŠธ์— ํ‘œ์‹œํ•  ๋ทฐ๋ฅผ ๋ ˆ์ด์•„์›ƒ ํŒŒ์ผ๋กœ๋ถ€ํ„ฐ ์ฝ์–ด์˜ค๋Š” ๋ถ€๋ถ„์€ โ‘ก onCreateView( ) ๋ฉ”์„œ๋“œ์ด๋ฉฐ ์•กํ‹ฐ๋น„ํ‹ฐ์˜ onCreate( )์™€ ๋™์ผ

- โ‘  onCreate( ) ๋ฉ”์„œ๋“œ๋Š” ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์ธ์ž๊ฐ€ ํ•จ๊ป˜ ๋„˜์–ด์˜จ๋‹ค๋ฉด ์ฃผ๋กœ โ‘  onCreate( ) ๋ฉ”์„œ๋“œ์—์„œ ๋ฐ›์•„์„œ ๋ณ€์ˆ˜์— ๋‹ด์Œ.

- โ‘ก onCreateView( ) ๋ฉ”์„œ๋“œ์—์„œ ์™„์„ฑ๋œ ๋ ˆ์ด์•„์›ƒ ๋ทฐ๋Š” ์ƒ๋ช…์ฃผ๊ธฐ์—๋Š” ํฌํ•จ๋˜์ง€ ์•Š๋Š” โ‘ข onViewCreated( ) ๋ฉ”์„œ๋“œ๋กœ ์ „๋‹ฌ๋˜๋ฉฐ ์ด์ชฝ์—์„œ ๋ทฐ๊ฐ€ ์™„์„ฑ๋œ ์ดํ›„์— ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋“ฑ์„ ์ˆ˜ํ–‰

 

ํ”„๋ž˜๊ทธ๋จผํŠธ ์ƒ์„ฑ

- ํ”„๋กœ์ ํŠธ ์ฐฝ์—์„œ ํŒจํ‚ค์ง€๋ฅผ ํด๋ฆญ ํ›„ ์šฐํด๋ฆญ ๋˜๋Š” ์•ˆ๋“œ๋กœ์ด๋“œ ์ŠคํŠœ๋””์˜ค ์ƒ๋‹จ ๋ฉ”๋‰ด์—์„œ File → New → Fragment → Fragment (Blank)๋ฅผ ํด๋ฆญ

 

ํ”„๋ž˜๊ทธ๋จผํŠธ์˜ ๋ ˆ์ด์•„์›ƒ ์ˆ˜์ •

๋ ˆ์ด์•„์›ƒ์„ ConstraintLayout์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  ์ด๋ฏธ์ง€ ๋ทฐ๋ฅผ ํ™”๋ฉด์— ๊ฝ‰ ์ฑ„์šฐ๋„๋ก ์„ค์ •

 

Glide ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ : ๋น ๋ฅด๊ณ  ํšจ์œจ์ ์œผ๋กœ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ

- ์ด๋ฏธ์ง€, Gif, ๋น„๋””์˜ค ์Šคํ‹ธ ๋กœ๋”ฉ๊ณผ ๋””์ฝ”๋”ฉ, ์บ์‹ฑ ๋“ฑ์˜ API ์ง€์›

- ๋‹ค์–‘ํ•œ ์ข…๋ฅ˜์˜ ์ด๋ฏธ์ง€๋ฅผ ๋น ๋ฅด๊ณ  ๋ถ€๋“œ๋Ÿฌ์šด ์Šคํฌ๋กค ์ง€์›

 

Glide ์‚ฌ์šฉ์„ ์œ„ํ•ด์„œ๋Š” ์˜์กด์„ฑ ๋ฐ ๊ถŒํ•œ ์„ค์ • ํ•„์š”

- ๋ฐฉ๋ฒ•1) Gradle ์ถ”๊ฐ€ (์ง์ ‘ ์ฝ”๋“œ ํƒ€์ดํ•‘)

    - Sync Now ์ˆ˜ํ–‰

 

- ๋งค๋‹ˆํŽ˜์ŠคํŠธ ์ถ”๊ฐ€(์›๊ฒฉ ์ด๋ฏธ์ง€ ์“ธ ๊ฒฝ์šฐ, ๊ถŒํ•œ ํ•„์š”) (// ์ธํ„ฐ๋„ท์„ ํ†ตํ•ด ์ด๋ฏธ์ง€ ๊ฐ€์ ธ์˜ด)

 

๋ทฐ์— ๋กœ๋“œํ•˜๋Š” ๋ฐฉ๋ฒ•

- .with(์—…๋กœ๋“œํ•  ์ด๋ฏธ์ง€ ๋„์šธ ๊ณณ), .load(์—…๋กœ๋“œ ์ด๋ฏธ์ง€ uri), .into(๋ณด์—ฌ์ค„ ์œ„์ ฏ)

 

๋ฐฉ๋ฒ•2) Glide ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ ์ค€๋น„

- ์ง์ ‘ build.gradle ํŒŒ์ผ์„ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ  ์•ˆ๋“œ๋กœ์ด๋“œ ์ŠคํŠœ๋””์˜ค์˜ ๋ฉ”๋‰ด ํ™œ์šฉ

- ์ƒ๋‹จ ๋ฉ”๋‰ด ์ค‘ File → Project Structure๋ฅผ ํด๋ฆญ

 

ํ”„๋ž˜๊ทธ๋จผํŠธ์— ์‚ฌ์ง„ ํ‘œ์‹œํ•˜๊ธฐ

โ‘  ํด๋ž˜์Šค ์„ ์–ธ ๋ฐ–์— const ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒ์ˆ˜๋กœ ์ •์˜

โ‘ก newInstance( ) ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜์—ฌ ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ณ  ์ธ์ž๋กœ uri๊ฐ’์„ ์ „๋‹ฌ 

โ‘ข ํ”„๋ž˜๊ทธ๋จผํŠธ๊ฐ€ ์ƒ์„ฑ๋˜๋ฉด onCreate( ) ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๊ณ  ARG_URI ํ‚ค์— ์ €์žฅ๋œ uri๊ฐ’์„ ๋ณ€์ˆ˜์— ์ €์žฅ 

โ‘ฃ onCreateView( ) ๋ฉ”์„œ๋“œ์—์„œ๋Š” ํ”„๋ž˜๊ทธ๋จผํŠธ์— ํ‘œ์‹œ๋  ๋ทฐ๋ฅผ ์ƒ์„ฑ

โ‘ค Glide.with (this)๋กœ ์‚ฌ์šฉ ์ค€๋น„๋ฅผ ํ•˜๊ณ  load ( )์— uri๊ฐ’์„ ์ธ์ž๋กœ ์ฃผ๊ณ  ์ด๋ฏธ์ง€ ๋กœ๋”ฉ

โ‘ฅ into( ) ๋ฉ”์„œ๋“œ๋กœ imageView์— ํ‘œ์‹œ

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 android.widget.ImageView
import com.bumptech.glide.Glide

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_URI = "uri"

/**
 * A simple [Fragment] subclass.
 * Use the [PhotoFragment.newInstance] factory method to
 * create an instance of this fragment.
 */
class PhotoFragment : Fragment() {
    // TODO: Rename and change types of parameters
    private var uri: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            uri = it.getString(ARG_URI)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_photo, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        var imageView : ImageView = view.findViewById(R.id.imageView)
        Glide.with(this).load(uri).into(imageView)      // ๊ทธ๋ฆผ ์ถœ๋ ฅ
    }

    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment PhotoFragment.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(uri: String) =
            PhotoFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_URI, uri)
                }
            }
    }
}

 

์•กํ‹ฐ๋น„ํ‹ฐ์— ViewPager ์ถ”๊ฐ€

- ๋ทฐํŽ˜์ด์ € : ์—ฌ๋Ÿฌ ํ”„๋ž˜๊ทธ๋จผํŠธ๋“ค์„ ์ขŒ์šฐ๋กœ ์Šฌ๋ผ์ด๋“œํ•˜๋Š” ๋ทฐ

 

PagerAdapter ์ž‘์„ฑ

- ViewPager์— ํ‘œ์‹œํ•  ๋‚ด์šฉ์„ ์ •์˜ํ•˜๋ ค๋ฉด ์–ด๋Œ‘ํ„ฐ๊ฐ€ ํ•„์š”

- ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ์•„์ดํ…œ์œผ๋กœ ๊ฐ€์ง€๋ฉด์„œ ViewPager์— ์„ค์ •ํ•˜๋Š” ์–ด๋Œ‘ํ„ฐ๋Š” ๋‹ค์Œ ๋‘ ๊ฐ€์ง€

    - FragmentPAgerAdapter: ํŽ˜์ด์ง€ ๋‚ด์šฉ์ด ์˜๊ตฌ์ ์ผ ๋•Œ ์ ํ•ฉ. ํ•œ ๋ฒˆ ๋กœ๋”ฉํ•œ ํŽ˜์ด์ง€๋Š” ๋ฉ”๋ชจ๋ฆฌ์— ๋ณด๊ด€ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋นจ๋ฆ„. ํŽ˜์ด์ง€๊ฐ€ ๋งŽ์œผ๋ฉด ๋งŽ์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์‚ฌ์šฉ

    - FragmentStatePagerAdapter: ๋งŽ์€ ์ˆ˜์˜ ํŽ˜์ด์ง€๊ฐ€ ์žˆ์„ ๋•Œ ์ ํ•ฉ. ๋ณด์ด์ง€ ์•Š๋Š” ํŽ˜์ด์ง€๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Œ. ์ƒ๋Œ€์ ์œผ๋กœ ์ ์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ฐจ์ง€

 

FragmentStatePagerAdapter ์‚ฌ์šฉ

- MainActivity ์šฐํด๋ฆญ → new → Kotlin File/Class (๋˜๋Š” ์•ˆ๋“œ๋กœ์ด๋“œ ์ŠคํŠœ๋””์˜ค ์ƒ๋‹จ ๋ฉ”๋‰ด์˜ File → New → Kotlin File/Class๋ฅผ ํด๋ฆญ)

โ‘  MyPagerAdapter.kt ํŒŒ์ผ์ด ์—ด๋ฆฌ๋ฉด FragmentStatePagerAdapter ํด๋ž˜์Šค๋ฅผ ์ƒ์†๋ฐ›์Œ

โ‘ก ์Šˆํผ ํด๋ž˜์Šค์˜ ์ƒ์„ฑ์ž๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” Add constructor parameters from FragmentStatePagerAdapter(FragmentManager!)๋ฅผ ํด๋ฆญ

โ‘ข ์ƒ์„ฑ์ž ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์ถ”๊ฐ€๋˜๊ณ  ๋‹ค์‹œ ๋นจ๊ฐ„ ์ค„์ด ํ‘œ์‹œ๋˜๋Š” ํด๋ž˜์Šค ์ด๋ฆ„์— ์ปค์„œ๋ฅผ ๋‘๊ณ 

๋ฏธ๊ตฌํ˜„๋œ ๋ฉค๋ฒ„๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” Implement members๋ฅผ ํด๋ฆญ

โ‘ฃ 'Ctrl + A' ๋ฅผ ํด๋ฆญํ•˜์—ฌ ๋ชจ๋“  ๋ฉ”์„œ๋“œ๋ฅผ ์„ ํƒํ•˜๊ณ  OK๋ฅผ ํด๋ฆญ

โ‘ค ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ ์ฝ”๋“œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•จ

โ‘ฅ ์–ด๋Œ‘ํ„ฐ๊ฐ€ ํ”„๋ž˜๊ทธ๋จผํŠธ์˜ ๋ชฉ๋ก์„ ๋ฆฌ์ŠคํŠธ๋กœ ๊ฐ€์ง€๋„๋ก ํ•จ

โ‘ฆ ์ด ๋ชฉ๋ก์€ update Fragments( ) ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ์™ธ๋ถ€์—์„œ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Œ (์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์ž‘์„ฑ)

โ‘ง getItem ( ) : position ์œ„์น˜์— ์–ด๋–ค ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ํ‘œ์‹œํ• ์ง€๋ฅผ ์ •์˜

โ‘จ getCount( ) : ์•„์ดํ…œํ”„๋ž˜๊ทธ๋จผํŠธ ๊ฐœ์ˆ˜๋ฅผ ์ •์˜

package com.example.mygallery

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.viewpager2.adapter.FragmentStateAdapter

class MyPagerAdapter(fragment: FragmentManager) : FragmentStatePagerAdapter(fragment) {

    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)
    }
}

 

์ „์ž์•ก์ž ์™„์„ฑ

- getAllPhotos ( ) ๋ฉ”์„œ๋“œ ์ˆ˜์ •

โ‘  ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ์•„์ดํ…œ์œผ๋กœ ํ•˜๋Š” ArrayList ๋ฅผ ์ƒ์„ฑ 

โ‘ก ์‚ฌ์ง„์„ Cursor๊ฐ์ฒด์—์„œ ๊ฐ€์ ธ์˜ฌ ๋•Œ PhotoFragment.newInstance (uri)๋กœ ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด์„œ fragments ๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€

โ‘ข MyPagerAdapter ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด์„œ ํ”„๋ž˜๊ทธ๋จผํŠธ ๋งค๋‹ˆ์ €๋ฅผ ์ƒ์„ฑ์ž์˜ ์ธ์ž๋กœ ์ „๋‹ฌ

โ‘ฃ ์•ฑ์„ ์‹คํ–‰ํ–ˆ์„ ๋•Œ ์‚ฌ์ง„์ด ํ‘œ์‹œ๋˜๊ณ  ์ขŒ์šฐ๋กœ ์Šฌ๋ผ์ด๋“œ ํ–ˆ์„ ๋•Œ ๋‹ค์Œ ์‚ฌ์ง„์œผ๋กœ ๋„˜์–ด๊ฐ€๋ฉด ์„ฑ๊ณต

class MainActivity : AppCompatActivity() {

    private val REQUEST_READ_EXTERNAL_STORAGE = 1000
    lateinit var viewPager: ViewPager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewPager = findViewById(R.id.viewPager)

       
       ...


    }

    private fun getAllPhotos(){
        // ๋ชจ๋“  ์‚ฌ์ง„ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
        ...
        
        val fragments = ArrayList<Fragment>()	// 1

        if(cursor != null){
            while(cursor.moveToNext()){
                // ์‚ฌ์ง„ ๊ฒฝ๋กœ Uri ๊ฐ€์ ธ์˜ค๊ธฐ
                val uri = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))
                Log.d("MainActivity", uri)
                fragments.add(PhotoFragment.newInstance(uri))		// 2
            }
            cursor.close()
        }

        // ์–ด๋Œ‘ํ„ฐ 3
        val adapter = MyPagerAdapter(supportFragmentManager)
        adapter.updateFragments(fragments)
        viewPager.adapter = adapter
    }
}

 

// MyPagerAdapter.kt ์ฝ”๋“œ ์ˆ˜์ •
class MyPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

 

3. ์Šฌ๋ผ์ด๋“œ์‡ผ ์ˆ˜ํ–‰ํ•˜๊ธฐ

getAllPhotos ( ) ๋ฉ”์„œ๋“œ์˜ ๋งˆ์ง€๋ง‰์— ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€

โ‘  3์ดˆ๋งˆ๋‹ค ์‹คํ–‰๋˜๋Š” ํƒ€์ด๋จธ๋ฅผ ์ƒ์„ฑ : 3์ดˆ๋งˆ๋‹ค ํŽ˜์ด์ง€๋ฅผ ์ „ํ™˜ํ•˜๋Š” UI ๋ณ€๊ฒฝ ์‹คํ–‰ (timer ์‚ฌ์šฉ)

โ‘ก Timer๊ฐ€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ๋กœ ๋™์ž‘ํ•ด UI๋ฅผ ๋ณ€๊ฒฝ์€ runOnUiThread ์ˆ˜ํ–‰ 

โ‘ข ํ˜„์žฌ ํŽ˜์ด์ง€๊ฐ€ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ๋‹ค์Œ ํŽ˜์ด์ง€๋กœ ๋ณ€๊ฒฝํ•˜๊ณ , ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€๋ผ๋ฉด ์ฒซ ํŽ˜์ด์ง€๋กœ ๋ณ€๊ฒฝ

 

class MainActivity : AppCompatActivity() {

    ...

        // 3์ดˆ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ์Šฌ๋ผ์ด๋“œ
        timer(period = 3000){
            runOnUiThread {
                if(viewPager.currentItem < adapter.count-1){
                    viewPager.currentItem++
                } else{
                    viewPager.currentItem = 0
                }
            }
        }
    }
}

 

์ตœ์ข…

MainActivity.kt

package com.example.mygallery

import android.Manifest
import android.app.AlertDialog
import android.content.pm.PackageManager
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager
import kotlin.concurrent.timer

class MainActivity : AppCompatActivity() {

    private val REQUEST_READ_EXTERNAL_STORAGE = 1000
    lateinit var viewPager: ViewPager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewPager = findViewById(R.id.viewPager)

        val readIMagePermission = 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)){
                // ์ด์ „์— ๊ถŒํ•œ ๊ฑฐ๋ถ€ํ•œ ์  ์žˆ์œผ๋ฉด ์„ค๋ช…(๊ฒฝ๊ณ )
                var dlg = AlertDialog.Builder(this)
                dlg.setTitle("๊ถŒํ•œ์ด ํ•„์š”ํ•œ ์ด์œ ")
                dlg.setMessage("์‚ฌ์ง„ ์ •๋ณด๋ฅผ ์–ป๊ธฐ ์œ„ํ•ด์„œ๋Š” ์™ธ๋ถ€ ์ €์žฅ์†Œ ๊ถŒํ•œ์ด ํ•„์ˆ˜๋กœ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.")
                dlg.setPositiveButton("ํ™•์ธ"){dialog, which-> ActivityCompat.requestPermissions(this@MainActivity,
                                    arrayOf(readIMagePermission), REQUEST_READ_EXTERNAL_STORAGE)}
                dlg.setNegativeButton("์ทจ์†Œ", null)
                dlg.show()
            } else{
                // ์ฒ˜์Œ ๊ถŒํ•œ ์š”์ฒญ
                ActivityCompat.requestPermissions(this@MainActivity,
                    arrayOf(readIMagePermission), REQUEST_READ_EXTERNAL_STORAGE)
            } else{
                // ๊ถŒํ•œ์ด ์ด๋ฏธ ์ œ๋Œ€๋กœ ํ—ˆ์šฉ๋จ
                getAllPhotos()
        }
    }

    private fun getAllPhotos(){
        // ๋ชจ๋“  ์‚ฌ์ง„ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
        val cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                null,   // ๊ฐ€์ ธ์˜ฌ ํ•ญ๋ชฉ ๋ฐฐ์—ด
                null,      // ์กฐ๊ฑด
                null,   // ์กฐ๊ฑด
                MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC")    // ์ดฌ์˜ ๋‚ ์งœ ๋‚ด๋ฆผ์ฐจ์ˆœ

        val fragments = ArrayList<Fragment>()

        if(cursor != null){
            while(cursor.moveToNext()){
                // ์‚ฌ์ง„ ๊ฒฝ๋กœ Uri ๊ฐ€์ ธ์˜ค๊ธฐ
                val uri = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))
                Log.d("MainActivity", uri)
                fragments.add(PhotoFragment.newInstance(uri))
            }
            cursor.close()
        }

        // ์–ด๋Œ‘ํ„ฐ
        val adapter = MyPagerAdapter(supportFragmentManager)
        adapter.updateFragments(fragments)
        viewPager.adapter = adapter

        // 3์ดˆ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ์Šฌ๋ผ์ด๋“œ
        timer(period = 3000){
            runOnUiThread {
                if(viewPager.currentItem < adapter.count-1){
                    viewPager.currentItem++
                } else{
                    viewPager.currentItem = 0
                }
            }
        }
    }
}

 

PhotoFragment.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 android.widget.ImageView
import com.bumptech.glide.Glide

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_URI = "uri"

/**
 * A simple [Fragment] subclass.
 * Use the [PhotoFragment.newInstance] factory method to
 * create an instance of this fragment.
 */
class PhotoFragment : Fragment() {
    // TODO: Rename and change types of parameters
    private var uri: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            uri = it.getString(ARG_URI)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_photo, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        var imageView : ImageView = view.findViewById(R.id.imageView)
        Glide.with(this).load(uri).into(imageView)      // ๊ทธ๋ฆผ ์ถœ๋ ฅ
    }

    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment PhotoFragment.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(uri: String) =
            PhotoFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_URI, uri)
                }
            }
    }
}

 

MyPagerAdapter.kt

package com.example.mygallery

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter

class MyPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {

    // ๋ทฐํŽ˜์ด์ €๊ฐ€ ํ‘œ์‹œํ•  ํ”„๋ž˜๊ทธ๋จผํŠธ ๋ชฉ๋ก
    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)
    }
}

 

 

 

์ •๋ฆฌ

- ์ฝ˜ํ…์ธ  ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ธฐ๊ธฐ์— ์ €์žฅ๋œ ์‚ฌ์ง„ ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Œ

- ์œ„ํ—˜ํ•œ ๊ถŒํ•œ์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์•ฑ์„ ์‹คํ–‰ํ•˜๋Š” ์ค‘์— ํ•ด๋‹น ๊ถŒํ•œ์˜ ์‚ฌ์šฉ ํ—ˆ์šฉ์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ์š”์ฒญํ•ด์•ผ ํ•จ 

- ํ”„๋ž˜๊ทธ๋จผํŠธ๋Š” UI ์กฐ๊ฐ์ž…์œผ๋กœ ์•กํ‹ฐ๋น„ํ‹ฐ์—๋Š” ์—ฌ๋Ÿฌ ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ๊ณ  ์žฌ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅ

- ์ด๋ฏธ์ง€๋ฅผ ๋กœ๋”ฉํ•  ๋•Œ ๋ฉ”๋ชจ๋ฆฌ์™€ ์บ์‹œ ๊ด€๋ฆฌ ๋ฐ ์„ฑ๋Šฅ์„ ๊ณ ๋ คํ•ด Glide ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ 

- ๋ทฐํŽ˜์ด์ €๋Š” ์—ฌ๋Ÿฌ ํ”„๋ž˜๊ทธ๋จผํŠธ๋ฅผ ์ขŒ์šฐ๋กœ ์Šฌ๋ผ์ด๋“œํ•˜๋Š” ์–ด๋Œ‘ํ„ฐ ํŒจํ„ด์„ ๊ตฌํ˜„ํ•จ