Skip to content

๋ฆฌ๋“œ๋ฏธ ์–‘์‹ย #2

@hyooosong

Description

@hyooosong

๐ŸŒœ ๋ฏธ๋ผํด ๋ชจ๋‹์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋‹น์‹ ์˜ ์˜๋ฏธ์žˆ๋Š” ์•„์นจ, meaning ๐ŸŒ


๐Ÿ‘‡ meaning_Android ๐Ÿ‘‡



๐ŸŽด About US

์ง„์ˆ˜ ํšจ์†ก ํ˜•์ค€

Contact:[email protected]

GitHub:jinsu4755

Contact:[email protected]

GitHub:hyooosong

Contact: [email protected]

GitHub:LEE-HYUNGJUN


๐Ÿฆ‚์ง„์ˆ˜

- ํƒ€์ž„์Šคํ…œํ”„ ์นด๋ฉ”๋ผ
- ๋กœ๊ทธ์ธ
- ์˜จ๋ณด๋”ฉ
- ์„œ๋ฒ„ ์—ฐ๊ฒฐ ๋กœ์ง ๊ตฌํ˜„

๐ŸŽ… ํšจ์†ก

- ๊ทธ๋ฃน ํƒญ
- ์บ˜๋ฆฐ๋” ๋ทฐ
- sharedPreferences ์‹ฑ๊ธ€ํ†ค ๊ฐ์ฒด ๊ตฌํ˜„

๐Ÿ‘จ ๐Ÿ‘ง ํ˜•์ค€

- ํ™ˆ ๋ฉ”์ธํŽ˜์ด์ง€
- ์นด๋“œ ๋ทฐ ์• ๋‹ˆ๋ฉ”์ด์…˜
- ๋งˆ์ด ํ”ผ๋“œ, ๊ทธ๋ฃน ํ”ผ๋“œ
- ํ”ผ๋“œ ์ƒ์„ธ๋ณด๊ธฐ ๋ทฐ

๐Ÿ† Meeting Log

๐ŸŽด ๋ฏธ๋‹์–ธ์ฆˆ ์•ˆ๋“œ๋กœ๋ฏธ๋‹ ํšŒ์˜๋ก


๐Ÿ“ List

1. [Service]

2. [Andromeaning Development Environment]

3. [Work Flow]

4. [Dependencies]

5. [Team Role]

  • [Andromeaning Conventions]
  • [Andromeaning Coding Style]
  • [Code Review Guideline]
  • [Git]

6. [meaning Tech Stack]

7. [Packaging]

8. [Main Feature Codes & Methods]


๐Ÿ’ซ Service about meaning

๋ชจ๋“  ๊ฒƒ์€ ๋ฐ”๋€” ์ˆ˜ ์žˆ๊ณ  ๋‚˜ ์—ญ์‹œ ๋ฌด์–ธ๊ฐ€๋ฅผ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ์ƒ์‹œ๊ฐ„์ด ๋‹ฌ๋ผ์ง„๋‹ค๋ฉด, ๋‹น์‹ ๋„ ๋ณ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

โ€˜๋‚ดโ€™๊ฐ€ ๋ˆˆ ๋œจ๋Š” ์‹œ๊ฐ„์ด ์•„๋‹Œ, โ€˜ํ•ดโ€™๊ฐ€ ๋œจ๋Š” ์‹œ๊ฐ„๋ถ€ํ„ฐ ํ•˜๋ฃจ๋ฅผ ์‹œ์ž‘ํ•˜๋Š” ๋ฏธ๋ผํด ๋ชจ๋‹.

๋ฏธ๋‹์„ ํ†ตํ•ด ๋ฏธ๋ผํด ๋ชจ๋‹์— ๋„์ „ํ•˜๋ฉฐ ๋‹น์‹ ๋งŒ์˜ ์˜๋ฏธ์žˆ๋Š” ์•„์นจ์„ ๋งŒ๋“ค์–ด ๋‚˜๊ฐ€๋ณด์„ธ์š”.

