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

View File

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

View File

@@ -1,13 +1,17 @@
package kr.co.vividnext.sodalive.chat.character.curation package kr.co.vividnext.sodalive.chat.character.curation
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Rect
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kr.co.vividnext.sodalive.chat.character.Character import kr.co.vividnext.sodalive.chat.character.Character
import kr.co.vividnext.sodalive.chat.character.CharacterAdapter import kr.co.vividnext.sodalive.chat.character.CharacterAdapter
import kr.co.vividnext.sodalive.databinding.ItemCurationSectionBinding import kr.co.vividnext.sodalive.databinding.ItemCurationSectionBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
class CurationSectionAdapter( class CurationSectionAdapter(
private var sections: List<CurationSection> = emptyList(), private var sections: List<CurationSection> = emptyList(),
@@ -15,6 +19,7 @@ class CurationSectionAdapter(
) : RecyclerView.Adapter<CurationSectionAdapter.ViewHolder>() { ) : RecyclerView.Adapter<CurationSectionAdapter.ViewHolder>() {
inner class ViewHolder( inner class ViewHolder(
private val context: Context,
private val binding: ItemCurationSectionBinding private val binding: ItemCurationSectionBinding
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
fun bind(section: CurationSection) { fun bind(section: CurationSection) {
@@ -27,18 +32,48 @@ class CurationSectionAdapter(
onCharacterClick = onCharacterClick onCharacterClick = onCharacterClick
) )
binding.rvCharacters.apply { val recyclerView = binding.rvCharacters
layoutManager = LinearLayoutManager(
context, recyclerView.layoutManager = LinearLayoutManager(
LinearLayoutManager.HORIZONTAL, context,
false 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( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
parent.context,
ItemCurationSectionBinding.inflate( ItemCurationSectionBinding.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
parent, parent,

View File

@@ -6,7 +6,10 @@ import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide 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.databinding.ItemRecentCharacterBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
class RecentCharacterAdapter( class RecentCharacterAdapter(
private var characters: List<RecentCharacter> = emptyList(), private var characters: List<RecentCharacter> = emptyList(),
@@ -21,6 +24,13 @@ class RecentCharacterAdapter(
binding.tvName.text = character.name binding.tvName.text = character.name
Glide.with(context) Glide.with(context)
.load(character.profileImageUrl) .load(character.profileImageUrl)
.apply(
RequestOptions().transform(
RoundedCorners(
16f.dpToPx().toInt()
)
)
)
.into(binding.ivProfile) .into(binding.ivProfile)
binding.root.setOnClickListener { onCharacterClick(character) } 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 <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical"
android:paddingVertical="24dp">
<!-- 배너 섹션 --> <!-- 배너 섹션 -->
<LinearLayout <LinearLayout
@@ -20,14 +21,14 @@
android:orientation="vertical"> android:orientation="vertical">
<com.zhpan.bannerview.BannerViewPager <com.zhpan.bannerview.BannerViewPager
android:id="@+id/event_banner_slider" android:id="@+id/banner_slider"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clipChildren="false" android:clipChildren="false"
android:clipToPadding="false" /> android:clipToPadding="false" />
<com.zhpan.indicator.IndicatorView <com.zhpan.indicator.IndicatorView
android:id="@+id/indicator_event_banner" android:id="@+id/indicator_banner"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
@@ -175,6 +176,6 @@
android:id="@+id/rv_curation_sections" android:id="@+id/rv_curation_sections"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="24dp" /> android:clipToPadding="false" />
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@@ -1,71 +1,64 @@
<?xml version="1.0" encoding="utf-8"?> <?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_width="168dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="16dp"> android:orientation="vertical">
<LinearLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="168dp"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:orientation="vertical">
<ImageView <ImageView
android:id="@+id/iv_character" android:id="@+id/iv_character"
android:layout_width="168dp" android:layout_width="168dp"
android:layout_height="168dp" android:layout_height="168dp"
android:background="@color/color_777777" android:background="@color/color_777777"
android:contentDescription="@null"
android:scaleType="centerCrop" /> android:scaleType="centerCrop" />
<!-- 순위 표시 (인기 캐릭터에서만 보임) -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:id="@+id/ll_ranking"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginTop="4dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_character_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/pretendard_regular"
android:textColor="@color/color_b0bec5"
android:textSize="18sp" />
<TextView
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_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="@font/pretendard_bold" android:layout_gravity="bottom"
android:textColor="@color/white" android:layout_marginStart="14dp"
android:textSize="72sp" /> 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"
android:layout_marginStart="6dp"
android:layout_marginTop="4dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_character_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/pretendard_regular"
android:textColor="@color/color_b0bec5"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_character_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/pretendard_regular"
android:textColor="#78909C"
android:textSize="14sp" />
</LinearLayout> </LinearLayout>
</LinearLayout>
</FrameLayout>

View File

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