728x90
반응형
각 가게들에 별점을 메기고 이에 따라서 가게를 추천해주는 기능을 만들 필요가 생겨서 만들게 되었습니다.
이 글을 안드로이드의 데이터 바인딩, 리사이클러뷰, 레트로핏2를 이미 숙지하셨다는 가정 하에 작성되었습니다.
각 버튼들을 원하는 이미지 혹은 색으로 만들 수 있으며 클릭시 전체 버튼의 색이 변하는 레이아웃입니다.
평가 액티비티의 레이아웃입니다.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="evaluate"
type="com.connple.weat.navigation.review.EvaluateViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/activity_evaluate_button_back"
android:layout_width="24dp"
android:layout_height="22dp"
android:src="@drawable/back_01"
android:background="?attr/selectableItemBackgroundBorderless"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginStart="@dimen/app_margin_start"
android:layout_marginTop="@dimen/app_margin_top" />
<ImageButton
android:id="@+id/activity_evaluate_button_skip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_marginEnd="@dimen/app_margin_end"
android:layout_marginTop="@dimen/app_margin_top"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:src="@drawable/ic_search_01"
android:textStyle="bold"
android:textColor="@color/weat" />
<TextView
android:id="@+id/activity_evaluate_tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/location"
android:includeFontPadding="false"
android:textSize="20sp"
android:layout_marginTop="@dimen/app_margin_top"
android:layout_marginStart="20dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toRightOf="@id/activity_evaluate_button_back"
style="@style/BoldText" />
<ImageView
android:layout_width="26dp"
android:layout_height="24dp"
app:layout_constraintLeft_toRightOf="@+id/activity_evaluate_tv1"
android:layout_marginTop="@dimen/app_margin_top"
app:layout_constraintTop_toTopOf="parent"
android:background="@drawable/ic_see_more_01"
android:backgroundTint="@color/gray" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/activity_evaluate_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
app:layout_constraintTop_toBottomOf="@+id/activity_evaluate_tv1"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintLeft_toLeftOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
그리고 액티비티의 리사이클러뷰에서 사용할 아이템의 레이아웃입니다.
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="evaluate"
type="com.connple.weat.navigation.review.EvaluateViewModel" />
<variable
name="pos"
type="Integer" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/item_evaluate_tv1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:text="@{evaluate.restaurants[pos].name}"
android:layout_marginStart="20dp"
android:textSize="18sp" />
<TextView
android:id="@+id/item_evaluate_tv2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/item_evaluate_tv1"
android:text="@{evaluate.restaurants[pos].address}"
android:layout_marginStart="20dp"
android:textSize="12sp" />
<ImageView
android:id="@+id/item_evaluate_image1"
android:layout_width="35dp"
android:layout_height="40dp"
android:layout_marginHorizontal="10dp"
android:layout_marginStart="20dp"
app:layout_constraintTop_toBottomOf="@+id/item_evaluate_tv2"
android:background="@drawable/ic_fill"
android:backgroundTint="@color/gray"
app:layout_constraintLeft_toLeftOf="parent" />
<ImageView
android:id="@+id/item_evaluate_image2"
android:layout_width="35dp"
android:layout_height="40dp"
android:background="@drawable/ic_fill"
android:layout_marginHorizontal="10dp"
android:backgroundTint="@color/gray"
app:layout_constraintTop_toBottomOf="@+id/item_evaluate_tv2"
app:layout_constraintLeft_toRightOf="@+id/item_evaluate_image1" />
<ImageView
android:id="@+id/item_evaluate_image3"
android:layout_width="35dp"
android:layout_height="40dp"
android:background="@drawable/ic_fill"
android:layout_marginHorizontal="10dp"
android:backgroundTint="@color/gray"
app:layout_constraintTop_toBottomOf="@+id/item_evaluate_tv2"
app:layout_constraintLeft_toRightOf="@+id/item_evaluate_image2" />
<ImageView
android:id="@+id/item_evaluate_image4"
android:layout_width="35dp"
android:layout_height="40dp"
android:layout_marginHorizontal="10dp"
android:background="@drawable/ic_fill"
android:backgroundTint="@color/gray"
app:layout_constraintTop_toBottomOf="@+id/item_evaluate_tv2"
app:layout_constraintLeft_toRightOf="@+id/item_evaluate_image3" />
<ImageView
android:id="@+id/item_evaluate_image5"
android:layout_width="35dp"
android:layout_height="40dp"
android:background="@drawable/ic_fill"
android:backgroundTint="@color/gray"
android:layout_marginHorizontal="10dp"
app:layout_constraintTop_toBottomOf="@+id/item_evaluate_tv2"
app:layout_constraintLeft_toRightOf="@+id/item_evaluate_image4" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="2dp"
app:layout_constraintTop_toBottomOf="@+id/item_evaluate_image1"
android:layout_marginHorizontal="20dp"
android:layout_marginTop="10dp"
android:background="@color/gray" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
레이아웃을 모두 구성하였으면 액티비티를 구성합시다.
뷰를 보여줄 액티비티 입니다.
package com.connple.weat.navigation.review
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import com.connple.weat.R
import com.connple.weat.databinding.ActivityEvaluate1Binding
import com.connple.weat.databinding.ActivityEvaluateBinding
import com.connple.weat.view.adapters.EvaluateAdapter
import kotlinx.android.synthetic.main.activity_evaluate.*
class EvaluateActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityEvaluateBinding =
DataBindingUtil.setContentView(this, R.layout.activity_evaluate)
val viewModel = ViewModelProvider(this).get(EvaluateViewModel::class.java)
val recyclerView = activity_evaluate_recycler
binding.evaluate = viewModel
EvaluateAdapter.viewModel = viewModel
viewModel.setRestaurants(this)
viewModel.updateUi(recyclerView, this)
}
}
뷰의 ui업데이트와 데이터 저장하고 가져올 뷰 모델 입니다.
package com.connple.weat.navigation.review
import android.content.Context
import android.view.View
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import androidx.recyclerview.widget.RecyclerView
import com.connple.weat.api.Weat
import com.connple.weat.api.model.Restaurant
import com.connple.weat.utils.abstracts.AppPreferenceManager
import com.connple.weat.view.adapters.EvaluateAdapter
import kotlinx.coroutines.*
class EvaluateViewModel : ViewModel() {
val restaurants: MutableLiveData<ArrayList<Restaurant>> = MutableLiveData()
lateinit var view: View
fun setRestaurants(context: Context) {
GlobalScope.launch(Dispatchers.IO) {
val responseRegion =
Weat.region.index(name = AppPreferenceManager.getString(context, "location"))
.execute()
try {
val response =
Weat.region.indexRestaurants(responseRegion.body()?.get(0)?.id?.toInt())
.execute()
coroutineScope {
withContext(Dispatchers.Main) {
restaurants.value = response.body() as ArrayList
}
}
} catch (e: Exception) {
e.printStackTrace()
println("레스토랑이 없음")
}
}
}
fun updateUi(recyclerView: RecyclerView, life: LifecycleOwner) {
restaurants.observe(life, Observer {
try {
EvaluateAdapter.itemSize = restaurants.value!!.size
} catch (e: java.lang.Exception) {
println("레스토랑이 없음")
}
println("getRestaurants() = ${getRestaurants()}")
recyclerView.adapter = EvaluateAdapter()
})
}
fun getRestaurants(): ArrayList<Restaurant> {
return restaurants.value ?: arrayListOf()
}
}
리사이클러뷰의 어뎁터 코드입니다.
package com.connple.weat.view.adapters
import android.annotation.SuppressLint
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import com.connple.weat.R
import com.connple.weat.databinding.ItemEvaluateBinding
import com.connple.weat.navigation.review.EvaluateViewModel
import kotlinx.android.synthetic.main.item_evaluate.view.*
class EvaluateAdapter() :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
var itemSize = 0
lateinit var viewModel: EvaluateViewModel
}
var ratingArray: Array<Int> = Array(itemSize) { 0 }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val binding: ItemEvaluateBinding =
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.item_evaluate,
parent,
false
)
return EvaluateViewHolder(binding)
}
inner class EvaluateViewHolder(val binding: ItemEvaluateBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(position: Int) {
binding.evaluate = viewModel
binding.pos = position
binding.executePendingBindings()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.setIsRecyclable(false)
val binding = holder as EvaluateViewHolder
binding.bind(position)
val view = binding.itemView
when {
ratingArray[position] == 1 -> setImage1(view, position)
ratingArray[position] == 2 -> setImage2(view, position)
ratingArray[position] == 3 -> setImage3(view, position)
ratingArray[position] == 4 -> setImage4(view, position)
ratingArray[position] == 5 -> setImage5(view, position)
}
view.item_evaluate_image1.setOnClickListener(onClickImageButtonListener1(view, position))
view.item_evaluate_image2.setOnClickListener(onClickImageButtonListener2(view, position))
view.item_evaluate_image3.setOnClickListener(onClickImageButtonListener3(view, position))
view.item_evaluate_image4.setOnClickListener(onClickImageButtonListener4(view, position))
view.item_evaluate_image5.setOnClickListener(onClickImageButtonListener5(view, position))
}
@SuppressLint("ResourceAsColor")
private fun onClickImageButtonListener1(view: View, position: Int) = View.OnClickListener {
setImage1(view, position)
}
fun setImage1(view: View, position: Int) {
ratingArray[position] = 1
view.item_evaluate_image1.background.setTint(Color.parseColor("#FFCF10"))
view.item_evaluate_image2.background.setTint(Color.parseColor("#CCCCCC"))
view.item_evaluate_image3.background.setTint(Color.parseColor("#CCCCCC"))
view.item_evaluate_image4.background.setTint(Color.parseColor("#CCCCCC"))
view.item_evaluate_image5.background.setTint(Color.parseColor("#CCCCCC"))
}
@SuppressLint("ResourceAsColor")
private fun onClickImageButtonListener2(view: View, position: Int) = View.OnClickListener {
setImage2(view, position)
}
private fun setImage2(view: View, position: Int) {
ratingArray[position] = 2
view.item_evaluate_image1.background.setTint(Color.parseColor("#FF8820"))
view.item_evaluate_image2.background.setTint(Color.parseColor("#FF8820"))
view.item_evaluate_image3.background.setTint(Color.parseColor("#CCCCCC"))
view.item_evaluate_image4.background.setTint(Color.parseColor("#CCCCCC"))
view.item_evaluate_image5.background.setTint(Color.parseColor("#CCCCCC"))
}
@SuppressLint("ResourceAsColor")
private fun onClickImageButtonListener3(view: View, position: Int) = View.OnClickListener {
setImage3(view, position)
}
private fun setImage3(view: View, position: Int) {
ratingArray[position] = 3
view.item_evaluate_image1.background.setTint(Color.parseColor("#FE7973"))
view.item_evaluate_image2.background.setTint(Color.parseColor("#FE7973"))
view.item_evaluate_image3.background.setTint(Color.parseColor("#FE7973"))
view.item_evaluate_image4.background.setTint(Color.parseColor("#CCCCCC"))
view.item_evaluate_image5.background.setTint(Color.parseColor("#CCCCCC"))
}
@SuppressLint("ResourceAsColor")
private fun onClickImageButtonListener4(view: View, position: Int) = View.OnClickListener {
setImage4(view, position)
}
private fun setImage4(view: View, position: Int) {
ratingArray[position] = 4
view.item_evaluate_image1.background.setTint(Color.parseColor("#F35DBC"))
view.item_evaluate_image2.background.setTint(Color.parseColor("#F35DBC"))
view.item_evaluate_image3.background.setTint(Color.parseColor("#F35DBC"))
view.item_evaluate_image4.background.setTint(Color.parseColor("#F35DBC"))
view.item_evaluate_image5.background.setTint(Color.parseColor("#CCCCCC"))
}
@SuppressLint("ResourceAsColor")
private fun onClickImageButtonListener5(view: View, position: Int) = View.OnClickListener {
setImage5(view, position)
}
private fun setImage5(view: View, position: Int) {
ratingArray[position] = 5
view.item_evaluate_image1.background.setTint(Color.parseColor("#9943fc"))
view.item_evaluate_image2.background.setTint(Color.parseColor("#9943fc"))
view.item_evaluate_image3.background.setTint(Color.parseColor("#9943fc"))
view.item_evaluate_image4.background.setTint(Color.parseColor("#9943fc"))
view.item_evaluate_image5.background.setTint(Color.parseColor("#9943fc"))
}
override fun getItemCount(): Int {
return itemSize
}
}
각 버튼이 클릭될 때 전부 색이 바뀌게 만들었으며 조금 더 고민하면 코드량을 줄일 수 있을 것 같습니다...
1. 액티비티와 뷰 모델을 생성한 후 레이아웃을 구성합니다.
2. 리사이클러 뷰에서 보여줄 식당의 목록을 레트로핏을 통해 가져옵니다.(임의 구현이시라면 더미 데이터를 데이터 클래스로 만드시는 것을 추천드립니다.)
3. 데이터를 가져옴과 동시에 식당 리스트가 초기화 되고 MutableLiveData에 의해서 리사이클러 뷰가 업데이트 됩니다.
4. 각 식당의 목록이 뷰에 표시됩니다.
레트로핏(http 통신 라이브러리)은 위의 글에서 다루고자 하는 범주가 아니므로 코드를 게시하지 않았습니다.
레트로핏에 대한 사용 방법은 아래 게시글을 참고해주시기 바랍니다.
728x90
반응형
'App > 개발' 카테고리의 다른 글
[안드로이드] 비트맵 파일을 파일 타입으로 변환하기 (bitmap to file) (1) | 2020.11.19 |
---|---|
[안드로이드] 이용약관에 따른 버튼 활성화/ 비활성화 및 휴대폰 번호 입력 확인 받는 레이아웃 만들기 (0) | 2020.11.16 |
[안드로이드] 세개 버튼으로 이루어진 애니메이션 팝업 버튼 만들기 (0) | 2020.10.15 |
[안드로이드] 6자리 블록으로 만들어진 인증번호 입력 만들기(자동 넘김, 자동 지움) (0) | 2020.10.13 |
[안드로이드] 뷰페이저 페이지 변경될 때 레이아웃 적용하기 (0) | 2020.09.20 |