refactor: item decoration 추가

This commit is contained in:
2025-08-04 22:04:19 +09:00
parent 33bdaa7dbd
commit 7b7513561d
8 changed files with 301 additions and 123 deletions

View File

@@ -6,8 +6,12 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import coil.transform.RoundedCornersTransformation
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
import kr.co.vividnext.sodalive.databinding.ItemCharacterBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
class CharacterAdapter(
private var characters: List<Character> = emptyList(),
@@ -34,6 +38,13 @@ class CharacterAdapter(
Glide.with(context)
.load(character.imageUrl)
.apply(
RequestOptions().transform(
RoundedCorners(
16f.dpToPx().toInt()
)
)
)
.into(binding.ivCharacter)
binding.root.setOnClickListener { onCharacterClick(character) }

View File

@@ -1,20 +1,22 @@
package kr.co.vividnext.sodalive.chat.character
import android.graphics.Rect
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.chat.character.curation.CurationSection
import kr.co.vividnext.sodalive.chat.character.curation.CurationSectionAdapter
import kr.co.vividnext.sodalive.chat.character.recent.RecentCharacter
import kr.co.vividnext.sodalive.chat.character.recent.RecentCharacterAdapter
import kr.co.vividnext.sodalive.databinding.FragmentCharacterTabBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
// 캐릭터 탭 프래그먼트
class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
FragmentCharacterTabBinding::inflate
) {
private lateinit var recentCharacterAdapter: RecentCharacterAdapter
private lateinit var popularCharacterAdapter: CharacterAdapter
private lateinit var newCharacterAdapter: CharacterAdapter
@@ -22,65 +24,208 @@ class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerViews()
setupView()
loadData()
}
private fun setupRecyclerViews() {
// 최근 대화한 캐릭터 RecyclerView 설정
recentCharacterAdapter = RecentCharacterAdapter { character ->
// 캐릭터 클릭 처리
onRecentCharacterClick(character)
}
binding.rvRecentCharacters.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
adapter = recentCharacterAdapter
private fun setupView() {
setupRecentCharactersRecyclerView()
setupPopularCharactersRecyclerView()
setupNewCharactersRecyclerView()
setupCurationSectionsRecyclerView()
}
private fun setupRecentCharactersRecyclerView() {
// 최근 대화한 캐릭터 RecyclerView 설정
recentCharacterAdapter = RecentCharacterAdapter {
onRecentCharacterClick(it)
}
val recyclerView = binding.rvRecentCharacters
recyclerView.layoutManager = LinearLayoutManager(
requireContext(),
LinearLayoutManager.HORIZONTAL,
false
)
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
when (parent.getChildAdapterPosition(view)) {
0 -> {
outRect.left = 0
outRect.right = 8f.dpToPx().toInt()
}
recentCharacterAdapter.itemCount - 1 -> {
outRect.left = 8f.dpToPx().toInt()
outRect.right = 0
}
else -> {
outRect.left = 8f.dpToPx().toInt()
outRect.right = 8f.dpToPx().toInt()
}
}
}
})
recyclerView.adapter = recentCharacterAdapter
}
private fun setupPopularCharactersRecyclerView() {
// 인기 캐릭터 RecyclerView 설정 (순위 표시)
popularCharacterAdapter = CharacterAdapter(
showRanking = true
) { character ->
// 캐릭터 클릭 처리
onCharacterClick(character)
}
binding.rvPopularCharacters.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
adapter = popularCharacterAdapter
) {
onCharacterClick(it)
}
val recyclerView = binding.rvPopularCharacters
recyclerView.layoutManager = LinearLayoutManager(
requireContext(),
LinearLayoutManager.HORIZONTAL,
false
)
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
when (parent.getChildAdapterPosition(view)) {
0 -> {
outRect.left = 0
outRect.right = 8f.dpToPx().toInt()
}
popularCharacterAdapter.itemCount - 1 -> {
outRect.left = 8f.dpToPx().toInt()
outRect.right = 0
}
else -> {
outRect.left = 8f.dpToPx().toInt()
outRect.right = 8f.dpToPx().toInt()
}
}
}
})
recyclerView.adapter = popularCharacterAdapter
binding.tvPopularCharacterAll.setOnClickListener {
}
}
private fun setupNewCharactersRecyclerView() {
// 신규 캐릭터 RecyclerView 설정
newCharacterAdapter = CharacterAdapter(
showRanking = false
) { character ->
// 캐릭터 클릭 처리
onCharacterClick(character)
}
binding.rvNewCharacters.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
adapter = newCharacterAdapter
) {
onCharacterClick(it)
}
// 큐레이션 섹션 RecyclerView 설정
curationSectionAdapter = CurationSectionAdapter { character ->
// 캐릭터 클릭 처리
onCharacterClick(character)
}
binding.rvCurationSections.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = curationSectionAdapter
val recyclerView = binding.rvNewCharacters
recyclerView.layoutManager = LinearLayoutManager(
requireContext(),
LinearLayoutManager.HORIZONTAL,
false
)
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
when (parent.getChildAdapterPosition(view)) {
0 -> {
outRect.left = 0
outRect.right = 8f.dpToPx().toInt()
}
// 전체보기 버튼 클릭 리스너
binding.tvPopularCharacterAll.setOnClickListener {
// 인기 캐릭터 전체보기 처리
newCharacterAdapter.itemCount - 1 -> {
outRect.left = 8f.dpToPx().toInt()
outRect.right = 0
}
else -> {
outRect.left = 8f.dpToPx().toInt()
outRect.right = 8f.dpToPx().toInt()
}
}
}
})
recyclerView.adapter = newCharacterAdapter
binding.tvNewCharacterAll.setOnClickListener {
// 신규 캐릭터 전체보기 처리
}
}
private fun setupCurationSectionsRecyclerView() {
// 큐레이션 섹션 RecyclerView 설정
curationSectionAdapter = CurationSectionAdapter {
onCharacterClick(it)
}
val recyclerView = binding.rvCurationSections
recyclerView.layoutManager = LinearLayoutManager(
requireContext(),
LinearLayoutManager.VERTICAL,
false
)
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
when (parent.getChildAdapterPosition(view)) {
0 -> {
outRect.top = 0
outRect.bottom = 24f.dpToPx().toInt()
}
curationSectionAdapter.itemCount - 1 -> {
outRect.top = 24f.dpToPx().toInt()
outRect.bottom = 0
}
else -> {
outRect.top = 24f.dpToPx().toInt()
outRect.bottom = 24f.dpToPx().toInt()
}
}
}
})
recyclerView.adapter = curationSectionAdapter
}
private fun loadData() {
// TODO: 실제 데이터 로딩 로직 구현
loadRecentCharacters()

View File

@@ -1,13 +1,17 @@
package kr.co.vividnext.sodalive.chat.character.curation
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Rect
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kr.co.vividnext.sodalive.chat.character.Character
import kr.co.vividnext.sodalive.chat.character.CharacterAdapter
import kr.co.vividnext.sodalive.databinding.ItemCurationSectionBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
class CurationSectionAdapter(
private var sections: List<CurationSection> = emptyList(),
@@ -15,6 +19,7 @@ class CurationSectionAdapter(
) : RecyclerView.Adapter<CurationSectionAdapter.ViewHolder>() {
inner class ViewHolder(
private val context: Context,
private val binding: ItemCurationSectionBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(section: CurationSection) {
@@ -27,18 +32,48 @@ class CurationSectionAdapter(
onCharacterClick = onCharacterClick
)
binding.rvCharacters.apply {
layoutManager = LinearLayoutManager(
val recyclerView = binding.rvCharacters
recyclerView.layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.HORIZONTAL,
false
)
adapter = characterAdapter
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
when (parent.getChildAdapterPosition(view)) {
0 -> {
outRect.left = 0
outRect.right = 8f.dpToPx().toInt()
}
characterAdapter.itemCount - 1 -> {
outRect.left = 8f.dpToPx().toInt()
outRect.right = 0
}
else -> {
outRect.left = 8f.dpToPx().toInt()
outRect.right = 8f.dpToPx().toInt()
}
}
}
})
recyclerView.adapter = characterAdapter
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
parent.context,
ItemCurationSectionBinding.inflate(
LayoutInflater.from(parent.context),
parent,

View File

@@ -6,7 +6,10 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
import kr.co.vividnext.sodalive.databinding.ItemRecentCharacterBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
class RecentCharacterAdapter(
private var characters: List<RecentCharacter> = emptyList(),
@@ -21,6 +24,13 @@ class RecentCharacterAdapter(
binding.tvName.text = character.name
Glide.with(context)
.load(character.profileImageUrl)
.apply(
RequestOptions().transform(
RoundedCorners(
16f.dpToPx().toInt()
)
)
)
.into(binding.ivProfile)
binding.root.setOnClickListener { onCharacterClick(character) }

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="135"
android:startColor="#80000000"
android:centerColor="#99000000"
android:endColor="#CC000000"
android:type="linear" />
<corners
android:bottomLeftRadius="16dp"
android:bottomRightRadius="0dp"
android:topLeftRadius="0dp"
android:topRightRadius="0dp" />
</shape>

View File

@@ -9,7 +9,8 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:paddingVertical="24dp">
<!-- 배너 섹션 -->
<LinearLayout
@@ -20,14 +21,14 @@
android:orientation="vertical">
<com.zhpan.bannerview.BannerViewPager
android:id="@+id/event_banner_slider"
android:id="@+id/banner_slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false" />
<com.zhpan.indicator.IndicatorView
android:id="@+id/indicator_event_banner"
android:id="@+id/indicator_banner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
@@ -175,6 +176,6 @@
android:id="@+id/rv_curation_sections"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp" />
android:clipToPadding="false" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -1,21 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="168dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:layout_width="168dp"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_character"
android:layout_width="168dp"
android:layout_height="168dp"
android:background="@color/color_777777"
android:contentDescription="@null"
android:scaleType="centerCrop" />
<!-- 순위 표시 (인기 캐릭터에서만 보임) -->
<LinearLayout
android:id="@+id/ll_ranking"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="14dp"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_ranking"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/pretendard_bold"
android:textColor="@color/white"
android:textSize="72sp" />
</LinearLayout>
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -35,37 +55,10 @@
android:id="@+id/tv_character_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginTop="4dp"
android:fontFamily="@font/pretendard_regular"
android:textColor="#78909C"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
<!-- 순위 표시 (인기 캐릭터에서만 보임) -->
<LinearLayout
android:id="@+id/ll_ranking"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@drawable/gradient_ranking_bg"
android:gravity="center"
android:orientation="horizontal"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
android:visibility="gone">
<TextView
android:id="@+id/tv_ranking"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/pretendard_bold"
android:textColor="@color/white"
android:textSize="72sp" />
</LinearLayout>
</FrameLayout>

View File

@@ -2,7 +2,6 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="48dp"
android:orientation="vertical">
<!-- 제목 -->
@@ -21,7 +20,6 @@
android:fontFamily="@font/pretendard_bold"
android:textColor="@color/white"
android:textSize="24sp" />
</LinearLayout>
<!-- 캐릭터 카드 리스트 -->
@@ -31,6 +29,5 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:clipToPadding="false"
android:paddingStart="24dp" />
android:paddingHorizontal="24dp" />
</LinearLayout>