refactor(creator-ranking): 하위 행 뷰를 추가한다

This commit is contained in:
2026-06-30 02:44:04 +09:00
parent d5cc5df704
commit 86be8e4af1
2 changed files with 230 additions and 0 deletions

View File

@@ -0,0 +1,143 @@
package kr.co.vividnext.sodalive.v2.widget.creatorranking
import android.content.Context
import android.graphics.Outline
import android.graphics.RenderEffect
import android.graphics.Shader
import android.os.Build
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import kr.co.vividnext.sodalive.R
import kotlin.math.max
import kotlin.math.roundToInt
class CreatorRankingLowerRowView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
private var image: ImageView? = null
private var rankGroup: View? = null
private var rankText: TextView? = null
private var deltaGroup: View? = null
private var deltaAmountText: TextView? = null
private var deltaIcon: ImageView? = null
private var nameText: TextView? = null
private var currentItem: CreatorRankingItem? = null
private var clickListener: ((CreatorRankingItem) -> Unit)? = null
override fun onFinishInflate() {
super.onFinishInflate()
image = findViewById(R.id.iv_creator_ranking_image)
rankGroup = findViewById(R.id.ll_creator_ranking_rank_group)
rankText = findViewById(R.id.tv_creator_ranking_rank)
deltaGroup = findViewById(R.id.ll_creator_ranking_delta)
deltaAmountText = findViewById(R.id.tv_creator_ranking_delta_amount)
deltaIcon = findViewById(R.id.iv_creator_ranking_delta_icon)
nameText = findViewById(R.id.tv_creator_ranking_name)
imageView().outlineProvider = roundOutlineProvider()
imageView().clipToOutline = true
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val width = MeasureSpec.getSize(widthMeasureSpec)
val ratioHeight = (width * FIGMA_HEIGHT_RATIO_NUMERATOR) / FIGMA_HEIGHT_RATIO_DENOMINATOR
val ratioHeightSpec = MeasureSpec.makeMeasureSpec(ratioHeight, MeasureSpec.EXACTLY)
super.onMeasure(widthMeasureSpec, ratioHeightSpec)
val contentHeight = requireNotNull(rankGroup).measuredHeight + paddingTop + paddingBottom
val height = max(ratioHeight, contentHeight)
if (height != ratioHeight) {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))
}
}
fun bind(item: CreatorRankingItem) {
currentItem = item
val style = CreatorRankingTextStyle.fromRank(item.rank)
requireNotNull(rankText).apply {
text = item.rank.toString()
setTextSize(TypedValue.COMPLEX_UNIT_SP, style.rankTextSizeSp.toFloat())
applyCreatorRankingRankGradient()
}
bindDelta(item, style)
requireNotNull(nameText).apply {
text = item.displayName(context.getString(R.string.creator_ranking_inaccessible_info))
setTextSize(TypedValue.COMPLEX_UNIT_SP, style.nameTextSizeSp.toFloat())
}
applyAccessState(item)
}
fun imageView(): ImageView = requireNotNull(image)
fun setOnCreatorClick(listener: ((CreatorRankingItem) -> Unit)?) {
clickListener = listener
currentItem?.let(::applyAccessState)
}
private fun bindDelta(
item: CreatorRankingItem,
style: CreatorRankingTextStyle
) {
requireNotNull(deltaGroup).visibility = if (item.showRankChange) VISIBLE else GONE
if (!item.showRankChange) return
val presentation = CreatorRankingDeltaPresentation.from(item.rankChangeType, item.rankChangeAmount)
applyDeltaContainer(presentation)
requireNotNull(deltaIcon).apply {
setImageResource(presentation.iconRes)
layoutParams = (layoutParams as ViewGroup.MarginLayoutParams).apply {
width = presentation.iconWidthDp.dpToPx()
height = presentation.iconHeightDp.dpToPx()
marginStart = presentation.iconMarginStartDp.dpToPx()
}
}
requireNotNull(deltaAmountText).apply {
text = presentation.amountText.orEmpty()
setTextSize(TypedValue.COMPLEX_UNIT_SP, style.deltaTextSizeSp.toFloat())
visibility = if (presentation.showAmount) VISIBLE else GONE
}
}
private fun applyDeltaContainer(presentation: CreatorRankingDeltaPresentation) {
requireNotNull(deltaGroup).apply {
setBackgroundResource(if (presentation.showPillBackground) R.drawable.bg_creator_ranking_delta else 0)
val horizontalPadding = if (presentation.showPillBackground) 4.dpToPx() else 0
setPadding(horizontalPadding, paddingTop, horizontalPadding, paddingBottom)
}
}
private fun applyAccessState(item: CreatorRankingItem) {
applyBlur(item.isInaccessible)
isClickable = item.isTouchable && clickListener != null
setOnClickListener(if (isClickable) OnClickListener { clickListener?.invoke(item) } else null)
}
private fun applyBlur(enabled: Boolean) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
imageView().setRenderEffect(
if (enabled) RenderEffect.createBlurEffect(16f, 16f, Shader.TileMode.CLAMP) else null
)
}
}
private fun roundOutlineProvider() = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setRoundRect(0, 0, view.width, view.height, 14.dpToPx().toFloat())
}
}
private fun Int.dpToPx(): Int = (this * resources.displayMetrics.density).roundToInt()
private companion object {
const val FIGMA_HEIGHT_RATIO_NUMERATOR = 100
const val FIGMA_HEIGHT_RATIO_DENOMINATOR = 374
}
}

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<kr.co.vividnext.sodalive.v2.widget.creatorranking.CreatorRankingLowerRowView 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="wrap_content"
android:paddingVertical="10dp">
<LinearLayout
android:id="@+id/ll_creator_ranking_rank_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:paddingHorizontal="14dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tv_creator_ranking_rank"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/phosphate_solid"
android:gravity="center"
android:shadowColor="#7A000000"
android:shadowRadius="4"
android:textColor="@color/white"
tools:text="11" />
<LinearLayout
android:id="@+id/ll_creator_ranking_delta"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_creator_ranking_delta"
android:gravity="center"
android:orientation="horizontal"
android:paddingHorizontal="4dp">
<TextView
android:id="@+id/tv_creator_ranking_delta_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/medium"
android:includeFontPadding="false"
android:textColor="@color/white"
tools:text="4" />
<ImageView
android:id="@+id/iv_creator_ranking_delta_icon"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginStart="2dp"
android:contentDescription="@null"
tools:src="@drawable/ic_rank_caret_increase" />
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/iv_creator_ranking_image"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="14dp"
android:background="@drawable/bg_creator_ranking_image"
android:contentDescription="@null"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toEndOf="@id/ll_creator_ranking_rank_group"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_creator_ranking_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:ellipsize="end"
android:fontFamily="@font/bold"
android:includeFontPadding="false"
android:maxLines="1"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/iv_creator_ranking_image"
app:layout_constraintTop_toTopOf="parent"
tools:text="크리에이터 이름" />
</kr.co.vividnext.sodalive.v2.widget.creatorranking.CreatorRankingLowerRowView>