tagFlow 展开按钮添加

This commit is contained in:
renhaoting 2025-10-21 14:57:36 +08:00
parent bb87a3d138
commit 2426f6b8bd
8 changed files with 95 additions and 90 deletions

View File

@ -1,6 +1,7 @@
package com.remax.visualnovel.configs package com.remax.visualnovel.configs
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import androidx.multidex.MultiDex import androidx.multidex.MultiDex
import androidx.multidex.MultiDexApplication import androidx.multidex.MultiDexApplication
@ -28,6 +29,9 @@ class NovelApplication : MultiDexApplication() {
fun getCurrentActivity(): Activity? { fun getCurrentActivity(): Activity? {
return currentActivity?.get() return currentActivity?.get()
} }
private lateinit var instance: NovelApplication
fun appContext(): Context = instance
} }
private val proxies = listOf<ApplicationProxy>(CommonApplicationProxy) private val proxies = listOf<ApplicationProxy>(CommonApplicationProxy)
@ -38,6 +42,7 @@ class NovelApplication : MultiDexApplication() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
MultiDex.install(this) MultiDex.install(this)
instance = this
proxies.forEach { it.onCreate(this) } proxies.forEach { it.onCreate(this) }
appInitializersProvider.startInit() appInitializersProvider.startInit()
} }

View File

@ -0,0 +1,11 @@
package com.remax.visualnovel.extension
import com.remax.visualnovel.configs.NovelApplication
private val resources = NovelApplication.appContext().resources
// dp, pixcel 相关
fun Float.dpToPx(): Float = this * resources.displayMetrics.density
fun Int.dpToPx(): Int = this * resources.displayMetrics.density.toInt()
fun Float.spToPx(): Float = this * resources.displayMetrics.scaledDensity

View File

