feat(banner): Phase 8 배너 동작을 보완한다
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -9,6 +9,10 @@ import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller
|
||||
import androidx.recyclerview.widget.PagerSnapHelper
|
||||
@@ -37,6 +41,19 @@ class BannerView @JvmOverloads constructor(
|
||||
private val autoScrollHandler = Handler(Looper.getMainLooper())
|
||||
private val autoScrollRunnable = Runnable { moveToNextBanner() }
|
||||
private var hasWindowAttachmentForAutoScroll = false
|
||||
private var isLifecycleStartedForAutoScroll = true
|
||||
private var lifecycleOwner: LifecycleOwner? = null
|
||||
private val lifecycleObserver = object : DefaultLifecycleObserver {
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
isLifecycleStartedForAutoScroll = true
|
||||
startAutoScrollIfNeeded()
|
||||
}
|
||||
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
isLifecycleStartedForAutoScroll = false
|
||||
stopAutoScroll()
|
||||
}
|
||||
}
|
||||
private val bannerScrollListener = object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
when (newState) {
|
||||
@@ -66,11 +83,13 @@ class BannerView @JvmOverloads constructor(
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
hasWindowAttachmentForAutoScroll = true
|
||||
observeLifecycleOwner()
|
||||
startAutoScrollIfNeeded()
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
stopAutoScroll()
|
||||
clearLifecycleOwner()
|
||||
hasWindowAttachmentForAutoScroll = false
|
||||
super.onDetachedFromWindow()
|
||||
}
|
||||
@@ -96,7 +115,7 @@ class BannerView @JvmOverloads constructor(
|
||||
|
||||
fun setItems(items: List<BannerItem>) {
|
||||
this.items = items
|
||||
currentIndex = BannerState.from(items.size, currentIndex).currentIndex
|
||||
currentIndex = 0
|
||||
visibility = if (items.isEmpty()) GONE else VISIBLE
|
||||
stopAutoScroll()
|
||||
adapter.submitItems(items)
|
||||
@@ -187,6 +206,7 @@ class BannerView @JvmOverloads constructor(
|
||||
private fun startAutoScrollIfNeeded() {
|
||||
stopAutoScroll()
|
||||
if (!hasWindowAttachmentForAutoScroll) return
|
||||
if (!isLifecycleStartedForAutoScroll) return
|
||||
if (BannerState.from(items.size, currentIndex).displayMode != BannerDisplayMode.Carousel) return
|
||||
autoScrollHandler.postDelayed(autoScrollRunnable, AUTO_SCROLL_INTERVAL_MS)
|
||||
}
|
||||
@@ -196,9 +216,15 @@ class BannerView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
private fun updateCurrentIndexFromSnap() {
|
||||
val layoutManager = requireNotNull(recyclerView).layoutManager ?: return
|
||||
val snapView = snapHelper.findSnapView(layoutManager) ?: return
|
||||
val position = layoutManager.getPosition(snapView)
|
||||
val recyclerView = requireNotNull(recyclerView)
|
||||
val layoutManager = recyclerView.layoutManager ?: return
|
||||
val snapView = snapHelper.findSnapView(layoutManager)
|
||||
val snapPosition = snapView?.let(layoutManager::getPosition) ?: RecyclerView.NO_POSITION
|
||||
val position = if (snapPosition == RecyclerView.NO_POSITION) {
|
||||
currentAdapterPosition
|
||||
} else {
|
||||
snapPosition
|
||||
}
|
||||
currentAdapterPosition = position
|
||||
currentIndex = adapter.toRealIndex(position)
|
||||
updateCounter()
|
||||
@@ -213,10 +239,25 @@ class BannerView @JvmOverloads constructor(
|
||||
val formatted = BannerCounterFormatter.format(state.currentIndex, items.size)
|
||||
val parts = formatted.split(" ")
|
||||
currentIndexText?.text = parts[0]
|
||||
separatorText?.text = parts[1]
|
||||
separatorText?.text = " ${parts[1]} "
|
||||
totalCountText?.text = parts[2]
|
||||
}
|
||||
|
||||
private fun observeLifecycleOwner() {
|
||||
val owner = findViewTreeLifecycleOwner() ?: return
|
||||
if (lifecycleOwner === owner) return
|
||||
clearLifecycleOwner()
|
||||
lifecycleOwner = owner
|
||||
isLifecycleStartedForAutoScroll = owner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)
|
||||
owner.lifecycle.addObserver(lifecycleObserver)
|
||||
}
|
||||
|
||||
private fun clearLifecycleOwner() {
|
||||
lifecycleOwner?.lifecycle?.removeObserver(lifecycleObserver)
|
||||
lifecycleOwner = null
|
||||
isLifecycleStartedForAutoScroll = true
|
||||
}
|
||||
|
||||
private fun Int.dpToPx(): Int = (this * resources.displayMetrics.density).roundToInt()
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_banner_current_index"
|
||||
style="@style/Typography.Caption2"
|
||||
style="@style/Typography.Body5"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/white"
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_banner_counter_separator"
|
||||
style="@style/Typography.Caption2"
|
||||
style="@style/Typography.Body5"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/gray_400"
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_banner_total_count"
|
||||
style="@style/Typography.Caption2"
|
||||
style="@style/Typography.Body5"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/gray_400"
|
||||
|
||||
Reference in New Issue
Block a user