์ผ์ฐ ์ผ์–ด๋‚˜๋Š” ์Šต๊ด€์œผ๋กœ ํ•˜๋ฃจ๋ฅผ ๊ธธ๊ฒŒ ๋ณด๋‚ด๋ฉด, ์„ฑ์žฅ์˜ ๋ฐœํŒ์„ ๋งˆ๋ จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฏธ๋‹๊ณผ ํ•จ๊ป˜ ์ฒด๊ณ„์ ์ธ ๊ณ„ํš์„ ์„ธ์šฐ๊ณ  ์ด๋ฅผ ๊ทœ์น™์ ์œผ๋กœ ์‹ค์ฒœํ•˜๋ฉด์„œ ์„ฑ์ทจ๊ฐ์„ ์–ป์–ด๋ณด์„ธ์š”.

์„ฑ์žฅ์ง€ํ–ฅ์ ์ธ ๊ทธ๋ฃน์›๊ณผ ๋ชฉํ‘œ๋ฅผ ๊ณต์œ ํ•œ๋‹ค๋ฉด ์šฐ๋ฆฌ๋Š” ํ•จ๊ป˜, ๋” ๋ฉ€๋ฆฌ ๊ฐˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.



๐Ÿ’ซ Development Environment

Android_Studio
Kotlin


๐Ÿ’ซ Work Flow

๐Ÿ’ซ Dependencies

Name Gradle
kotlin org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version
Android KTX implementation 'androidx.core:core-ktx:1.3.2
Design androidx.appcompat:appcompat:1.2.0
com.google.android.material:material:1.2.1
androidx.constraintlayout:constraintlayout:2.0.4
androidx.legacy:legacy-support-v4:1.0.0
viewModel init support androidx.activity:activity-ktx:1.1.0
androidx.fragment:fragment-ktx:1.2.5
LiveData and ViewModel (Arch components) androidx.lifecycle:lifecycle-livedata-ktx:2.2.0
androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0
retrofit com.squareup.retrofit2:retrofit:2.9.0
com.squareup.retrofit2:converter-gson:2.9.0
com.squareup.okhttp3:logging-interceptor:4.9.0
Gson com.google.code.gson:gson:2.8.6
CameraX core library using camera2 implementation androidx.camera:camera-core:$camerax_version
androidx.camera:camera-camera2:$camerax_version
CameraX Lifecycle Library androidx.camera:camera-lifecycle:$camerax_version
CameraX View class androidx.camera:camera-view:1.0.0-alpha20
Test junit:junit:4.13.1
androidx.test.ext:junit:1.1.2
androidx.test.espresso:espresso-core:3.3.0
image load com.github.bumptech.glide:glide:4.11.0
com.github.bumptech.glide:compiler:4.11.0
splash lottie com.airbnb.android:lottie:3.5.0

  • Material Design Component
    ๊ตฌ๊ธ€ Material Design์„ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌํ˜„์ฒด ์ œ๊ณต ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, UI์— ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  • Glide
    url ํ˜•์‹ ์ด๋ฏธ์ง€๋ฅผ ImageView์— ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  • AAC Lifecycle
    Live Data, Lifecycle, ViewModel ๊ณผ ๊ฐ™์€ ์ƒ๋ช…์ฃผ๊ธฐ์™€ ์—ฐ๋™๋œ ์ปดํฌ๋„ŒํŠธ๋“ค๊ณผ ํด๋ž˜์Šค ์ œ๊ณต

  • Coroutine
    ๋น„๋™๊ธฐ ์ž‘์—…์„ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ํƒ€์ž„์Šคํ…œํ”„ ์นด๋ฉ”๋ผ์—์„œ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์‹œ๊ฐ„์˜ ๋ณ€๊ฒฝ์„ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ.

  • Activity, Fragment ktx
    ViewModel์„ onCreate์—์„œ ์ดˆ๊ธฐํ™” ํ•˜๋Š”๊ฒฝ์šฐ ์—ฌ๋Ÿฌ๋ฒˆ ์ƒ์„ฑํ˜น์€ ์ƒํƒœ ์†์‹ค์„ ๋ง‰๊ธฐ ์œ„ํ•ด lazy delegate ์ž‘์—…์œผ๋กœ viewModel ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„์„œ ์‚ฌ์šฉ.

  • Retrofit
    ์•ˆ๋“œ๋กœ์ด๋“œ REST API ํ†ต์‹  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ. AsyncTask ์—†์ด Background Thread์—์„œ ์‹คํ–‰๋˜๋ฉฐ callback์„ ํ†ตํ•ด Main Thread์—์„œ์˜ UI ์—…๋ฐ์ดํŠธ๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œ๊ณต. ์„œ๋ฒ„ ํ†ต์‹ ์„ ์œ„ํ•ด ์‚ฌ์šฉ.

  • CameraX
    CameraX๋Š” ์นด๋ฉ”๋ผ ์•ฑ ๊ฐœ๋ฐœ์„ ๋” ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์–ด์ง„ Jetpack ์ง€์› ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, ํƒ€์ž„์Šคํ…œํ”„ ์นด๋ฉ”๋ผ ๋ถ€๋ถ„์—์„œ ์‚ฌ์šฉ.

  • Lottie
    Splash ๋ฐ Login ๋ฐฐ๊ฒฝ์œผ๋กœ ์‚ฌ์šฉ


