Apple Market (1) 에 이어서 메인 페이지의 나머지 기능과 상세 페이지를 구현 하였다.
과제 목표 (메인 페이지)
[v]상품 선택시 아래 상품 상세 페이지로 이동합니다.
[v] 상품 상세페이지 이동시 intent로 객체를 전달합니다. (Parcelize 사용)
(상세 페이지)
[v] 디자인 및 화면 구성을 최대한 동일하게 해주세요. (사이즈 및 여백도 최대한 맞춰주세요.) ✨
[v] 메인화면에서 전달받은 데이터로 판매자, 주소, 아이템, 글내용, 가격등을 화면에 표시합니다.
[v] 하단 가격표시 레이아웃을 제외하고 전체화면은 스크롤이 되어야합니다. (예시 비디오 참고)
[v] 상단 < 버튼을 누르면 상세 화면은 종료되고 메인화면으로 돌아갑니다.
@Parcelize
data class ProductEntity(
val id: Int?,
val resId: Int?,
val name: String?,
val explain: String?,
val seller: String?,
val price: Int?,
val location: String?,
val like: Int?,
val chat: Int?
) : Parcelable
class ProductAdapter(private val items: MutableList<ProductEntity>) :
RecyclerView.Adapter<ProductAdapter.Holder>() {
interface ItemClick {
fun onItemClick(view: View, position: Int)
}
var itemClick: ItemClick? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductAdapter.Holder {
val binding =
ItemProductRecyclerBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return Holder(binding)
}
override fun onBindViewHolder(holder: ProductAdapter.Holder, position: Int) {
holder.itemView.setOnClickListener {
itemClick?.onItemClick(it, position)
}
items[position].resId?.let { holder.productImageView.setImageResource(it) }
holder.productImageView.clipToOutline = true
holder.productName.text = items[position].name
holder.productLocation.text = items[position].location
val price = "${items[position].price?.let { it.decimalFormat() }}원"
holder.productPrice.text = price
val chatCount = "${items[position].chat}"
val likeCount = "${items[position].like}"
holder.productChatCount.text = chatCount
holder.productLikeCount.text = likeCount
}
override fun getItemCount(): Int = items.size
inner class Holder(binding: ItemProductRecyclerBinding) :
RecyclerView.ViewHolder(binding.root) {
val productImageView = binding.ivProduct
val productName = binding.tvProductName
val productLocation = binding.tvProductLocation
val productPrice = binding.tvProductPrice
val productChatCount = binding.tvChatCount
val productLikeCount = binding.tvLikeCount
}
}
object ProductManager {
private var items: MutableList<ProductEntity> = arrayListOf()
fun Context.loadList(): MutableList<ProductEntity> {
val assetManager = assets
val inputStream = assetManager.open("dummy_data.tsv")
val bufferedReader = BufferedReader(InputStreamReader(inputStream))
bufferedReader.forEachLine {
val tokens = it.split("\t")
val resource = resources.getIdentifier(tokens[1], "drawable", packageName)
val post = ProductEntity(
tokens[0].toInt(),
resource,
tokens[2],
tokens[3].replace("\\n", "\n").replace(" + ", "").replace("\"", ""),
tokens[4],
tokens[5].toInt(),
tokens[6],
tokens[7].toInt(),
tokens[8].toInt()
)
items.add(post)
}
return items
}
}
decimalFormat 함수를 원래는 메인 액티비티에 추가 했는데 디테일 액티비티에서도 사용할거라 obejct로 따로 생성하였다.
object ProductObject {
fun Int.decimalFormat(): String { // 숫자 세 자리 마다 쉼표 출력
val dec = DecimalFormat("#,###")
return dec.format(this)
}
}
class MainActivity : AppCompatActivity() {
companion object {
const val EXTRA_PRODUCT_ENTITY = "extra_product_entity"
const val NOTIFICATION_CHANNEL_ID = "one-channel"
const val NOTIFICATION_ID = 11
}
private val binding: ActivityMainBinding by lazy {
ActivityMainBinding.inflate(layoutInflater)
}
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
showAlertDialog()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
initView()
this.onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
}
private fun initView() {
setSpinnerLocation()
initRecyclerView()
binding.ivNotification.setOnClickListener {
showNotification()
}
}
private fun initRecyclerView() {
val items = loadList()
val adapter = ProductAdapter(items)
adapter.itemClick = object : ProductAdapter.ItemClick {
override fun onItemClick(view: View, position: Int) {
val intent = Intent(this@MainActivity, DetailActivity::class.java)
val data = ProductEntity(
items[position].id,
items[position].resId,
items[position].name,
items[position].explain,
items[position].seller,
items[position].price,
items[position].location,
items[position].like,
items[position].chat
)
intent.putExtra(EXTRA_PRODUCT_ENTITY, data)
startActivity(intent)
}
}
binding.productRecyclerView.run {
layoutManager = LinearLayoutManager(this@MainActivity)
addItemDecoration(
DividerItemDecoration(
this@MainActivity,
LinearLayoutManager.VERTICAL
)
)
this.adapter = adapter
}
}
private fun showAlertDialog() {
android.app.AlertDialog.Builder(this@MainActivity).apply {
setTitle(getString(R.string.dialog_title))
setMessage(getString(R.string.dialog_message))
setIcon(R.drawable.img_main_chat_16dp)
setPositiveButton(getString(R.string.dialog_button_positive)) { _, _ -> finish() }
setNegativeButton(getString(R.string.dialog_button_negative), null)
}.show()
}
private fun setSpinnerLocation() {
binding.spinnerLocation.adapter = ArrayAdapter(
this@MainActivity,
android.R.layout.simple_spinner_dropdown_item,
listOf(
getString(R.string.location_0)
)
)
}
private fun showNotification() {
createNotificationChannel()
val builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID).apply {
setSmallIcon(R.mipmap.ic_launcher)
setWhen(System.currentTimeMillis())
setContentTitle(getString(R.string.notification_title))
setContentText(getString(R.string.notification_message))
}
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.notify(NOTIFICATION_ID, builder.build())
}
private fun createNotificationChannel() {
val channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
getString(R.string.notification_channel_name),
NotificationManager.IMPORTANCE_DEFAULT
).apply {
description = getString(R.string.notification_channel_description)
enableVibration(true)
}
val uri: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val audioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ALARM)
.build()
channel.setSound(uri, audioAttributes)
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
}
}
paintFlags 함수를 사용하여 텍스트 밑줄 추가하기
binding.tvMannersTemperature.paintFlags = Paint.UNDERLINE_TEXT_FLAG
class DetailActivity : AppCompatActivity() {
private val binding: ActivityDetailBinding by lazy {
ActivityDetailBinding.inflate(layoutInflater)
}
private val productEntity: ProductEntity? by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent?.getParcelableExtra(
EXTRA_PRODUCT_ENTITY, ProductEntity::class.java
)
} else {
intent?.getParcelableExtra(
EXTRA_PRODUCT_ENTITY
)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
initView()
}
private fun initView() {
setUnderLineText()
setProductEntity()
binding.ivBackwards.setOnClickListener {
finish()
}
}
private fun setUnderLineText() {
binding.tvMannersTemperature.paintFlags = Paint.UNDERLINE_TEXT_FLAG // 텍스트 밑줄
}
private fun setProductEntity() {
productEntity?.resId?.let { binding.ivDetailImage.setImageResource(it) }
binding.tvDetailName.text = productEntity?.name
binding.tvDetailExplain.text = productEntity?.explain
binding.tvDetailSeller.text = productEntity?.seller
val price = "${productEntity?.price?.decimalFormat()}원"
binding.tvDatailPrice.text = price
binding.tvDetailLocation.text = productEntity?.location
}
}
줄 간격 설정하기
android:lineSpacingExtra="6dp"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DetailActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/linearLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical">
<ImageView
android:id="@+id/ivDetailImage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:src="@drawable/sample8" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="horizontal">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/img_detail_usericon_48dp" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_weight="2"
android:orientation="vertical">
<TextView
android:id="@+id/tvDetailSeller"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="3dp"
android:text="난쉽"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvDetailLocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="동래구 온천제2동"
android:textColor="@color/동래구온천제2동" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|end"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:text="@string/hot"
android:textColor="@color/고열"
android:textSize="16sp"
android:textStyle="bold" />
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/img_detail_status_32dp" />
</LinearLayout>
<TextView
android:id="@+id/tvMannersTemperature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="@string/text_manners_temperature"
android:textColor="@color/이모지아래" />
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/칸나눔" />
<TextView
android:id="@+id/tvDetailName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="샤넬 탑핸들 가방"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvDetailExplain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:lineSpacingExtra="6dp"
android:text="샤넬 트랜디 CC 탑핸들 스몰 램스킨 블랙 금장 플랩백!"
android:textSize="18sp" />
</LinearLayout>
</ScrollView>
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/ivBackwards"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_margin="16dp"
android:background="@drawable/img_left_arrow" />
</androidx.appcompat.widget.Toolbar>
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_margin="16dp"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<ImageView
android:id="@+id/ivDetailLike"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="16dp"
android:background="@drawable/selector_detail_like_24dp" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@color/칸나눔" />
<TextView
android:id="@+id/tvDatailPrice"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="2"
android:text="180,000원"
android:textSize="22sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnDetailChat"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_weight="1"
android:background="@drawable/button_corner_radius"
android:text="@string/detail_chat"
android:textColor="@color/white"
android:textStyle="bold" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>