1.0.12: PTT-кнопка в стиле Zello (градиент, тень, глянец, ободок)

This commit is contained in:
ua1zbe
2026-08-21 11:39:46 +03:00
parent 7342dddd0b
commit 587d46578e

View File

@@ -3,15 +3,18 @@ package ru.ua1zbe.svxremote.ui
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.Path
import android.graphics.Shader
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import ru.ua1zbe.svxremote.R
/**
* Большая круглая кнопка PTT. Удерживание — передача.
* Большая круглая кнопка PTT в стиле Zello: градиентная заливка с глянцем,
* мягкая тень (плавающая кнопка) и тонкий ободок.
* Состояния: idle (синяя), нажата (красная), отключена (серая).
*/
class PttButton @JvmOverloads constructor(
@@ -19,8 +22,11 @@ class PttButton @JvmOverloads constructor(
attrs: AttributeSet? = null
) : View(context, attrs) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val ringPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val glossPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val micPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val clip = Path()
private var radius = 0f
private var centerX = 0f
@@ -31,7 +37,6 @@ class PttButton @JvmOverloads constructor(
set(value) {
field = value
if (!value) setPressed(false)
refreshColors()
invalidate()
}
@@ -50,22 +55,28 @@ class PttButton @JvmOverloads constructor(
}
init {
fillPaint.style = Paint.Style.FILL
fillPaint.setShadowLayer(dp(8).toFloat(), 0f, dp(4).toFloat(), Color.argb(80, 0, 0, 0))
ringPaint.style = Paint.Style.STROKE
ringPaint.strokeWidth = dp(3).toFloat()
ringPaint.color = Color.parseColor("#3D444D")
paint.style = Paint.Style.FILL
refreshColors()
ringPaint.strokeWidth = dp(2).toFloat()
glossPaint.style = Paint.Style.FILL
micPaint.style = Paint.Style.FILL
micPaint.color = Color.WHITE
isClickable = true
isFocusable = true
}
private fun refreshColors() {
paint.color = when {
!enabled2 -> Color.parseColor("#484F58")
isPressed -> Color.parseColor("#F85149")
else -> Color.parseColor("#1F6FEB")
}
invalidate()
/** (верхний цвет, нижний цвет) градиента заливки. */
private fun fillColors(): Pair<Int, Int> = when {
!enabled2 -> 0x565E68 to 0x3C424B
isPressed -> 0xFF8A80 to 0xDB3A30
else -> 0x4A9BFF to 0x1561D6
}
private fun ringColor(): Int = when {
!enabled2 -> 0x6B727B
isPressed -> 0xFFA79F
else -> 0x6FB0FF
}
private fun dp(v: Int): Int = (v * resources.displayMetrics.density).toInt()
@@ -74,32 +85,51 @@ class PttButton @JvmOverloads constructor(
super.onSizeChanged(w, h, oldw, oldh)
centerX = w / 2f
centerY = h / 2f
radius = minOf(w, h) / 2f - dp(16)
refreshColors()
radius = minOf(w, h) / 2f - dp(18)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val r = radius / (1f + if (isPressed) 0.06f else 0f)
canvas.drawCircle(centerX, centerY, r, paint)
canvas.drawCircle(centerX, centerY, r, ringPaint)
val cx = centerX
val cy = centerY
val (top, bottom) = fillColors()
// Иконка микрофона (как icons8: капсула + чашка + ножка + основание)
val micPaint = Paint(Paint.ANTI_ALIAS_FLAG)
micPaint.color = Color.WHITE
micPaint.style = Paint.Style.FILL
// Заливка с вертикальным градиентом (глянец)
fillPaint.shader = LinearGradient(
cx, cy - r, cx, cy + r, top, bottom, Shader.TileMode.CLAMP
)
canvas.drawCircle(cx, cy, r, fillPaint)
val x = centerX
val y = centerY
val s = r * 0.5f
// Тонкий ободок по краю
ringPaint.color = ringColor()
canvas.drawCircle(cx, cy, r - dp(1).toFloat(), ringPaint)
// Верхний глянец (белое затухание сверху)
glossPaint.shader = LinearGradient(
cx, cy - r, cx, cy + r * 0.18f,
Color.argb(70, 255, 255, 255), Color.TRANSPARENT, Shader.TileMode.CLAMP
)
clip.reset()
clip.addCircle(cx, cy, r, Path.Direction.CW)
canvas.save()
canvas.clipPath(clip)
canvas.drawRect(cx - r, cy - r, cx + r, cy + r, glossPaint)
canvas.restore()
glossPaint.shader = null
drawMic(canvas, cx, cy, r * 0.5f)
}
/** Иконка микрофона (капсула + держатель + ножка + основание). */
private fun drawMic(canvas: Canvas, x: Float, y: Float, s: Float) {
// корпус-капсула
val halfW = s * 0.34f
canvas.drawRoundRect(
x - halfW, y - s * 0.85f, x + halfW, y + s * 0.10f,
halfW, halfW, micPaint
)
// чашка-держатель (U-образная, заполненная)
// чашка-держатель (U-образная)
val cup = Path()
cup.fillType = Path.FillType.EVEN_ODD
cup.moveTo(x - s * 0.52f, y + s * 0.06f)
@@ -147,4 +177,4 @@ class PttButton @JvmOverloads constructor(
else -> true
}
}
}
}