feat(creator): 채널 홈 탭 고정 스크롤을 연결한다

This commit is contained in:
2026-06-15 19:10:49 +09:00
parent d3bfc57294
commit dc217f97af
3 changed files with 54 additions and 1 deletions

View File

@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.creator.channel
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Color
import android.graphics.Typeface import android.graphics.Typeface
import android.view.Gravity import android.view.Gravity
import android.view.View import android.view.View
@@ -30,6 +31,7 @@ import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioConte
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHeaderUiModel import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHeaderUiModel
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHomeUiState import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHomeUiState
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelScrollState
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTab import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTab
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTitleBarState import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTitleBarState
import kr.co.vividnext.sodalive.v2.creator.channel.ui.CreatorChannelHomeSectionAdapter import kr.co.vividnext.sodalive.v2.creator.channel.ui.CreatorChannelHomeSectionAdapter
@@ -44,6 +46,8 @@ class CreatorChannelHomeActivity : BaseActivity<ActivityCreatorChannelHomeBindin
private val sectionAdapter = CreatorChannelHomeSectionAdapter(::onScheduleClicked, ::onAudioContentClicked) private val sectionAdapter = CreatorChannelHomeSectionAdapter(::onScheduleClicked, ::onAudioContentClicked)
private var creatorId: Long = 0L private var creatorId: Long = 0L
private var currentHeader: CreatorChannelHeaderUiModel? = null private var currentHeader: CreatorChannelHeaderUiModel? = null
private var statusBarHeight: Int = 0
private val baseTitleBarHeight: Int by lazy { 60.dpToPx().toInt() }
override val shouldApplySystemBarTopInset: Boolean = false override val shouldApplySystemBarTopInset: Boolean = false
@@ -57,6 +61,7 @@ class CreatorChannelHomeActivity : BaseActivity<ActivityCreatorChannelHomeBindin
setupRecyclerView() setupRecyclerView()
setStatusBarIconAppearance() setStatusBarIconAppearance()
setTitleBarTopInset() setTitleBarTopInset()
setupScrollListener()
setupClickListeners() setupClickListeners()
observeViewModel() observeViewModel()
viewModel.loadHome(creatorId) viewModel.loadHome(creatorId)
@@ -194,15 +199,44 @@ class CreatorChannelHomeActivity : BaseActivity<ActivityCreatorChannelHomeBindin
} }
private fun setTitleBarTopInset() { private fun setTitleBarTopInset() {
val baseTitleBarHeight = 60.dpToPx().toInt()
ViewCompat.setOnApplyWindowInsetsListener(binding.titleBarContainer) { view, insets -> ViewCompat.setOnApplyWindowInsetsListener(binding.titleBarContainer) { view, insets ->
val topInset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top val topInset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top
statusBarHeight = topInset
view.updatePadding(top = topInset) view.updatePadding(top = topInset)
view.updateLayoutParams { view.updateLayoutParams {
height = baseTitleBarHeight + topInset height = baseTitleBarHeight + topInset
} }
updateScrollState(binding.nestedScrollView.scrollY)
insets insets
} }
ViewCompat.requestApplyInsets(binding.titleBarContainer)
}
private fun setupScrollListener() {
binding.nestedScrollView.setOnScrollChangeListener { _, _, scrollY, _, _ ->
updateScrollState(scrollY)
}
}
private fun updateScrollState(scrollY: Int) {
val stickyTop = CreatorChannelScrollState.calculateStickyTop(statusBarHeight, baseTitleBarHeight)
val headerHeight = binding.headerContainer.height
if (headerHeight <= 0) return
val tabTranslationY = (scrollY - (headerHeight - stickyTop)).coerceAtLeast(0)
binding.horizontalTabScrollView.translationY = tabTranslationY.toFloat()
val tabBarTop = headerHeight - scrollY + tabTranslationY
val profileVisibleHeight = (headerHeight - scrollY).coerceIn(0, headerHeight)
val shouldUseBlackTitleBar = CreatorChannelScrollState.shouldUseBlackTitleBar(
titleBarBottom = stickyTop,
tabBarTop = tabBarTop,
profileImageVisibleHeight = profileVisibleHeight,
profileImageTotalHeight = headerHeight
)
binding.titleBarContainer.setBackgroundColor(
if (shouldUseBlackTitleBar) Color.BLACK else Color.TRANSPARENT
)
} }
private fun setStatusBarIconAppearance() { private fun setStatusBarIconAppearance() {

View File

@@ -119,6 +119,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="52dp" android:layout_height="52dp"
android:background="@color/black" android:background="@color/black"
android:elevation="1dp"
android:fillViewport="false" android:fillViewport="false"
android:overScrollMode="never" android:overScrollMode="never"
android:scrollbars="none"> android:scrollbars="none">

View File

@@ -60,6 +60,7 @@ class CreatorChannelHomeActivitySourceTest {
assertTrue(layout.contains("<HorizontalScrollView")) assertTrue(layout.contains("<HorizontalScrollView"))
assertTrue(layout.contains("@+id/horizontal_tab_scroll_view")) assertTrue(layout.contains("@+id/horizontal_tab_scroll_view"))
assertTrue(layout.contains("android:elevation=\"1dp\""))
assertTrue(layout.contains("@+id/tab_container")) assertTrue(layout.contains("@+id/tab_container"))
assertTrue(layout.contains("@+id/rv_home_sections")) assertTrue(layout.contains("@+id/rv_home_sections"))
assertTrue(layout.contains("android:drawableStart=\"@drawable/ic_new_talk\"")) assertTrue(layout.contains("android:drawableStart=\"@drawable/ic_new_talk\""))
@@ -96,6 +97,23 @@ class CreatorChannelHomeActivitySourceTest {
assertTrue(baseActivity.contains("if (shouldApplySystemBarTopInset) systemBars.top else 0")) assertTrue(baseActivity.contains("if (shouldApplySystemBarTopInset) systemBars.top else 0"))
assertTrue(source.contains("override val shouldApplySystemBarTopInset: Boolean = false")) assertTrue(source.contains("override val shouldApplySystemBarTopInset: Boolean = false"))
assertTrue(source.contains("setTitleBarTopInset")) assertTrue(source.contains("setTitleBarTopInset"))
assertTrue(source.contains("ViewCompat.requestApplyInsets(binding.titleBarContainer)"))
}
@Test
fun `scroll source는 tab sticky와 title bar black 전환을 연결한다`() {
val source = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeActivity.kt"
).readText()
assertTrue(source.contains("setupScrollListener"))
assertTrue(source.contains("binding.nestedScrollView.setOnScrollChangeListener"))
assertTrue(source.contains("CreatorChannelScrollState.calculateStickyTop"))
assertTrue(source.contains("binding.horizontalTabScrollView.translationY"))
assertTrue(source.contains("CreatorChannelScrollState.shouldUseBlackTitleBar"))
assertTrue(source.contains("binding.titleBarContainer.setBackgroundColor"))
assertTrue(source.contains("Color.BLACK"))
assertTrue(source.contains("Color.TRANSPARENT"))
} }
@Test @Test