fix(content-ranking): 카드 UI 크기를 반응형 적용한다

This commit is contained in:
2026-07-10 10:16:36 +09:00
parent e77d0a1d5b
commit abd084cff8
4 changed files with 204 additions and 33 deletions
@@ -6,6 +6,7 @@ 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
@@ -13,6 +14,7 @@ import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.v2.widget.ranking.RankingResponsiveScale
import kotlin.math.roundToInt
open class ContentRankingGridCardView @JvmOverloads constructor(
@@ -29,6 +31,7 @@ open class ContentRankingGridCardView @JvmOverloads constructor(
private var titleText: TextView? = null
private var creatorText: TextView? = null
private var currentItem: ContentRankingItem? = null
private var currentTextStyle: ContentRankingTextStyle? = null
private var clickListener: ((ContentRankingItem) -> Unit)? = null
override fun onFinishInflate() {
@@ -48,11 +51,13 @@ open class ContentRankingGridCardView @JvmOverloads constructor(
fun bind(item: ContentRankingItem) {
currentItem = item
val textStyle = currentTextStyle ?: defaultTextStyle()
applyTextStyle(textStyle)
requireNotNull(rankText).apply {
text = item.rank.toString()
applyContentRankingRankGradient()
}
bindDelta(item)
bindDelta(item, textStyle)
requireNotNull(titleText).apply {
text = item.displayContentName(context.getString(R.string.content_ranking_inaccessible_info))
visibility = if (text.isNullOrBlank()) View.INVISIBLE else View.VISIBLE
@@ -79,34 +84,58 @@ open class ContentRankingGridCardView @JvmOverloads constructor(
currentItem?.let(::applyAccessState)
}
private fun bindDelta(item: ContentRankingItem) {
private fun bindDelta(
item: ContentRankingItem,
textStyle: ContentRankingTextStyle
) {
requireNotNull(deltaGroup).visibility = if (item.showRankChange) View.VISIBLE else View.GONE
if (!item.showRankChange) return
val presentation = ContentRankingDeltaPresentation.from(item.rankChangeType, item.rankChangeAmount)
applyDeltaContainer(presentation)
val scale = RankingResponsiveScale.fromResources(resources)
applyDeltaContainer(presentation, scale)
requireNotNull(deltaIcon).apply {
setImageResource(presentation.iconRes)
layoutParams = (layoutParams as ViewGroup.MarginLayoutParams).apply {
width = presentation.iconWidthDp.dpToPx()
height = presentation.iconHeightDp.dpToPx()
marginStart = presentation.iconMarginStartDp.dpToPx()
width = presentation.iconWidthDp.dpToPx(scale)
height = presentation.iconHeightDp.dpToPx(scale)
marginStart = presentation.iconMarginStartDp.dpToPx(scale)
}
}
requireNotNull(deltaAmountText).apply {
text = presentation.amountText
setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.deltaTextSizeSp)
visibility = if (presentation.showAmount) View.VISIBLE else View.GONE
}
}
private fun applyDeltaContainer(presentation: ContentRankingDeltaPresentation) {
private fun applyDeltaContainer(
presentation: ContentRankingDeltaPresentation,
scale: Float
) {
requireNotNull(deltaGroup).apply {
setBackgroundResource(if (presentation.showPillBackground) R.drawable.bg_creator_ranking_delta else 0)
val horizontalPadding = if (presentation.showPillBackground) 4.dpToPx() else 0
val horizontalPadding = if (presentation.showPillBackground) 4.dpToPx(scale) else 0
setPadding(horizontalPadding, paddingTop, horizontalPadding, paddingBottom)
}
}
private fun applyTextStyle(style: ContentRankingTextStyle) {
requireNotNull(rankText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.rankTextSizeSp)
requireNotNull(titleText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.titleTextSizeSp)
requireNotNull(creatorText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.creatorTextSizeSp)
requireNotNull(deltaAmountText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.deltaTextSizeSp)
}
private fun defaultTextStyle(): ContentRankingTextStyle {
val scale = RankingResponsiveScale.fromResources(resources)
return if (this is ContentRankingSmallGridCardView) {
ContentRankingTextStyle.smallGrid(scale)
} else {
ContentRankingTextStyle.mediumGrid(scale)
}
}
private fun applyAccessState(item: ContentRankingItem) {
applyBlur(item.isInaccessible)
isClickable = item.isTouchable && clickListener != null
@@ -120,26 +149,32 @@ open class ContentRankingGridCardView @JvmOverloads constructor(
private fun positionMedium(size: ContentRankingCardSize) {
val scale = size.widthPx / 185f
val textStyle = ContentRankingTextStyle.mediumGrid(RankingResponsiveScale.fromResources(resources))
currentTextStyle = textStyle
requireNotNull(rankText).apply {
textSize = 54f
setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.rankTextSizeSp)
layoutParams = LayoutParams((55 * scale).roundToInt(), (75 * scale).roundToInt())
setPadding(0, 0, 0, (6 * scale).roundToInt())
}
requireNotNull(titleText).textSize = 22f
requireNotNull(titleText).setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.titleTextSizeSp)
requireNotNull(creatorText).setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.creatorTextSizeSp)
placeDelta(left = 10, top = 70, scale = scale)
placeLabel(width = 165, top = 136, scale = scale, size = size)
}
private fun positionSmall(size: ContentRankingCardSize) {
val scale = size.widthPx / 122f
val textStyle = ContentRankingTextStyle.smallGrid(RankingResponsiveScale.fromResources(resources))
currentTextStyle = textStyle
requireNotNull(rankText).apply {
textSize = 40f
setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.rankTextSizeSp)
layoutParams = LayoutParams((42 * scale).roundToInt(), (56 * scale).roundToInt()).apply {
leftMargin = (8 * scale).roundToInt()
}
setPadding(0, 0, 0, (5 * scale).roundToInt())
}
requireNotNull(titleText).textSize = 14f
requireNotNull(titleText).setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.titleTextSizeSp)
requireNotNull(creatorText).setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.creatorTextSizeSp)
placeDelta(left = 10, top = 49, scale = scale)
placeLabel(width = 102, top = 81, scale = scale, size = size)
}
@@ -178,7 +213,7 @@ open class ContentRankingGridCardView @JvmOverloads constructor(
}
}
private fun Int.dpToPx(): Int = (this * resources.displayMetrics.density).roundToInt()
private fun Int.dpToPx(scale: Float = 1f): Int = (this * scale * resources.displayMetrics.density).roundToInt()
private companion object {
const val SMALL_THRESHOLD_PX = 140
@@ -6,6 +6,7 @@ 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
@@ -13,6 +14,7 @@ import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.v2.widget.ranking.RankingResponsiveScale
import kotlin.math.roundToInt
class ContentRankingHorizontalCardView @JvmOverloads constructor(
@@ -48,11 +50,13 @@ class ContentRankingHorizontalCardView @JvmOverloads constructor(
fun bind(item: ContentRankingItem) {
currentItem = item
val textStyle = ContentRankingTextStyle.horizontal(RankingResponsiveScale.fromResources(resources))
applyTextStyle(textStyle)
requireNotNull(rankText).apply {
text = item.rank.toString()
applyContentRankingRankGradient()
}
bindDelta(item)
bindDelta(item, textStyle)
val inaccessibleMessage = context.getString(R.string.content_ranking_inaccessible_info)
requireNotNull(titleText).text = item.displayContentName(inaccessibleMessage)
requireNotNull(creatorText).text = item.displayCreatorName()
@@ -69,6 +73,7 @@ class ContentRankingHorizontalCardView @JvmOverloads constructor(
height = size.heightPx
}
positionViews(size)
applyTextStyle(ContentRankingTextStyle.horizontal(RankingResponsiveScale.fromResources(resources)))
}
fun imageView(): ImageView = requireNotNull(image)
@@ -78,35 +83,51 @@ class ContentRankingHorizontalCardView @JvmOverloads constructor(
currentItem?.let(::applyAccessState)
}
private fun bindDelta(item: ContentRankingItem) {
private fun bindDelta(
item: ContentRankingItem,
textStyle: ContentRankingTextStyle
) {
val deltaGroup = findViewById<View>(R.id.ll_content_ranking_delta)
deltaGroup.visibility = if (item.showRankChange) View.VISIBLE else View.GONE
if (!item.showRankChange) return
val presentation = ContentRankingDeltaPresentation.from(item.rankChangeType, item.rankChangeAmount)
applyDeltaContainer(presentation)
val scale = RankingResponsiveScale.fromResources(resources)
applyDeltaContainer(presentation, scale)
requireNotNull(deltaIcon).apply {
setImageResource(presentation.iconRes)
layoutParams = (layoutParams as ViewGroup.MarginLayoutParams).apply {
width = presentation.iconWidthDp.dpToPx()
height = presentation.iconHeightDp.dpToPx()
marginStart = presentation.iconMarginStartDp.dpToPx()
width = presentation.iconWidthDp.dpToPx(scale)
height = presentation.iconHeightDp.dpToPx(scale)
marginStart = presentation.iconMarginStartDp.dpToPx(scale)
}
}
requireNotNull(deltaAmountText).apply {
text = presentation.amountText
setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.deltaTextSizeSp)
visibility = if (presentation.showAmount) View.VISIBLE else View.GONE
}
}
private fun applyDeltaContainer(presentation: ContentRankingDeltaPresentation) {
private fun applyDeltaContainer(
presentation: ContentRankingDeltaPresentation,
scale: Float
) {
findViewById<View>(R.id.ll_content_ranking_delta).apply {
setBackgroundResource(if (presentation.showPillBackground) R.drawable.bg_creator_ranking_delta else 0)
val horizontalPadding = if (presentation.showPillBackground) 4.dpToPx() else 0
val horizontalPadding = if (presentation.showPillBackground) 4.dpToPx(scale) else 0
setPadding(horizontalPadding, paddingTop, horizontalPadding, paddingBottom)
}
}
private fun applyTextStyle(style: ContentRankingTextStyle) {
requireNotNull(rankText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.rankTextSizeSp)
requireNotNull(titleText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.titleTextSizeSp)
requireNotNull(creatorText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.creatorTextSizeSp)
requireNotNull(inaccessibleText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.titleTextSizeSp)
requireNotNull(deltaAmountText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.deltaTextSizeSp)
}
private fun applyAccessState(item: ContentRankingItem) {
applyBlur(item.isInaccessible)
isClickable = item.isTouchable && clickListener != null
@@ -157,5 +178,5 @@ class ContentRankingHorizontalCardView @JvmOverloads constructor(
}
}
private fun Int.dpToPx(): Int = (this * resources.displayMetrics.density).roundToInt()
private fun Int.dpToPx(scale: Float = 1f): Int = (this * scale * resources.displayMetrics.density).roundToInt()
}
@@ -6,6 +6,7 @@ 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
@@ -13,6 +14,7 @@ import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.v2.widget.ranking.RankingResponsiveScale
import kotlin.math.roundToInt
class ContentRankingLargeCardView @JvmOverloads constructor(
@@ -50,11 +52,13 @@ class ContentRankingLargeCardView @JvmOverloads constructor(
fun bind(item: ContentRankingItem) {
currentItem = item
val textStyle = ContentRankingTextStyle.large(RankingResponsiveScale.fromResources(resources))
applyTextStyle(textStyle)
requireNotNull(rankText).apply {
text = item.rank.toString()
applyContentRankingRankGradient()
}
bindDelta(item)
bindDelta(item, textStyle)
requireNotNull(titleText).apply {
text = item.displayContentName(context.getString(R.string.content_ranking_inaccessible_info))
visibility = if (text.isNullOrBlank()) View.INVISIBLE else View.VISIBLE
@@ -72,6 +76,7 @@ class ContentRankingLargeCardView @JvmOverloads constructor(
height = size.heightPx
}
positionViews(size)
applyTextStyle(ContentRankingTextStyle.large(RankingResponsiveScale.fromResources(resources)))
}
fun imageView(): ImageView = contentImageView()
@@ -83,34 +88,49 @@ class ContentRankingLargeCardView @JvmOverloads constructor(
currentItem?.let(::applyAccessState)
}
private fun bindDelta(item: ContentRankingItem) {
private fun bindDelta(
item: ContentRankingItem,
textStyle: ContentRankingTextStyle
) {
requireNotNull(deltaGroup).visibility = if (item.showRankChange) View.VISIBLE else View.GONE
if (!item.showRankChange) return
val presentation = ContentRankingDeltaPresentation.from(item.rankChangeType, item.rankChangeAmount)
applyDeltaContainer(presentation)
val scale = RankingResponsiveScale.fromResources(resources)
applyDeltaContainer(presentation, scale)
requireNotNull(deltaIcon).apply {
setImageResource(presentation.iconRes)
layoutParams = (layoutParams as ViewGroup.MarginLayoutParams).apply {
width = presentation.iconWidthDp.dpToPx()
height = presentation.iconHeightDp.dpToPx()
marginStart = presentation.iconMarginStartDp.dpToPx()
width = presentation.iconWidthDp.dpToPx(scale)
height = presentation.iconHeightDp.dpToPx(scale)
marginStart = presentation.iconMarginStartDp.dpToPx(scale)
}
}
requireNotNull(deltaAmountText).apply {
text = presentation.amountText
setTextSize(TypedValue.COMPLEX_UNIT_SP, textStyle.deltaTextSizeSp)
visibility = if (presentation.showAmount) View.VISIBLE else View.GONE
}
}
private fun applyDeltaContainer(presentation: ContentRankingDeltaPresentation) {
private fun applyDeltaContainer(
presentation: ContentRankingDeltaPresentation,
scale: Float
) {
requireNotNull(deltaGroup).apply {
setBackgroundResource(if (presentation.showPillBackground) R.drawable.bg_creator_ranking_delta else 0)
val horizontalPadding = if (presentation.showPillBackground) 4.dpToPx() else 0
val horizontalPadding = if (presentation.showPillBackground) 4.dpToPx(scale) else 0
setPadding(horizontalPadding, paddingTop, horizontalPadding, paddingBottom)
}
}
private fun applyTextStyle(style: ContentRankingTextStyle) {
requireNotNull(rankText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.rankTextSizeSp)
requireNotNull(titleText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.titleTextSizeSp)
requireNotNull(creatorText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.creatorTextSizeSp)
requireNotNull(deltaAmountText).setTextSize(TypedValue.COMPLEX_UNIT_SP, style.deltaTextSizeSp)
}
private fun applyAccessState(item: ContentRankingItem) {
applyBlur(item.isInaccessible)
isClickable = item.isTouchable && clickListener != null
@@ -159,7 +179,7 @@ class ContentRankingLargeCardView @JvmOverloads constructor(
}
}
private fun Int.dpToPx(): Int = (this * resources.displayMetrics.density).roundToInt()
private fun Int.dpToPx(scale: Float = 1f): Int = (this * scale * resources.displayMetrics.density).roundToInt()
private companion object {
const val FIGMA_WIDTH = 374