From 587d46578edbbca34a3a75451d0afdea33a0aeb8 Mon Sep 17 00:00:00 2001 From: ua1zbe Date: Fri, 21 Aug 2026 11:39:46 +0300 Subject: [PATCH] =?UTF-8?q?1.0.12:=20PTT-=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B2=20=D1=81=D1=82=D0=B8=D0=BB=D0=B5=20Zello=20(?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D0=B4=D0=B8=D0=B5=D0=BD=D1=82,=20=D1=82?= =?UTF-8?q?=D0=B5=D0=BD=D1=8C,=20=D0=B3=D0=BB=D1=8F=D0=BD=D0=B5=D1=86,=20?= =?UTF-8?q?=D0=BE=D0=B1=D0=BE=D0=B4=D0=BE=D0=BA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ru/ua1zbe/svxremote/ui/PttButton.kt | 84 +++++++++++++------ 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/ru/ua1zbe/svxremote/ui/PttButton.kt b/app/src/main/java/ru/ua1zbe/svxremote/ui/PttButton.kt index 5ef3226..ccf7c88 100644 --- a/app/src/main/java/ru/ua1zbe/svxremote/ui/PttButton.kt +++ b/app/src/main/java/ru/ua1zbe/svxremote/ui/PttButton.kt @@ -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 = 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 } } -} \ No newline at end of file +}