字体选择器 回调更新

This commit is contained in:
renhaoting 2025-10-31 15:45:40 +08:00
parent 8dc7d2072f
commit ce45b71448
4 changed files with 72 additions and 135 deletions

View File

@ -14,17 +14,18 @@ class FontSetView @JvmOverloads constructor(
) : LinearLayout(context, attrs, defStyleAttr) {
companion object {
private var GAP = 2
private const val GAP = 2
private const val MIN_VALUE = 12
private const val MAX_VALUE = 20
}
private var mBinding: LayoutFontSetViewBinding
private var mBinding: LayoutFontSetViewBinding =
LayoutFontSetViewBinding.inflate(LayoutInflater.from(context), this, true)
private var mFontValue = 16 // 12, 14, 16, 18, 20
private val mFontMinValue = 16
private val mFontMaxValue = 20
private var mFontValue = MIN_VALUE + GAP * 2
init {
mBinding = LayoutFontSetViewBinding.inflate(LayoutInflater.from(context), this, true)
setupClickListeners()
}
@ -33,47 +34,32 @@ class FontSetView @JvmOverloads constructor(
private fun setupClickListeners() {
with (mBinding) {
ivFontPlus.setOnClickListener {
if (mFontValue > mFontMinValue) {
ivFontMinus.setOnClickListener {
if (mFontValue > MIN_VALUE) {
mFontValue -= GAP
}
tvFontValue.text = mFontValue.toString()
levelSeekbar.setLevel((mFontValue - 16)/GAP)
levelSeekbar.setLevel((mFontValue - MIN_VALUE)/GAP)
}
ivFontAdd.setOnClickListener {
if (mFontValue < mFontMinValue) {
if (mFontValue < MAX_VALUE) {
mFontValue += GAP
}
tvFontValue.text = mFontValue.toString()
levelSeekbar.setLevel((mFontValue - 16)/GAP)
levelSeekbar.setLevel((mFontValue - MIN_VALUE)/GAP)
}
levelSeekbar.setOnLevelChangeListener(object : LevelSeekBar.OnLevelChangeListener {
override fun onLevelChanged(
seekBar: LevelSeekBar,
level: Int,
fromUser: Boolean
) {
mFontValue = 16 + level * GAP
if (mFontValue > mFontMaxValue) {
mFontValue = mFontMaxValue
}
if (mFontValue < mFontMinValue) {
mFontValue = mFontMinValue
}
tvFontValue.text = mFontValue.toString()
levelSeekbar.setOnLevelChangeListener { level->
mFontValue = 16 + level * GAP
if (mFontValue > MAX_VALUE) {
mFontValue = MAX_VALUE
}
override fun onStartTrackingTouch(seekBar: LevelSeekBar) {
if (mFontValue < MIN_VALUE) {
mFontValue = MIN_VALUE
}
override fun onStopTrackingTouch(seekBar: LevelSeekBar) {
}
})
tvFontValue.text = mFontValue.toString()
}
}
}

View File

@ -12,14 +12,15 @@ import com.remax.visualnovel.utils.spannablex.utils.dp
import androidx.core.content.withStyledAttributes
class LevelSeekBar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var totalLevels = 5
private var currentLevel = 2
private var mTotalLevel = 5
private var mCurLevel = 2
// 尺寸
private var trackHeight = ResUtil.getPixelSize(R.dimen.dp_5).toFloat()
@ -46,32 +47,25 @@ class LevelSeekBar @JvmOverloads constructor(
// 监听器
private var onLevelChangeListener: OnLevelChangeListener? = null
private var isDragging = false
// 触摸相关
private var lastTouchX = 0f
interface OnLevelChangeListener {
fun onLevelChanged(seekBar: LevelSeekBar, level: Int, fromUser: Boolean)
fun onStartTrackingTouch(seekBar: LevelSeekBar)
fun onStopTrackingTouch(seekBar: LevelSeekBar)
fun onLevelChanged(level: Int)
}
init {
setupAttributes(attrs)
setupPaints()
setBackgroundResource(R.color.red_ff3b30)
}
private fun setupAttributes(attrs: AttributeSet?) {
attrs?.let {
context.withStyledAttributes(it, R.styleable.CustomLevelSeekBar) {
totalLevels = getInt(R.styleable.CustomLevelSeekBar_totalLevels, totalLevels)
currentLevel = getInt(R.styleable.CustomLevelSeekBar_currentLevel, currentLevel)
.coerceIn(0, totalLevels - 1)
mTotalLevel = getInt(R.styleable.CustomLevelSeekBar_totalLevels, mTotalLevel)
mCurLevel = getInt(R.styleable.CustomLevelSeekBar_currentLevel, mCurLevel)
.coerceIn(0, mTotalLevel - 1)
trackColor = getColor(R.styleable.CustomLevelSeekBar_trackColor, trackColor)
activeTrackColor =
@ -118,124 +112,94 @@ class LevelSeekBar @JvmOverloads constructor(
}
private fun drawNodes(canvas: Canvas) {
if (totalLevels <= 1) return
if (mTotalLevel <= 1) return
val centerY = height / 2f
for (i in 0 until totalLevels) {
for (i in 0 until mTotalLevel) {
val x = calculatePositionForLevel(i)
nodePaint.color = if (i <= currentLevel) activeNodeColor else nodeColor
//canvas.drawCircle(x, centerY, nodeRadius, nodePaint)
val trackRect = RectF(x - nodeWidth/2, centerY - nodeHeight/2 + 6, x + nodeWidth/2, centerY + nodeHeight/2)
nodePaint.color = if (i <= mCurLevel) activeNodeColor else nodeColor
val trackRect = RectF(x - nodeWidth/2, centerY - nodeHeight/2, x + nodeWidth/2, centerY + nodeHeight/2)
canvas.drawRoundRect(trackRect, trackEndRadius, trackEndRadius, nodePaint)
}
}
private fun drawThumb(canvas: Canvas) {
if (totalLevels <= 1) return
if (mTotalLevel <= 1) return
val centerY = height / 2f
val thumbX = calculatePositionForLevel(currentLevel)
val thumbX = calculatePositionForLevel(mCurLevel)
canvas.drawCircle(thumbX, centerY, thumbRadius - 1F.dp.toFloat(), thumbPaint)
}
private fun calculatePositionForLevel(level: Int): Float {
if (totalLevels <= 1) return width / 2f
if (mTotalLevel <= 1) return width / 2f
val availableWidth = width - 2 * thumbRadius
return thumbRadius + (availableWidth * level.toFloat() / (totalLevels - 1))
return thumbRadius + (availableWidth * level.toFloat() / (mTotalLevel - 1))
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
if (isPointInThumb(event.x, event.y) || isPointInTrack(event.x, event.y)) {
isDragging = true
lastTouchX = event.x
onLevelChangeListener?.onStartTrackingTouch(this)
handleTouch(event.x)
parent?.requestDisallowInterceptTouchEvent(true)
return true
}
lastTouchX = event.x
handleTouch(event.x)
parent?.requestDisallowInterceptTouchEvent(true)
return true
}
MotionEvent.ACTION_MOVE -> {
if (isDragging) {
lastTouchX = event.x
handleTouch(event.x)
return true
}
lastTouchX = event.x
handleTouch(event.x)
return true
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
if (isDragging) {
isDragging = false
snapToNearestLevel(lastTouchX)
onLevelChangeListener?.onStopTrackingTouch(this)
parent?.requestDisallowInterceptTouchEvent(false)
return true
}
snapToNearestLevel(lastTouchX)
parent?.requestDisallowInterceptTouchEvent(false)
return true
}
}
return super.onTouchEvent(event)
}
private fun isPointInThumb(x: Float, y: Float): Boolean {
val thumbX = calculatePositionForLevel(currentLevel)
val centerY = height / 2f
val distance = Math.sqrt(
(x - thumbX) * (x - thumbX) + (y - centerY) * (y - centerY).toDouble()
)
return distance <= thumbRadius
}
private fun isPointInTrack(x: Float, y: Float): Boolean {
val centerY = height / 2f
val trackTop = centerY - trackHeight / 2 - thumbRadius // 扩大触摸区域
val trackBottom = centerY + trackHeight / 2 + thumbRadius
return x in 0f..width.toFloat() && y in trackTop..trackBottom
}
private fun handleTouch(x: Float) {
if (totalLevels <= 1) return
if (mTotalLevel <= 1) return
val newLevel = calculateLevelForPosition(x)
if (newLevel != currentLevel) {
currentLevel = newLevel
if (newLevel != mCurLevel) {
mCurLevel = newLevel
invalidate()
onLevelChangeListener?.onLevelChanged(this, currentLevel, true)
}
}
private fun snapToNearestLevel(x: Float) {
if (totalLevels <= 1) return
if (mTotalLevel <= 1) return
val exactLevel = calculateExactLevelForPosition(x)
val newLevel = (exactLevel + 0.5f).toInt().coerceIn(0, totalLevels - 1)
val newLevel = (exactLevel + 0.5f).toInt().coerceIn(0, mTotalLevel - 1)
if (newLevel != currentLevel) {
currentLevel = newLevel
invalidate()
onLevelChangeListener?.onLevelChanged(this, currentLevel, true)
}
mCurLevel = newLevel
invalidate()
onLevelChangeListener?.onLevelChanged(mCurLevel)
}
private fun calculateLevelForPosition(x: Float): Int {
if (totalLevels <= 1) return 0
if (mTotalLevel <= 1) return 0
val availableWidth = width - 2 * thumbRadius
val progress = ((x - thumbRadius) / availableWidth).coerceIn(0f, 1f)
return (progress * (totalLevels - 1)).toInt().coerceIn(0, totalLevels - 1)
return (progress * (mTotalLevel - 1)).toInt().coerceIn(0, mTotalLevel - 1)
}
private fun calculateExactLevelForPosition(x: Float): Float {
if (totalLevels <= 1) return 0f
if (mTotalLevel <= 1) return 0f
val availableWidth = width - 2 * thumbRadius
val progress = ((x - thumbRadius) / availableWidth).coerceIn(0f, 1f)
return progress * (totalLevels - 1)
return progress * (mTotalLevel - 1)
}
@ -243,45 +207,32 @@ class LevelSeekBar @JvmOverloads constructor(
//---------------------------- public 设置方法 ---------------------------------//
fun setLevel(level: Int, fromUser: Boolean = false) {
val newLevel = level.coerceIn(0, totalLevels - 1)
if (newLevel != currentLevel) {
currentLevel = newLevel
val newLevel = level.coerceIn(0, mTotalLevel - 1)
if (newLevel != mCurLevel) {
mCurLevel = newLevel
invalidate()
onLevelChangeListener?.onLevelChanged(this, currentLevel, fromUser)
}
}
fun getLevel(): Int = currentLevel
fun getLevel(): Int = mCurLevel
fun setTotalLevels(levels: Int) {
if (levels > 0 && levels != totalLevels) {
totalLevels = levels
currentLevel = currentLevel.coerceIn(0, totalLevels - 1)
if (levels > 0 && levels != mTotalLevel) {
mTotalLevel = levels
mCurLevel = mCurLevel.coerceIn(0, mTotalLevel - 1)
invalidate()
}
}
fun getTotalLevels(): Int = totalLevels
fun setOnLevelChangeListener(listener: OnLevelChangeListener) {
this.onLevelChangeListener = listener
}
fun getTotalLevels(): Int = mTotalLevel
fun setOnLevelChangeListener(
onLevelChanged: (LevelSeekBar, Int, Boolean) -> Unit = { _, _, _ -> },
onStartTrackingTouch: (LevelSeekBar) -> Unit = {},
onStopTrackingTouch: (LevelSeekBar) -> Unit = {}
onLevelChanged: (Int) -> Unit
) {
this.onLevelChangeListener = object : OnLevelChangeListener {
override fun onLevelChanged(seekBar: LevelSeekBar, level: Int, fromUser: Boolean) {
onLevelChanged(seekBar, level, fromUser)
}
override fun onStartTrackingTouch(seekBar: LevelSeekBar) {
onStartTrackingTouch(seekBar)
}
override fun onStopTrackingTouch(seekBar: LevelSeekBar) {
onStopTrackingTouch(seekBar)
override fun onLevelChanged(level: Int) {
onLevelChanged(level)
}
}
}

View File

@ -38,14 +38,14 @@ class MaxNumView @JvmOverloads constructor(
private fun setupClickListeners() {
with (mBinding) {
ivLeftIcon.setOnClickListener {
mCurIndex = mCurIndex.takeUnless { it > 0 }?.minus(1) ?: mCurIndex
mCurIndex = mCurIndex.takeIf { it > 0 }?.minus(1) ?: mCurIndex
mCurValue = mFixedValueList[mCurIndex]
tvCenter.text = mCurValue.toString()
mEventListener?.onValueChanged(mCurValue)
}
ivRightIcon.setOnClickListener {
mCurIndex = mCurIndex.takeUnless { it < mFixedValueList.size }?.plus(1) ?: mCurIndex
mCurIndex = mCurIndex.takeIf { it < mFixedValueList.size - 1 }?.plus(1) ?: mCurIndex
mCurValue = mFixedValueList[mCurIndex]
tvCenter.text = mCurValue.toString()
mEventListener?.onValueChanged(mCurValue)

View File

@ -65,7 +65,7 @@
android:layout_toEndOf="@+id/left_container"
android:layout_marginTop="@dimen/dp_10">
<com.remax.visualnovel.widget.uitoken.view.UITokenImageView
android:id="@+id/iv_font_plus"
android:id="@+id/iv_font_minus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
@ -78,7 +78,7 @@
android:layout_width="match_parent"
android:layout_height="@dimen/dp_20"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/iv_font_plus"
android:layout_toEndOf="@id/iv_font_minus"
android:layout_toStartOf="@+id/iv_font_add"
android:layout_marginHorizontal="@dimen/dp_10"
/>