[Android / Kotlin] AppleMarket (2)

Subeen·2024년 1월 7일
0

Android

목록 보기
33/71

Apple Market (1) 에 이어서 메인 페이지의 나머지 기능과 상세 페이지를 구현 하였다.
과제 목표 (메인 페이지)
[v]상품 선택시 아래 상품 상세 페이지로 이동합니다.
[v] 상품 상세페이지 이동시 intent로 객체를 전달합니다. (Parcelize 사용)
(상세 페이지)
[v] 디자인 및 화면 구성을 최대한 동일하게 해주세요. (사이즈 및 여백도 최대한 맞춰주세요.) ✨
[v] 메인화면에서 전달받은 데이터로 판매자, 주소, 아이템, 글내용, 가격등을 화면에 표시합니다.
[v] 하단 가격표시 레이아웃을 제외하고 전체화면은 스크롤이 되어야합니다. (예시 비디오 참고)
[v] 상단 < 버튼을 누르면 상세 화면은 종료되고 메인화면으로 돌아갑니다.

결과 화면

ProductEntity

@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

ProductAdapter

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

    }
}

ProductManager

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

ProductObject

decimalFormat 함수를 원래는 메인 액티비티에 추가 했는데 디테일 액티비티에서도 사용할거라 obejct로 따로 생성하였다.

object ProductObject {
    fun Int.decimalFormat(): String {  // 숫자 세 자리 마다 쉼표 출력
        val dec = DecimalFormat("#,###")
        return dec.format(this)
    }
}

MainActivity


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

DetailActivity

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
    }

}

layout_detail

줄 간격 설정하기

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>
profile
개발 공부 기록 🌱

0개의 댓글