@ -9,8 +9,6 @@ import com.alibaba.android.arouter.launcher.ARouter
import com.dylanc.loadingstateview.BgColorType import com.dylanc.loadingstateview.BgColorType
import com.remax.visualnovel.app.base.BaseBindingFragment import com.remax.visualnovel.app.base.BaseBindingFragment
import com.remax.visualnovel.databinding.FragmentMainActorBinding import com.remax.visualnovel.databinding.FragmentMainActorBinding
import com.remax.visualnovel.databinding.FragmentMainBookBinding
import com.remax.visualnovel.ui.main.book.BookListViewModel
import com.remax.visualnovel.utils.Routers import com.remax.visualnovel.utils.Routers
import com.remax.visualnovel.widget.custom.TagItem import com.remax.visualnovel.widget.custom.TagItem
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -56,7 +54,7 @@ class ActorListFragment : BaseBindingFragment<FragmentMainActorBinding>() {
TagItem("8", "LastLine"), TagItem("8", "LastLine"),
) )
tagFlowLayout.setTags(tags) tagFlowLayout.setTagDataList(tags)
tagFlowLayout.setOnTagClickListener { tag -> tagFlowLayout.setOnTagClickListener { tag ->
Toast.makeText(context, "Clicked: ${tag.text}", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Clicked: ${tag.text}", Toast.LENGTH_SHORT).show()

View File

@ -7,11 +7,16 @@ import android.graphics.drawable.GradientDrawable
import android.text.TextUtils import android.text.TextUtils
import android.util.AttributeSet import android.util.AttributeSet
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.remax.visualnovel.R import com.remax.visualnovel.R
import com.remax.visualnovel.extension.dpToPx
import com.remax.visualnovel.extension.spToPx
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import androidx.core.content.withStyledAttributes
class TagFlowLayout2 @JvmOverloads constructor( class TagFlowLayout2 @JvmOverloads constructor(
context: Context, context: Context,
@ -20,26 +25,27 @@ class TagFlowLayout2 @JvmOverloads constructor(
) : ViewGroup(context, attrs, defStyleAttr) { ) : ViewGroup(context, attrs, defStyleAttr) {
// 属性变量 // 属性变量
private var horizontalSpacing = 20f.dpToPx() private var horizontalSpacing = 10f.dpToPx()
private var verticalSpacing = 12f.dpToPx() private var verticalSpacing = 8f.dpToPx()
private var textSize = 14f.spToPx() private var textSize = 12f.spToPx()
private var textColor = Color.WHITE private var textColor = context.resources.getColor(R.color.tag_text_color)
private var tagBackground: Drawable? = null private var tagBackground: Drawable? = null
private var maxLines = Int.MAX_VALUE private var maxLinesWhileShrink = 1
private var eachLineMaxTagNum = 2
private var expandIndicator: Drawable? = null private var expandIndicator: Drawable? = null
private var collapseIndicator: Drawable? = null private var shrinkIndicator: Drawable? = null
private var eachLineMaxTagNum = 2 //一行最大标签宽度数
// 状态变量 // 状态变量
private var isExpanded = false private var isExpanded = false
private var actualLineCount = 0 private var actualLineCount = 0
private var showExpandButton = false private var showExpandButton = false
private var eachLineAvailableWidth = 0 // 可用宽度 private var eachLineAvailableWidth = 0
// 数据 // 数据
private val tagItems = mutableListOf<TagItem>() private val tagItems = mutableListOf<TagItem>()
private val tagViews = mutableListOf<TextView>() private val tagViews = mutableListOf<TextView>()
private lateinit var expandButton: TextView private lateinit var expandIcon: ImageView
// 监听器 // 监听器
private var onTagClickListener: ((TagItem) -> Unit)? = null private var onTagClickListener: ((TagItem) -> Unit)? = null
@ -51,26 +57,27 @@ class TagFlowLayout2 @JvmOverloads constructor(
} }
private fun initAttributes(attrs: AttributeSet?) { private fun initAttributes(attrs: AttributeSet?) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.TagFlowLayout2) context.withStyledAttributes(attrs, R.styleable.TagFlowLayout2) {
horizontalSpacing = typedArray.getDimension( horizontalSpacing = getDimension(
R.styleable.TagFlowLayout2_tag_horizontal_spacing, horizontalSpacing R.styleable.TagFlowLayout2_tag_horizontal_spacing, horizontalSpacing
) )
verticalSpacing = typedArray.getDimension( verticalSpacing = getDimension(
R.styleable.TagFlowLayout2_tag_vertical_spacing, verticalSpacing R.styleable.TagFlowLayout2_tag_vertical_spacing, verticalSpacing
) )
textSize = typedArray.getDimension( textSize = getDimension(
R.styleable.TagFlowLayout2_tag_text_size, textSize R.styleable.TagFlowLayout2_tag_text_size, textSize
) )
textColor = typedArray.getColor( textColor = getColor(
R.styleable.TagFlowLayout2_tag_text_color, textColor R.styleable.TagFlowLayout2_tag_text_color, textColor
) )
tagBackground = typedArray.getDrawable(R.styleable.TagFlowLayout2_tag_background) tagBackground = getDrawable(R.styleable.TagFlowLayout2_tag_background)
maxLines = typedArray.getInt(R.styleable.TagFlowLayout2_tag_max_lines, Int.MAX_VALUE) maxLinesWhileShrink = getInt(R.styleable.TagFlowLayout2_tag_max_lines, 1)
expandIndicator = typedArray.getDrawable(R.styleable.TagFlowLayout2_expand_indicator_drawable) eachLineMaxTagNum = getInt(R.styleable.TagFlowLayout2_tag_each_line_max_num, 2)
collapseIndicator = typedArray.getDrawable(R.styleable.TagFlowLayout2_collapse_indicator_drawable)
eachLineMaxTagNum = typedArray.getInt(R.styleable.TagFlowLayout2_each_line_max_num, 2) expandIndicator = getDrawable(R.styleable.TagFlowLayout2_tag_expand_drawable)
typedArray.recycle() shrinkIndicator = getDrawable(R.styleable.TagFlowLayout2_tag_shrink_drawable)
}
if (tagBackground == null) { if (tagBackground == null) {
@ -79,8 +86,8 @@ class TagFlowLayout2 @JvmOverloads constructor(
if (expandIndicator == null) { if (expandIndicator == null) {
expandIndicator = ContextCompat.getDrawable(context, R.mipmap.tag_flow_expand) expandIndicator = ContextCompat.getDrawable(context, R.mipmap.tag_flow_expand)
} }
if (collapseIndicator == null) { if (shrinkIndicator == null) {
collapseIndicator = ContextCompat.getDrawable(context, R.mipmap.tag_flow_shrink) shrinkIndicator = ContextCompat.getDrawable(context, R.mipmap.tag_flow_shrink)
} }
} }
@ -92,38 +99,26 @@ class TagFlowLayout2 @JvmOverloads constructor(
} }
private fun initExpandButton() { private fun initExpandButton() {
expandButton = TextView(context).apply { expandIcon = AppCompatImageView(context).apply {
text = "展开"
setCompoundDrawablesWithIntrinsicBounds(null, null, expandIndicator, null)
compoundDrawablePadding = 4.dpToPx()
setTextColor(Color.parseColor("#8A8A8E"))
textSize = 12f textSize = 12f
setPadding(12.dpToPx(), 6.dpToPx(), 8.dpToPx(), 6.dpToPx()) setPadding(5.dpToPx(), 5.dpToPx(), 5.dpToPx(), 5.dpToPx())
background = createExpandButtonBackground() bringToFront()
setOnClickListener { setOnClickListener {
toggleExpandState() toggleExpandState()
} }
} }
addView(expandButton) addView(expandIcon)
updateExpandButton()
} }
private fun createExpandButtonBackground(): Drawable {
val gradientDrawable = GradientDrawable()
gradientDrawable.cornerRadius = 16f.dpToPx()
gradientDrawable.setColor(Color.parseColor("#E5E5EA"))
gradientDrawable.setStroke(1.dpToPx(), Color.parseColor("#C6C6C8"))
return gradientDrawable
}
// 设置标签数据 fun setTagDataList(tags: List<TagItem>) {
fun setTags(tags: List<TagItem>) {
tagItems.clear() tagItems.clear()
tagViews.forEach { removeView(it) } tagViews.forEach { removeView(it) }
tagViews.clear() tagViews.clear()
tagItems.addAll(tags) tagItems.addAll(tags)
tags.forEach { tag -> tags.forEach { tag ->
val textView = createTagView(tag) val textView = createTagView(tag)
tagViews.add(textView) tagViews.add(textView)
@ -138,7 +133,7 @@ class TagFlowLayout2 @JvmOverloads constructor(
text = tag.text text = tag.text
setTextColor(textColor) setTextColor(textColor)
textSize = textSize / resources.displayMetrics.scaledDensity textSize = textSize / resources.displayMetrics.scaledDensity
setPadding(16.dpToPx(), 8.dpToPx(), 16.dpToPx(), 8.dpToPx()) setPadding(10.dpToPx(), 7.dpToPx(), 10.dpToPx(), 7.dpToPx())
setBackgroundResource(R.drawable.tag_flow_item_bg) setBackgroundResource(R.drawable.tag_flow_item_bg)
isSingleLine = true isSingleLine = true
ellipsize = TextUtils.TruncateAt.END ellipsize = TextUtils.TruncateAt.END
@ -172,8 +167,8 @@ class TagFlowLayout2 @JvmOverloads constructor(
var curLineTotalWidth = 0 var curLineTotalWidth = 0
var curLineTotalHeight = 0 var curLineTotalHeight = 0
var lineCount = 0 var lineIndex = 0
val maxDisplayLines = if (isExpanded) Int.MAX_VALUE else maxLines val maxDisplayLines = if (isExpanded) Int.MAX_VALUE else maxLinesWhileShrink
tagViews.forEach { view -> tagViews.forEach { view ->
val childWidth = view.measuredWidth val childWidth = view.measuredWidth
@ -181,16 +176,15 @@ class TagFlowLayout2 @JvmOverloads constructor(
// 检查是否需要换行(考虑水平间距) // 检查是否需要换行(考虑水平间距)
val curLineNeedTotalWidth: Int = if (curLineTotalWidth == 0) childWidth val curLineNeedTotalWidth: Int = if (curLineTotalWidth == 0) childWidth
else curLineTotalWidth + horizontalSpacing.toInt() + childWidth else curLineTotalWidth + horizontalSpacing.toInt() + childWidth
if (curLineNeedTotalWidth > eachLineAvailableWidth) { if (curLineNeedTotalWidth > eachLineAvailableWidth) { // 换行处理
// 换行处理 lineIndex++
lineCount++ if (lineIndex >= maxDisplayLines) {
if (lineCount >= maxDisplayLines) {
return@forEach return@forEach
} }
totalNeedHeight += curLineTotalHeight + (if (lineCount >= 1) verticalSpacing.toInt() else 0) totalNeedHeight += curLineTotalHeight + (if (lineIndex >= 1) verticalSpacing.toInt() else 0)
curLineTotalWidth = childWidth curLineTotalWidth = childWidth
curLineTotalHeight = childHeight curLineTotalHeight = childHeight
} else { } else {
@ -200,21 +194,21 @@ class TagFlowLayout2 @JvmOverloads constructor(
} }
// 添加最后一行高度 // 添加最后一行高度
if (lineCount < maxDisplayLines && tagViews.isNotEmpty()) { if (tagViews.isNotEmpty()) {
totalNeedHeight += curLineTotalHeight totalNeedHeight += curLineTotalHeight
} }
// 添加padding // 添加padding
totalNeedHeight += paddingTop + paddingBottom totalNeedHeight += paddingTop + paddingBottom
actualLineCount = lineCount + 1 actualLineCount = lineIndex + 1
showExpandButton = actualLineCount > maxLines && !isExpanded showExpandButton = actualLineCount > maxLinesWhileShrink && !isExpanded
// 测量展开按钮 // 测量展开按钮
if (showExpandButton) { if (showExpandButton) {
val buttonWidthSpec = MeasureSpec.makeMeasureSpec(eachLineAvailableWidth, MeasureSpec.AT_MOST) val buttonWidthSpec = MeasureSpec.makeMeasureSpec(eachLineAvailableWidth, MeasureSpec.AT_MOST)
val buttonHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) val buttonHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
expandButton.measure(buttonWidthSpec, buttonHeightSpec) expandIcon.measure(buttonWidthSpec, buttonHeightSpec)
} }
setMeasuredDimension(width, totalNeedHeight) setMeasuredDimension(width, totalNeedHeight)
@ -230,7 +224,7 @@ class TagFlowLayout2 @JvmOverloads constructor(
var currentTop = paddingTop var currentTop = paddingTop
var currentLineHeight = 0 var currentLineHeight = 0
var lineCount = 0 var lineCount = 0
val maxDisplayLines = if (isExpanded) Int.MAX_VALUE else maxLines val maxDisplayLines = if (isExpanded) Int.MAX_VALUE else maxLinesWhileShrink
// 布局可见的标签 // 布局可见的标签
for (i in tagViews.indices) { for (i in tagViews.indices) {
@ -264,22 +258,22 @@ class TagFlowLayout2 @JvmOverloads constructor(
private fun layoutExpandButton(parentWidth: Int, currentTop: Int, currentLineHeight: Int) { private fun layoutExpandButton(parentWidth: Int, currentTop: Int, currentLineHeight: Int) {
if (showExpandButton) { if (showExpandButton) {
expandButton.visibility = VISIBLE expandIcon.visibility = VISIBLE
val buttonWidth = expandButton.measuredWidth val buttonWidth = expandIcon.measuredWidth
val buttonHeight = expandButton.measuredHeight val buttonHeight = expandIcon.measuredHeight
// 计算按钮位置(在当前行右侧) // 计算按钮位置(在当前行右侧)
val buttonLeft = parentWidth - paddingRight - buttonWidth val buttonLeft = parentWidth - paddingRight - buttonWidth
val buttonTop = currentTop + (currentLineHeight - buttonHeight) / 2 val buttonTop = currentTop + (currentLineHeight - buttonHeight) / 2
expandButton.layout( expandIcon.layout(
buttonLeft, buttonLeft,
buttonTop, buttonTop,
buttonLeft + buttonWidth, buttonLeft + buttonWidth,
buttonTop + buttonHeight buttonTop + buttonHeight
) )
} else { } else {
expandButton.visibility = GONE expandIcon.visibility = GONE
} }
} }
@ -291,12 +285,10 @@ class TagFlowLayout2 @JvmOverloads constructor(
} }
private fun updateExpandButton() { private fun updateExpandButton() {
val indicator = if (isExpanded) collapseIndicator else expandIndicator val indicator = if (isExpanded) shrinkIndicator else expandIndicator
expandButton.setCompoundDrawablesWithIntrinsicBounds(null, null, indicator, null) expandIcon.setImageDrawable(indicator)
expandButton.text = if (isExpanded) "收起" else "展开"
} }
// 公共方法
fun setOnTagClickListener(listener: (TagItem) -> Unit) { fun setOnTagClickListener(listener: (TagItem) -> Unit) {
onTagClickListener = listener onTagClickListener = listener
} }
@ -311,7 +303,7 @@ class TagFlowLayout2 @JvmOverloads constructor(
} }
} }
fun collapse() { fun shrink() {
if (isExpanded) { if (isExpanded) {
toggleExpandState() toggleExpandState()
} }
@ -319,15 +311,11 @@ class TagFlowLayout2 @JvmOverloads constructor(
fun isExpanded(): Boolean = isExpanded fun isExpanded(): Boolean = isExpanded
// 设置最大标签数
fun setMaxTagsNumEachLine(maxTagNumForEachLine: Int) { fun setMaxTagsNumEachLine(maxTagNumForEachLine: Int) {
require(maxTagNumForEachLine >= 1 && maxTagNumForEachLine <= 10) { "Ratio must be between 1 and 10" } require(maxTagNumForEachLine >= 1 && maxTagNumForEachLine <= 10) { "tags numb in one line must be between 1 and 8" }
eachLineMaxTagNum = maxTagNumForEachLine eachLineMaxTagNum = maxTagNumForEachLine
requestLayout() requestLayout()
} }
// 扩展函数
private fun Float.dpToPx(): Float = this * resources.displayMetrics.density
private fun Int.dpToPx(): Int = (this * resources.displayMetrics.density).toInt()
private fun Float.spToPx(): Float = this * resources.displayMetrics.scaledDensity
} }

View File

@ -2,5 +2,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" > android:shape="rectangle" >
<corners android:radius="15dp" /> <corners android:radius="15dp" />
<solid android:color="@color/tag_bg_gray" /> <solid android:color="@color/tag_bg_color" />
</shape> </shape>

View File

@ -36,9 +36,10 @@
app:tag_horizontal_spacing="10dp" app:tag_horizontal_spacing="10dp"
app:tag_vertical_spacing="8dp" app:tag_vertical_spacing="8dp"
app:tag_text_size="12sp" app:tag_text_size="12sp"
app:tag_max_lines="1"
app:tag_text_color="@color/white" app:tag_text_color="@color/white"
app:each_line_max_num="2" app:tag_each_line_max_num="2"
app:expand_indicator_drawable="@mipmap/tag_flow_expand" app:tag_expand_drawable="@mipmap/tag_flow_expand"
app:collapse_indicator_drawable="@mipmap/tag_flow_shrink" /> app:tag_shrink_drawable="@mipmap/tag_flow_shrink" />
</LinearLayout> </LinearLayout>

View File

@ -1441,9 +1441,9 @@
<attr name="tag_text_color" format="color" /> <attr name="tag_text_color" format="color" />
<attr name="tag_background" format="reference" /> <attr name="tag_background" format="reference" />
<attr name="tag_max_lines" format="integer" /> <attr name="tag_max_lines" format="integer" />
<attr name="expand_indicator_drawable" format="reference" /> <attr name="tag_expand_drawable" format="reference" />
<attr name="collapse_indicator_drawable" format="reference" /> <attr name="tag_shrink_drawable" format="reference" />
<attr name="each_line_max_num" format="integer" /> <attr name="tag_each_line_max_num" format="integer" />
</declare-styleable> </declare-styleable>

View File

@ -182,7 +182,9 @@
<!-- new added --> <!-- new added -->
<color name="tag_bg_gray">#ffbac5d2</color> <color name="tag_bg_color">#ffbac5d2</color>
<color name="tag_text_color">#ffe5f1ff</color>
</resources> </resources>