๐Ÿ’ซ Team Role

  • ๐ŸŒฑ Andromeaning Conventions

  • ๐ŸŒฑ Andromeaning Coding Style

  • ๐ŸŒฑ Code Review Guideline

  • ๐ŸŒฑ Git

    • feat : ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€ํ•˜๊ธฐ
    • fix : ๋ฒ„๊ทธ ์ˆ˜์ •ํ•˜๋Š” ๊ฒฝ์šฐ
    • style : ์ƒ‰์ƒ ๋ณ€๊ฒฝ, ํฐํŠธ ๋ณ€๊ฒฝ ๋“ฑ์ด ์žˆ๋Š” ๊ฒฝ์šฐ
    • refactor : ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง ํ•˜๋Š” ๊ฒฝ์šฐ
    • upload : ํŒŒ์ผ ์ƒ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ
    • docs : ๋ฌธ์„œ ์ˆ˜์ •ํ•˜๋Š” ๊ฒฝ์šฐ


๐Ÿ’ซ meaning Tech Stack

MVC์™€ MVVM์˜ ํ˜ผํ•ฉ ์•„ํ‚คํ…์ฒ˜๋กœ ๊ฐœ๋ฐœ ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  • ** AAC DataBinding, ViewModel **
private lateinit var binding: ActivityLoginBinding
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = DataBindingUtil.setContentView(this, R.layout.activity_login)
    binding.viewModel = loginViewModel
    binding.lifecycleOwner = this
    initView()
}
private val loginViewModel: LoginViewModel by viewModels {
    object : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T =
            LoginViewModel(MeaningStorage.getInstance(this@LoginActivity)) as T
    }
}
  • Coroutine - ๋น„๋™๊ธฐ ์ž‘์—…

    fun runCurrentTimer() = viewModelScope.launch() {
        while (isEnableTimer) {
            _currentTime.value = SimpleDateFormat(TIME_FORMAT, Locale.KOREA)
                .format(System.currentTimeMillis())
            _currentDate.value = SimpleDateFormat(DATE_FORMAT, Locale.KOREA)
                .format(System.currentTimeMillis())
            delay(10000)
        }
    }
  • CameraX

    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
        cameraProviderFuture.addListener(
            cameraProvideFutureListener(cameraProviderFuture),
            getMainExecutor()
        )
    }
    private fun cameraProvideFutureListener(
        cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
    ) = Runnable {
        val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
        val preview = getCameraPreview()
        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
        setImageCapture()
        try {
            cameraProvider.unbindAll()
            cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
        } catch (failBindException: Exception) {
            Log.e(TAG, "Use case binding failed", failBindException)
        }
    }
    private fun getCameraPreview(): Preview = Preview.Builder()
        .build()
        .also {
            it.setSurfaceProvider(binding.cameraViewFinder.surfaceProvider)
        }
    private fun setImageCapture() {
        imageCapture = ImageCapture.Builder()
            .build()
    }
    private fun getMainExecutor() = ContextCompat.getMainExecutor(requireContext())
    private fun takePhoto() {
        val imageCapture = imageCapture ?: return
        imageCapture.takePicture(
            getMainExecutor(),
            getImageCapturedCallback()
        )
    }
    private fun getImageCapturedCallback(): TimeStampCameraCallback =
        TimeStampCameraCallback().apply {
            setOnCaptureSuccessListener { imageCaptureEvent(it) }
        }
    private fun imageCaptureEvent(image: Bitmap) {
        cameraViewModel.image = image
        cameraViewModel.isEnableTimer = false
        (requireActivity() as TimeStampCameraActivity).changeFragment(
            CameraResultFragment(),
            null
        )
    }

