字体选择器 回调更新

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

View File

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

View File

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

View File

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