Replace RX/MIC bars with S-meter (S1-S9, +20..+60, peak-hold)
This commit is contained in:
@@ -35,6 +35,7 @@ import ru.ua1zbe.svxremote.prefs.Prefs
|
|||||||
import ru.ua1zbe.svxremote.proto.MsgParser
|
import ru.ua1zbe.svxremote.proto.MsgParser
|
||||||
import ru.ua1zbe.svxremote.proto.ReflectorClient
|
import ru.ua1zbe.svxremote.proto.ReflectorClient
|
||||||
import ru.ua1zbe.svxremote.ui.PttButton
|
import ru.ua1zbe.svxremote.ui.PttButton
|
||||||
|
import ru.ua1zbe.svxremote.ui.SMeterView
|
||||||
import ru.ua1zbe.svxremote.ui.SoundMeterPreviewView
|
import ru.ua1zbe.svxremote.ui.SoundMeterPreviewView
|
||||||
|
|
||||||
private const val REQ_MIC = 1001
|
private const val REQ_MIC = 1001
|
||||||
@@ -58,10 +59,8 @@ class MainActivity : Activity(), SvxService.Listener {
|
|||||||
private lateinit var lblTxState: TextView
|
private lateinit var lblTxState: TextView
|
||||||
private lateinit var lblConnectHint: TextView
|
private lateinit var lblConnectHint: TextView
|
||||||
private lateinit var connectZone: View
|
private lateinit var connectZone: View
|
||||||
private lateinit var rxBarFill: View
|
private lateinit var rxMeter: SMeterView
|
||||||
private lateinit var rxDb: TextView
|
private lateinit var micMeter: SMeterView
|
||||||
private lateinit var micBarFill: View
|
|
||||||
private lateinit var micDb: TextView
|
|
||||||
private lateinit var pttButton: PttButton
|
private lateinit var pttButton: PttButton
|
||||||
private lateinit var btnSettings: ImageButton
|
private lateinit var btnSettings: ImageButton
|
||||||
private lateinit var btnMonitor: ImageButton
|
private lateinit var btnMonitor: ImageButton
|
||||||
@@ -134,10 +133,9 @@ class MainActivity : Activity(), SvxService.Listener {
|
|||||||
connectZone = findViewById(R.id.connect_zone)
|
connectZone = findViewById(R.id.connect_zone)
|
||||||
lblCallsign = findViewById(R.id.lbl_callsign)
|
lblCallsign = findViewById(R.id.lbl_callsign)
|
||||||
lblTg = findViewById(R.id.lbl_tg)
|
lblTg = findViewById(R.id.lbl_tg)
|
||||||
rxBarFill = findViewById(R.id.rx_bar_fill)
|
rxMeter = findViewById(R.id.rx_meter)
|
||||||
rxDb = findViewById(R.id.rx_db)
|
micMeter = findViewById(R.id.mic_meter)
|
||||||
micBarFill = findViewById(R.id.mic_bar_fill)
|
micMeter.setColor(0xFFF85149.toInt())
|
||||||
micDb = findViewById(R.id.mic_db)
|
|
||||||
pttButton = findViewById(R.id.ptt_button)
|
pttButton = findViewById(R.id.ptt_button)
|
||||||
btnSettings = findViewById(R.id.btn_settings)
|
btnSettings = findViewById(R.id.btn_settings)
|
||||||
btnMonitor = findViewById(R.id.btn_monitor)
|
btnMonitor = findViewById(R.id.btn_monitor)
|
||||||
@@ -482,17 +480,10 @@ class MainActivity : Activity(), SvxService.Listener {
|
|||||||
|
|
||||||
override fun onLevel(source: Int, level: Float) {
|
override fun onLevel(source: Int, level: Float) {
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
val db = if (level <= 0.001f) {
|
|
||||||
getString(R.string.meter_idle)
|
|
||||||
} else {
|
|
||||||
getString(R.string.meter_db, (level * 60).toInt().coerceIn(1, 60))
|
|
||||||
}
|
|
||||||
if (source == 0) { // SRC_MIC
|
if (source == 0) { // SRC_MIC
|
||||||
micBarFill.scaleX = level.coerceIn(0f, 1f)
|
micMeter.setLevel(level)
|
||||||
micDb.text = db
|
|
||||||
} else { // SRC_SPEAKER
|
} else { // SRC_SPEAKER
|
||||||
rxBarFill.scaleX = level.coerceIn(0f, 1f)
|
rxMeter.setLevel(level)
|
||||||
rxDb.text = db
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
124
app/src/main/java/ru/ua1zbe/svxremote/ui/SMeterView.kt
Normal file
124
app/src/main/java/ru/ua1zbe/svxremote/ui/SMeterView.kt
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package ru.ua1zbe.svxremote.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* S-метр: полоса со шкалой S1–S9, +20…+60, заливкой уровня, белой пик-меткой
|
||||||
|
* (peak-hold) и цифрой текущего уровня справа («S9», «+43» или «---»).
|
||||||
|
* Уровень 0..1; S9 = 55% пути, +60 = 88%.
|
||||||
|
*/
|
||||||
|
class SMeterView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null
|
||||||
|
) : View(context, attrs) {
|
||||||
|
|
||||||
|
private var fillColor = 0xFF58A6FF.toInt()
|
||||||
|
private val track = Paint().apply { color = 0xFF21262D.toInt() }
|
||||||
|
private val fill = Paint().apply { color = fillColor }
|
||||||
|
private val tick = Paint().apply { color = 0xFF8B949E.toInt(); strokeWidth = dp(1f) }
|
||||||
|
private val tickMajor = Paint().apply { color = 0xFF8B949E.toInt(); strokeWidth = dp(1.5f) }
|
||||||
|
private val peakPaint = Paint().apply { color = 0xFFFFFFFF.toInt(); strokeWidth = dp(2f) }
|
||||||
|
private val label = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
color = 0xFF8B949E.toInt()
|
||||||
|
textSize = sp(9)
|
||||||
|
}
|
||||||
|
private val valuePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
color = 0xFFE6EDF3.toInt()
|
||||||
|
textSize = sp(12)
|
||||||
|
textAlign = Paint.Align.RIGHT
|
||||||
|
}
|
||||||
|
private val idlePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
color = 0xFF8B949E.toInt()
|
||||||
|
textSize = sp(12)
|
||||||
|
textAlign = Paint.Align.RIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var target = 0f
|
||||||
|
private var shown = 0f
|
||||||
|
private var peak = 0f
|
||||||
|
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
private val ticker = object : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
// Сглаживание уровня и медленный спад пика
|
||||||
|
shown += (target - shown) * 0.35f
|
||||||
|
if (Math.abs(target - shown) < 0.002f) shown = target
|
||||||
|
peak = maxOf(peak * 0.985f, shown)
|
||||||
|
if (peak < 0.003f) peak = 0f
|
||||||
|
invalidate()
|
||||||
|
handler.postDelayed(this, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setColor(color: Int) {
|
||||||
|
fillColor = color
|
||||||
|
fill.color = color
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setLevel(level: Float) {
|
||||||
|
target = level.coerceIn(0f, 1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow()
|
||||||
|
handler.post(ticker)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromWindow() {
|
||||||
|
handler.removeCallbacksAndMessages(null)
|
||||||
|
super.onDetachedFromWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dp(v: Float): Float = v * resources.displayMetrics.density
|
||||||
|
private fun sp(v: Int): Float = v * resources.displayMetrics.scaledDensity
|
||||||
|
|
||||||
|
private fun levelText(level: Float): String = when {
|
||||||
|
level < 0.005f -> "---"
|
||||||
|
level < 0.55f -> "S${(level / 0.55f * 9f).roundToInt().coerceIn(1, 9)}"
|
||||||
|
else -> "+${(((level - 0.55f) / 0.45f) * 60f).roundToInt().coerceIn(1, 99)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) {
|
||||||
|
val w = width.toFloat()
|
||||||
|
val h = height.toFloat()
|
||||||
|
val left = dp(2f)
|
||||||
|
val right = w - dp(2f)
|
||||||
|
val bandTop = dp(4f)
|
||||||
|
val bandH = dp(14f)
|
||||||
|
|
||||||
|
// полоса
|
||||||
|
canvas.drawRect(left, bandTop, right, bandTop + bandH, track)
|
||||||
|
val lvl = shown.coerceIn(0f, 1f)
|
||||||
|
if (lvl > 0.002f) {
|
||||||
|
canvas.drawRect(left, bandTop, left + lvl * (right - left), bandTop + bandH, fill)
|
||||||
|
}
|
||||||
|
|
||||||
|
// деления: S1..S9, +20..+60
|
||||||
|
val marks = listOf("S1" to 0.11f, "S3" to 0.22f, "S5" to 0.33f, "S7" to 0.44f,
|
||||||
|
"S9" to 0.55f, "+20" to 0.66f, "+40" to 0.77f, "+60" to 0.88f)
|
||||||
|
for ((text, pos) in marks) {
|
||||||
|
val mx = left + pos * (right - left)
|
||||||
|
val major = pos == 0.55f || pos == 0.88f
|
||||||
|
canvas.drawLine(mx, bandTop, mx, bandTop + bandH, if (major) tickMajor else tick)
|
||||||
|
canvas.drawText(text, mx - dp(8f), bandTop + bandH + dp(11f), label)
|
||||||
|
}
|
||||||
|
|
||||||
|
// пик-метка
|
||||||
|
if (peak > 0.003f) {
|
||||||
|
val px = left + peak.coerceIn(0f, 1f) * (right - left)
|
||||||
|
canvas.drawLine(px, bandTop - dp(3f), px, bandTop + bandH + dp(3f), peakPaint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// цифра уровня справа от полосы (и чуть выше делений)
|
||||||
|
val p = if (target < 0.005f) idlePaint else valuePaint
|
||||||
|
canvas.drawText(levelText(target), right, bandTop + bandH - dp(3f), p)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -141,40 +141,22 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="center_vertical">
|
android:gravity="top">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="34dp"
|
android:layout_width="34dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
android:text="@string/meter_rx"
|
android:text="@string/meter_rx"
|
||||||
android:textColor="@color/text_secondary"
|
android:textColor="@color/text_secondary"
|
||||||
android:textSize="13sp"
|
android:textSize="13sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<FrameLayout
|
<ru.ua1zbe.svxremote.ui.SMeterView
|
||||||
|
android:id="@+id/rx_meter"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="16dp"
|
android:layout_height="52dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1" />
|
||||||
android:background="@drawable/bg_meter">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/rx_bar_fill"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="@drawable/bg_meter_fill_rx"
|
|
||||||
android:pivotX="0"
|
|
||||||
android:scaleX="0" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/rx_db"
|
|
||||||
android:layout_width="64dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="end"
|
|
||||||
android:fontFamily="monospace"
|
|
||||||
android:text="@string/meter_idle"
|
|
||||||
android:textColor="@color/text_primary"
|
|
||||||
android:textSize="13sp" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -182,40 +164,22 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="center_vertical">
|
android:gravity="top">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="34dp"
|
android:layout_width="34dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
android:text="@string/meter_mic"
|
android:text="@string/meter_mic"
|
||||||
android:textColor="@color/text_secondary"
|
android:textColor="@color/text_secondary"
|
||||||
android:textSize="13sp"
|
android:textSize="13sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<FrameLayout
|
<ru.ua1zbe.svxremote.ui.SMeterView
|
||||||
|
android:id="@+id/mic_meter"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="16dp"
|
android:layout_height="52dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1" />
|
||||||
android:background="@drawable/bg_meter">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/mic_bar_fill"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="@drawable/bg_meter_fill_mic"
|
|
||||||
android:pivotX="0"
|
|
||||||
android:scaleX="0" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/mic_db"
|
|
||||||
android:layout_width="64dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="end"
|
|
||||||
android:fontFamily="monospace"
|
|
||||||
android:text="@string/meter_idle"
|
|
||||||
android:textColor="@color/text_primary"
|
|
||||||
android:textSize="13sp" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user