안드로이드/앱개발(Android)

(코틀린 kotlin) 명언앱

김염인 2022. 2. 5. 23:21

aop-part3-chapter02

⚠️ 주의사항

Firebase 프로젝트 에서 다운받은 google-services.json 파일을 추가해야합니다.

목차

  1. 인트로, 프로젝트 셋업
  2. 기본 UI 구성하기
  3. Remote Config 소개
  4. Remote Config 구성하기
  5. Remote Config 연동하기
  6. 완성도 높이기

결과화면

 

  • Layout 설정

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />

</FrameLayout>

- ProgressBar는 다음 명언이 나올 때 까지 돌아갈 수 있기위해 설정해 주었다.

여기서 핵심 기능은 viewpager2를 추가한 것이다.

 

페이지를 넘기듯이 이렇게 슉-슉- 넘기는 것을 viewPager(뷰 페이저)라고 한다.

2019년에 구글이 viewPager2를 발표하면서 사용하기 굉장히 쉬워졌다.

그냥 리사이클러뷰 사용하듯이 사용하면 된다.

 

ViewPager2  |  Android 개발자  |  Android Developers

스와이프할 수 있는 형식으로 뷰 또는 프래그먼트를 표시합니다. 최근 업데이트 현재 안정화 버전 다음 버전 후보 베타 버전 알파 버전 2020년 4월 1일 1.0.0 - - 1.1.0-alpha01 AndroidX 종속 항목 ViewPager2

developer.android.com

자세한 변동사항은 위 공식문서에서 확인할 수 있다.

 


  • Firebase Remote Config 이용하기

Firebase 기본 설정과 같이 Google.json 파일을 app수준의 폴더에 넣어주고 build.gradle에

implementation platform('com.google.firebase:firebase-bom:29.0.3')
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation 'com.google.firebase:firebase-config-ktx'

세줄의 implementation을 추가 해준다. 그리고 firebase remote config의 매개변수를 추가해준다.

이곳이 Remote Config 구역이다. 이곳에서 매개변수 추가를 누르고

위와 같이 Json 형태로 Data를 넣어준다. 그리고 MainActivity에서 여기에 저장 돼있는 remote Config의 값을 아래와 같이 설정하면 가져 올 수 있다.

private fun initData() {
    val remoteConfig = Firebase.remoteConfig
    remoteConfig.setConfigSettingsAsync(
        remoteConfigSettings {
            minimumFetchIntervalInSeconds = 0
        }
    )
    remoteConfig.fetchAndActivate().addOnCompleteListener {
        progressBar.visibility = View.GONE // remoteConfig fectch가 완료 되면 사라지게 함, gone으로 사라지게 가능 !
        if (it.isSuccessful) {
            val quotes = parseQuotesJson(remoteConfig.getString("quotes"))
            val isNameRevealed = remoteConfig.getBoolean("is_name_revealed")
            displayQuotesPager(quotes, isNameRevealed)
        }
    }
}

개발용으로 시간을 단축시키는 설정이 적용됐다. remoteConfig.setConfigSettingsAsync() 로 비동기 세팅을 한다. minimumFectIntervalInSeconds 값을 0으로 줘서 delay없이 바로 fetch해 오게 된다.

그리고 jsonArray를 생성하고 jsonArray값들을 ArrayList로 parsing해주어야 한다. 아래의 방법이 JSONObject list에 하나씩 추가해준다.