๐Ÿ’ซ Packaging

๐ŸŒ…meaning.morning
 โ”ฃ ๐Ÿ“‚data
 โ”ฃ ๐Ÿ“‚network
 โ”ƒ โ”ฃ ๐Ÿ“‚request
 โ”ƒ โ”ฃ ๐Ÿ“‚response
 โ”ฃ  ๐Ÿ“‚presentation
 โ”ƒ โ”ฃ ๐Ÿ“‚adapter
 โ”ƒ โ”ƒ โ”ฃ ๐Ÿ“‚feed
 โ”ƒ โ”ƒ โ”ฃ ๐Ÿ“‚group
 โ”ƒ โ”ƒ โ”ฃ ๐Ÿ“‚home
 โ”ƒ โ”ฃ ๐Ÿ“‚camera
 โ”ƒ โ”ฃ ๐Ÿ“‚group
 โ”ƒ โ”ƒ โ”ฃ ๐Ÿ“‚feed
 โ”ƒ โ”ฃ ๐Ÿ“‚home
 โ”ƒ โ”ƒ โ”ฃ ๐Ÿ“‚card
 โ”ƒ โ”ƒ โ”ฃ ๐Ÿ“‚feed
 โ”ƒ โ”ฃ ๐Ÿ“‚login
 โ”ƒ โ”— ๐Ÿ“‚onboarding
 โ”—๐Ÿ“‚utils

๐Ÿ’ซ Main Feature Codes & Methods

โœ” sharedPreference ์‹ฑ๊ธ€ํ„ด ์ž‘์„ฑ

object๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ž‘์„ฑํ•˜๊ธฐ.

Multi-Thread Safeํ•˜๋„๋ก ๋งŒ๋“ค๊ธฐ.

SharedPreference์ง€๋งŒ ๋ณด๋‹ค ์ง๊ด€์ ์ธ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜๊ธฐ.

class MeaningStorage(context: Context) {
	/* ... */
    companion object {
        private var instance: MeaningStorage? = null
        
        fun getInstance(context: Context) = instance ?: synchronized(this) {
            instance ?: MeaningStorage(context).apply {
                instance = this
            }
        }
    }
}

โœ” TimeStamp Camera

