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