private fun parseQuotesJson(json: String): List<Quote> {
    val jsonArray = JSONArray(json)
    var jsonList = emptyList<JSONObject>()
    for (index in 0 until jsonArray.length()) {
        val jsonObject = jsonArray.getJSONObject(index)
        jsonObject?.let {
            jsonList = jsonList + it
        }
    }

 

JSONArray로 가공 되기전(최초상태) -> 

[{"quote":"삶이 있는 한 희망은 있다 ","name":"키케로"},{"quote":"산다는것 그것은 치열한 전투이다","name":"로망로랑"},{"quote":"하루에 3시간을 걸으면 7년 후에 지구를 한바퀴 돌 수 있다.","name":"사무엘존슨"},{"quote":"피할수 없으면 즐겨라","name":"엘리엇"},{"quote":"먼저핀꽃은 먼저진다  남보다 먼저 공을 세우려고 조급히 서둘것이 아니다","name":"채근담"}] 

 

JSONArray로 가공 된 후(JSONArray)는 List형태가 아니기 때문에 map을 사용하지 못한다. ->

[{"quote":"삶이 있는 한 희망은 있다 ","name":"키케로"},{"quote":"산다는것 그것은 치열한 전투이다","name":"로망로랑"},{"quote":"하루에 3시간을 걸으면 7년 후에 지구를 한바퀴 돌 수 있다.","name":"사무엘존슨"},{"quote":"피할수 없으면 즐겨라","name":"엘리엇"},{"quote":"먼저핀꽃은 먼저진다  남보다 먼저 공을 세우려고 조급히 서둘것이 아니다","name":"채근담"}]

 

최종 가공(map 사용) ->

[Quote(quote=삶이 있는 한 희망은 있다 , name=키케로), Quote(quote=산다는것 그것은 치열한 전투이다, name=로망로랑), Quote(quote=하루에 3시간을 걸으면 7년 후에 지구를 한바퀴 돌 수 있다., name=사무엘존슨), Quote(quote=피할수 없으면 즐겨라, name=엘리엇), Quote(quote=먼저핀꽃은 먼저진다  남보다 먼저 공을 세우려고 조급히 서둘것이 아니다, name=채근담)]

 

그리고 최종 가공된 코드를 

private fun displayQuotesPager(quotes: List<Quote>, isNameRevealed: Boolean) {
        val adapter = QuotesPagerAdapter(
            quotes = quotes,
            isNameRevealed = isNameRevealed
        )
        viewPager.adapter = adapter
        viewPager.setCurrentItem(adapter.itemCount / 2, false)
    }
}

viewPager의 adapter에 넣어주면 최종 리스트로 저장된 최종 가공 형태가 adapter에 전달이 된다.

 

 


  • QuoterPagerAdapter 설정

Quote의 명언을 생성하는 과정은 firebase에 있는 명언 데이터를 가공하여 리스트로 가져와 리스트를 adapter를 통해 각각의 한장한장의 명언 카드를 만들어 mainView에 모두 뿌려 주는 과정을 가져야한다

즉 List -> Adapter -> 우리가 보는 최종 View 과정을 거쳐야 한다. 그러기 위해서는 QuoterPagerAdapter를 recyclerView로 생성을 해주면 된다.

 

QuoterPagerAdapter.kt

package com.example.aop_part3_chaptor02

import android.annotation.SuppressLint
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class QuotesPagerAdapter(
    private val quotes: List<Quote>,
    private val isNameRevealed: Boolean
) : RecyclerView.Adapter<QuotesPagerAdapter.QuoteViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuoteViewHolder {
        return QuoteViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_quote, parent, false))
    }

    override fun onBindViewHolder(holder: QuoteViewHolder, position: Int) {
        val actualPosition = position % quotes.size
        holder.bind(quotes[actualPosition], isNameRevealed)
    }

    override fun getItemCount() = Int.MAX_VALUE

    class QuoteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val quoteTextView: TextView = itemView.findViewById(R.id.quoteTextView)
        private val nameTextView: TextView = itemView.findViewById(R.id.nameTextView)

        @SuppressLint("SetTextI18n")
        fun bind(quote: Quote, isNameRevealed: Boolean) {
            quoteTextView.text = "\"${quote.quote}\""

            if(isNameRevealed) {
                nameTextView.text = "- ${quote.name}"
                nameTextView.visibility = View.VISIBLE
            } else {
                nameTextView.visibility = View.GONE
            }
        }
    }
}

각각의 뷰홀더에 명언들을 뿌려 준다. 그리고 ViewHolder에 최종 bind()를 통해 text와 view를 연결하여 묶어주는 역할을 한다.

 

  • 마무리
private fun initViews() { // 넘길때 점점 연해지도록 설정하는 initVeiws() 페이지전환효과
    viewPager.setPageTransformer { page, position ->
        when {
            position.absoluteValue >= 1F -> {
                page.alpha = 0F
            }
            position == 0F -> {
                page.alpha = 1F
            }
            else -> {
                page.alpha = 1F - 2 * position.absoluteValue // position.absoluteValue이 오른쪽이나 왼쪽으로 넘기면 0, 0.2, 0.5 -> 1 등으로 늘어남 !
            }
        }
    }
}

마지막으로 페이지를 넘길 때 색이 점점 연해지도록 설정해준다. 그러면 페이지 전환 효과가 적용이 된다.