imageimageimage

  • Camera Permission
    private fun initTimeStampCamera() {
        if (allPermissionGranted()) {
            loadCameraView()
            return
        }
        requestPermission()
    }

    private fun allPermissionGranted() = REQUIRED_PERMISSIONS.all {
        ContextCompat.checkSelfPermission(
            applicationContext,
            it
        ) == PackageManager.PERMISSION_GRANTED
    }

    private fun requestPermission() {
        ActivityCompat.requestPermissions(
            this,
            REQUIRED_PERMISSIONS,
            CameraViewModel.REQUEST_CODE_PERMISSIONS
        )
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        if (requestCode == CameraViewModel.REQUEST_CODE_PERMISSIONS) {
            permissionResponseEvent()
        }
    }

    private fun permissionResponseEvent() {
        if (allPermissionGranted()) {
            loadCameraView()
            return
        }
        permissionDeniedEvent()
    }

    private fun permissionDeniedEvent() {
        showToast("๊ถŒํ•œ์„ ์Šน์ธํ•˜์ง€ ์•Š์œผ๋ฉด ๋‹น์‹ ์˜ ๋ฏธ๋ผํด ๋ชจ๋‹์„ ๊ธฐ๋กํ•  ์ˆ˜ ์—†์–ด์š”!")
        finish()
    }

    private fun loadCameraView() {
        changeFragment(CameraFragment())
    }

    private fun changeFragment(initFragment: Fragment) {
        val transaction = supportFragmentManager.beginTransaction()
        transaction.apply {
            replace(R.id.fragment_camera, initFragment)
            commit()
        }
    }
    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
        cameraProviderFuture.addListener(
            cameraProvideFutureListener(cameraProviderFuture),
            getMainExecutor()
        )
    }

    private fun cameraProvideFutureListener(
        cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
    ) = Runnable {
        val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
        val preview = getCameraPreview()
        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
        setImageCapture()
        try {
            cameraProvider.unbindAll()
            cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
        } catch (failBindException: Exception) {
            Log.e(TAG, "Use case binding failed", failBindException)
        }
    }

    private fun getCameraPreview(): Preview = Preview.Builder()
        .build()
        .also {
            it.setSurfaceProvider(binding.cameraViewFinder.surfaceProvider)
        }

    private fun setImageCapture() {
        imageCapture = ImageCapture.Builder()
            .build()
    }

    private fun getMainExecutor() = ContextCompat.getMainExecutor(requireContext())

    private fun takePhoto() {
        val imageCapture = imageCapture ?: return
        imageCapture.takePicture(
            getMainExecutor(),
            getImageCapturedCallback()
        )
    }

    private fun getImageCapturedCallback(): TimeStampCameraCallback =
        TimeStampCameraCallback().apply {
            setOnCaptureSuccessListener { imageCaptureEvent(it) }
        }

    private fun imageCaptureEvent(image: Bitmap) {
        cameraViewModel.image = image
        cameraViewModel.isEnableTimer = false
        /* ... */
    }

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋งŒ๋“ค์–ด์ง„ ์นด๋ฉ”๋ผ๋ฅผ ๋ทฐ๋ชจ๋ธ์— ์ €์žฅํ•˜์—ฌ ๊ฒฐ๊ณผ ์ฐฝ์œผ๋กœ ๋„˜๊ธฐ๊ณ  ๊ฒฐ๊ณผ์ฐฝ์—์„œ๋Š” ํ•ด๋‹น ๋ทฐ๋ฅผ Bitmap์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ €์žฅํ•œ๋‹ค.

class TimeStampImageCreator(private val context: Context) {
    /* ... */
    fun saveOf(viewGroup: ConstraintLayout) {
        val width = viewGroup.width
        val height = viewGroup.height
        removeViewEvent(viewGroup)
        val bitmapBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmapBuffer)
        viewGroup.draw(canvas)
        saveImage(bitmapBuffer)
    }
    
    private fun removeViewEvent(viewGroup: ConstraintLayout) {
        viewGroup.apply {
            clearFocus()
            isPressed = false
            invalidate()
        }
    }
    private fun getOutputDirectory(): File {
        val mediaDir = context.externalMediaDirs.firstOrNull()?.let {
            File(it, context.resources.getString(R.string.app_name)).apply {
                mkdirs()
            }
        }
        return if (mediaDir != null && mediaDir.exists()) mediaDir else context.filesDir
    }

    private fun saveImage(bitmapBuffer: Bitmap) {
        photo = getPhotoFile()
        try {
            val outputStream = FileOutputStream(photo)
            bitmapBuffer.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
            outputStream.close()
            galleryAddPicture()
        } catch (errorMessage: FileNotFoundException) {
            errorMessage.stackTrace
        } catch (errorMessage: IOException) {
            errorMessage.stackTrace
        } finally {
            bitmapBuffer.recycle()
        }
    }

    private fun getPhotoFile() = File(
        getOutputDirectory(),
        SimpleDateFormat(
            "yyyy-MM-dd HH:mm:ss",
            Locale.KOREA
        ).format(System.currentTimeMillis()) + ".jpeg"
    )
}

๋งŒ๋“  ํŒŒ์ผ์€ ๊ธ€์“ฐ๊ธฐ ํ™”๋ฉด์œผ๋กœ ๋„˜๊ธด๋‹ค.

โœ” MyFeedPictureAdapter

์•„์ดํ…œ ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๋ถ„๋ฆฌ.

class MyFeedPictureAdapter : RecyclerView.Adapter<MyFeedPictureAdapter.MyFeedPictureViewHolder>() {
    var data = mutableListOf<MyFeedPictureData>()
    private lateinit var itemClickListener : ItemClickListener
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyFeedPictureViewHolder {
        val binding = FeedItemListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return MyFeedPictureViewHolder(binding)
    }
    override fun getItemCount(): Int {
        return data.size
    }
    override fun onBindViewHolder(holder: MyFeedPictureViewHolder, position: Int) {
        holder.onBind(data[position])
        holder.itemView.setOnClickListener {
            itemClickListener.onClick(it,position)
        }
    }
    fun submitData(list : List<MyFeedPictureData>){
        data.addAll(list)
        notifyDataSetChanged()
    }
    class MyFeedPictureViewHolder(val binding: FeedItemListBinding) : RecyclerView.ViewHolder(binding.root) {
        fun onBind(data: MyFeedPictureData) {
            binding.feedItemList = data
        }
    }
    interface ItemClickListener{
        fun onClick(view : View, position: Int)
    }
    fun setItemClickListener(itemClickListener: ItemClickListener){
        this.itemClickListener = itemClickListener
    }
}

๐Ÿ’ซ Layout ๊ด€๋ จ

  • Layout ์‚ฌ์šฉ

    ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์œผ๋กœ ์‚ฌ์šฉ์œผ๋กœ ๋ชจ๋“  ๋ทฐ์˜ ์ตœ์ƒ์œ„๊ฐ€ Layout ํƒœ๊ทธ ์•„๋ž˜ ์žˆ์Œ

  • coordinatorlayout, NestedScrollView ์‚ฌ์šฉ
    ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๋ฐœ์ƒ์‹œ behavior๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ทฐ์˜ ๋ณ€๊ฒฝ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉ.

    - fragment_group.xml
    - activity_my_feed_main.xml
    - activity_group_settting.xml
    
  • ๋‹จ์ˆœ ๋„ํ˜• ์—์…‹ - ์บ˜๋ฆฐ๋” ๋ทฐ ์•„๋ž˜ ์›

image

radius ํ™•์ธ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜์—ฌ ๋””์ž์ด๋„ˆ์—๊ฒŒ ์š”์ฒญํ›„ ์—์…‹์œผ๋กœ ๋ฐ›๊ธฐ๋กœํ•จ

- HomeFragment
  • ์ ˆ๋Œ€ ํฌ๊ธฐ ์ง€์ •

    - feed_item_list.xml
    - dialog_group_recycler.xml
    - dialog_group_detail.xml
    - fragment_home.xml
    
    • feed_item_list : ํ”ผ๋“œ ์•„์ดํ…œ์œผ๋กœ ๋“ค์–ด์˜ฌ ์‚ฌ์ง„ ํฌ๊ธฐ๊ฐ€ ๊ธฐ๊ธฐ๋ณ„๋กœ ๋‹ค๋ฅผ ๊ฒฝ์šฐ๋ฅผ ๋”ฐ๋ผ ์ ˆ๋Œ€ ํฌ๊ธฐ ์ง€์ •
    • dialog : ํ™”๋ฉด ๋น„์œจ์— ๋”ฐ๋ผ๊ฐ€ ์•„๋‹Œ ๋‹ค์ด์–ผ๋กœ๊ทธ ์ฐฝ์˜ ํฌ๊ธฐ ๊ณ ์ •์„ ์œ„ํ•ด์„œ ์‚ฌ์šฉ
    • fragement_home.xml : > ๋ชจ์–‘ ์—์…‹ ํฌ๊ธฐ๊ฐ€ ๋„ˆ๋ฌด ์ž‘๋‹ค๋Š” ์š”์ฒญ์— ์ ˆ๋Œ€ํฌ๊ธฐ๋กœ ์•ฝ๊ฐ„ ํฌ๊ธฐ ์ฆ๊ฐ€ ์ง€์ •.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions