1 parent
e4cf707d0a
commit
8f021959ef
6 files changed
+198
-74
No files matched your search
@@ -2,49 +2,131 @@ package io.github.transui.android;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.LinearGradient;
|
||||||
|
import android.graphics.Matrix;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.graphics.Shader;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
public final class AmplitudeView extends View {
|
public final class AmplitudeView extends View {
|
||||||
private static final int LEVEL_COUNT = 22;
|
private static final int LEVEL_COUNT = 192;
|
||||||
|
|
||||||
private final float[] levels = new float[LEVEL_COUNT];
|
private final float[] levels = new float[LEVEL_COUNT];
|
||||||
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
private final Paint fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
private final Paint edgePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
private final Paint centerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
private final Path waveform = new Path();
|
||||||
|
private final LinearGradient fillGradient;
|
||||||
|
private final Matrix gradientMatrix = new Matrix();
|
||||||
|
private final float sampleSpacing;
|
||||||
|
private final float minimumHalfHeight;
|
||||||
|
private final int signalColor;
|
||||||
|
private float previousLevel;
|
||||||
|
|
||||||
public AmplitudeView(Context context, AttributeSet attributes) {
|
public AmplitudeView(Context context, AttributeSet attributes) {
|
||||||
super(context, attributes);
|
super(context, attributes);
|
||||||
paint.setColor(context.getColor(R.color.signal));
|
float density = getResources().getDisplayMetrics().density;
|
||||||
paint.setStrokeCap(Paint.Cap.ROUND);
|
sampleSpacing = 5 * density;
|
||||||
|
minimumHalfHeight = 1.5f * density;
|
||||||
|
signalColor = context.getColor(R.color.signal);
|
||||||
|
|
||||||
|
fillPaint.setStyle(Paint.Style.FILL);
|
||||||
|
int transparentSignal = signalColor & 0x00ffffff;
|
||||||
|
fillGradient = new LinearGradient(
|
||||||
|
0, 0, 0, 1,
|
||||||
|
new int[]{transparentSignal, signalColor, transparentSignal},
|
||||||
|
new float[]{0, 0.5f, 1}, Shader.TileMode.CLAMP);
|
||||||
|
fillPaint.setShader(fillGradient);
|
||||||
|
edgePaint.setColor(signalColor);
|
||||||
|
edgePaint.setAlpha(210);
|
||||||
|
edgePaint.setStyle(Paint.Style.STROKE);
|
||||||
|
edgePaint.setStrokeWidth(1.5f * density);
|
||||||
|
edgePaint.setStrokeJoin(Paint.Join.ROUND);
|
||||||
|
centerPaint.setColor(signalColor);
|
||||||
|
centerPaint.setAlpha(70);
|
||||||
|
centerPaint.setStrokeWidth(density);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setAmplitude(float amplitude) {
|
void setAmplitude(float amplitude) {
|
||||||
|
float level = Math.max(0, Math.min(1, amplitude));
|
||||||
|
level = Math.max(level, previousLevel * 0.58f);
|
||||||
System.arraycopy(levels, 1, levels, 0, levels.length - 1);
|
System.arraycopy(levels, 1, levels, 0, levels.length - 1);
|
||||||
levels[levels.length - 1] = Math.max(0, Math.min(1, amplitude));
|
levels[levels.length - 1] = level;
|
||||||
|
previousLevel = level;
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
Arrays.fill(levels, 0);
|
Arrays.fill(levels, 0);
|
||||||
|
previousLevel = 0;
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
|
||||||
|
super.onSizeChanged(width, height, oldWidth, oldHeight);
|
||||||
|
float top = getPaddingTop();
|
||||||
|
float contentHeight = Math.max(1, height - getPaddingTop() - getPaddingBottom());
|
||||||
|
gradientMatrix.setScale(1, contentHeight);
|
||||||
|
gradientMatrix.postTranslate(0, top);
|
||||||
|
fillGradient.setLocalMatrix(gradientMatrix);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDraw(Canvas canvas) {
|
protected void onDraw(Canvas canvas) {
|
||||||
super.onDraw(canvas);
|
super.onDraw(canvas);
|
||||||
float availableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
|
float availableWidth = Math.max(0, getWidth() - getPaddingLeft() - getPaddingRight());
|
||||||
float gap = availableWidth / (LEVEL_COUNT * 2 - 1);
|
int visibleCount = Math.min(LEVEL_COUNT,
|
||||||
float strokeWidth = Math.max(2, gap);
|
Math.max(2, (int) (availableWidth / sampleSpacing) + 1));
|
||||||
|
float step = availableWidth / (visibleCount - 1);
|
||||||
|
float startX = getPaddingLeft();
|
||||||
float centerY = getHeight() / 2f;
|
float centerY = getHeight() / 2f;
|
||||||
float maximumHeight = Math.max(0, getHeight() - getPaddingTop() - getPaddingBottom());
|
float maximumHalfHeight = Math.max(minimumHalfHeight,
|
||||||
paint.setStrokeWidth(strokeWidth);
|
(getHeight() - getPaddingTop() - getPaddingBottom()) / 2f);
|
||||||
|
int firstLevel = LEVEL_COUNT - visibleCount;
|
||||||
|
|
||||||
for (int index = 0; index < levels.length; index++) {
|
canvas.drawLine(startX, centerY, startX + availableWidth, centerY, centerPaint);
|
||||||
float height = Math.max(strokeWidth, levels[index] * maximumHeight);
|
waveform.reset();
|
||||||
float x = getPaddingLeft() + strokeWidth / 2 + index * gap * 2;
|
|
||||||
canvas.drawLine(x, centerY - height / 2, x, centerY + height / 2, paint);
|
float previousX = startX;
|
||||||
|
float previousY = upperY(firstLevel, centerY, maximumHalfHeight);
|
||||||
|
waveform.moveTo(previousX, previousY);
|
||||||
|
for (int index = 1; index < visibleCount; index++) {
|
||||||
|
float x = startX + index * step;
|
||||||
|
float y = upperY(firstLevel + index, centerY, maximumHalfHeight);
|
||||||
|
waveform.quadTo(previousX, previousY, (previousX + x) / 2, (previousY + y) / 2);
|
||||||
|
previousX = x;
|
||||||
|
previousY = y;
|
||||||
}
|
}
|
||||||
|
waveform.lineTo(previousX, previousY);
|
||||||
|
|
||||||
|
previousY = lowerY(firstLevel + visibleCount - 1, centerY, maximumHalfHeight);
|
||||||
|
waveform.lineTo(previousX, previousY);
|
||||||
|
for (int index = visibleCount - 2; index >= 0; index--) {
|
||||||
|
float x = startX + index * step;
|
||||||
|
float y = lowerY(firstLevel + index, centerY, maximumHalfHeight);
|
||||||
|
waveform.quadTo(previousX, previousY, (previousX + x) / 2, (previousY + y) / 2);
|
||||||
|
previousX = x;
|
||||||
|
previousY = y;
|
||||||
|
}
|
||||||
|
waveform.lineTo(previousX, previousY);
|
||||||
|
waveform.close();
|
||||||
|
|
||||||
|
canvas.drawPath(waveform, fillPaint);
|
||||||
|
canvas.drawPath(waveform, edgePaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float upperY(int index, float centerY, float maximumHalfHeight) {
|
||||||
|
return centerY - minimumHalfHeight
|
||||||
|
- levels[index] * (maximumHalfHeight - minimumHalfHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float lowerY(int index, float centerY, float maximumHalfHeight) {
|
||||||
|
return centerY + minimumHalfHeight
|
||||||
|
+ levels[index] * (maximumHalfHeight - minimumHalfHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ import android.widget.ImageButton;
|
|||||||
public final class PushToTalkButton extends ImageButton {
|
public final class PushToTalkButton extends ImageButton {
|
||||||
private Runnable pressedAction;
|
private Runnable pressedAction;
|
||||||
private Runnable releasedAction;
|
private Runnable releasedAction;
|
||||||
|
private boolean trackingPress;
|
||||||
|
|
||||||
public PushToTalkButton(Context context, AttributeSet attributes) {
|
public PushToTalkButton(Context context, AttributeSet attributes) {
|
||||||
super(context, attributes);
|
super(context, attributes);
|
||||||
@@ -16,25 +17,35 @@ public final class PushToTalkButton extends ImageButton {
|
|||||||
void setPushToTalkActions(Runnable pressedAction, Runnable releasedAction) {
|
void setPushToTalkActions(Runnable pressedAction, Runnable releasedAction) {
|
||||||
this.pressedAction = pressedAction;
|
this.pressedAction = pressedAction;
|
||||||
this.releasedAction = releasedAction;
|
this.releasedAction = releasedAction;
|
||||||
|
trackingPress = false;
|
||||||
|
setPressed(false);
|
||||||
|
setScaleX(1);
|
||||||
|
setScaleY(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
if (pressedAction == null || releasedAction == null) return super.onTouchEvent(event);
|
if (pressedAction == null || releasedAction == null) return super.onTouchEvent(event);
|
||||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||||
|
trackingPress = true;
|
||||||
pressedAction.run();
|
pressedAction.run();
|
||||||
setPressed(true);
|
setPressed(true);
|
||||||
|
animate().scaleX(0.92f).scaleY(0.92f).setDuration(90).start();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||||
releasedAction.run();
|
if (trackingPress) releasedAction.run();
|
||||||
|
trackingPress = false;
|
||||||
setPressed(false);
|
setPressed(false);
|
||||||
|
animate().scaleX(1).scaleY(1).setDuration(120).start();
|
||||||
super.performClick();
|
super.performClick();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (event.getAction() == MotionEvent.ACTION_CANCEL) {
|
if (event.getAction() == MotionEvent.ACTION_CANCEL) {
|
||||||
releasedAction.run();
|
if (trackingPress) releasedAction.run();
|
||||||
|
trackingPress = false;
|
||||||
setPressed(false);
|
setPressed(false);
|
||||||
|
animate().scaleX(1).scaleY(1).setDuration(120).start();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -42,10 +53,6 @@ public final class PushToTalkButton extends ImageButton {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean performClick() {
|
public boolean performClick() {
|
||||||
if (pressedAction != null && releasedAction != null) {
|
|
||||||
pressedAction.run();
|
|
||||||
releasedAction.run();
|
|
||||||
}
|
|
||||||
return super.performClick();
|
return super.performClick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@ import android.os.Looper;
|
|||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.speech.RecognizerIntent;
|
import android.speech.RecognizerIntent;
|
||||||
import android.speech.SpeechRecognizer;
|
import android.speech.SpeechRecognizer;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -79,6 +80,7 @@ public final class SpeechRecognitionActivity extends Activity {
|
|||||||
|
|
||||||
private void configureRecordButton() {
|
private void configureRecordButton() {
|
||||||
record.setAlpha(1);
|
record.setAlpha(1);
|
||||||
|
resizeRecordButton(config.pushToTalk ? 80 : 64, config.pushToTalk ? 23 : 19);
|
||||||
if (config.pushToTalk) {
|
if (config.pushToTalk) {
|
||||||
record.setImageResource(R.drawable.ic_mic);
|
record.setImageResource(R.drawable.ic_mic);
|
||||||
record.setContentDescription(getString(R.string.hold_to_talk));
|
record.setContentDescription(getString(R.string.hold_to_talk));
|
||||||
@@ -90,6 +92,17 @@ public final class SpeechRecognitionActivity extends Activity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void resizeRecordButton(int sizeDp, int paddingDp) {
|
||||||
|
float density = getResources().getDisplayMetrics().density;
|
||||||
|
int size = Math.round(sizeDp * density);
|
||||||
|
int padding = Math.round(paddingDp * density);
|
||||||
|
ViewGroup.LayoutParams parameters = record.getLayoutParams();
|
||||||
|
parameters.width = size;
|
||||||
|
parameters.height = size;
|
||||||
|
record.setLayoutParams(parameters);
|
||||||
|
record.setPadding(padding, padding, padding, padding);
|
||||||
|
}
|
||||||
|
|
||||||
private void ensurePermissionAndStart() {
|
private void ensurePermissionAndStart() {
|
||||||
if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
|
if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
|
||||||
prepareForInput();
|
prepareForInput();
|
||||||
@@ -181,7 +194,7 @@ public final class SpeechRecognitionActivity extends Activity {
|
|||||||
public void onRecording() {
|
public void onRecording() {
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(() -> {
|
||||||
status.setText(R.string.listening);
|
status.setText(R.string.listening);
|
||||||
record.setImageResource(R.drawable.ic_stop);
|
if (!config.pushToTalk) record.setImageResource(R.drawable.ic_stop);
|
||||||
recordingStartedAt = SystemClock.elapsedRealtime();
|
recordingStartedAt = SystemClock.elapsedRealtime();
|
||||||
elapsed.setText(R.string.elapsed_zero);
|
elapsed.setText(R.string.elapsed_zero);
|
||||||
handler.post(updateElapsed);
|
handler.post(updateElapsed);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.os.Handler;
|
|||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.speech.SpeechRecognizer;
|
import android.speech.SpeechRecognizer;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.view.inputmethod.InputConnection;
|
import android.view.inputmethod.InputConnection;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -22,6 +23,13 @@ public final class TransuiInputMethodService extends InputMethodService {
|
|||||||
private VoiceRecordingController controller;
|
private VoiceRecordingController controller;
|
||||||
private boolean sessionActive;
|
private boolean sessionActive;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onEvaluateFullscreenMode() {
|
||||||
|
// Voice input should stay attached to the calling app in landscape. Android's
|
||||||
|
// default extract editor is the large, blank text area shown above the IME.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateInputView() {
|
public View onCreateInputView() {
|
||||||
FrameLayout parent = new FrameLayout(this);
|
FrameLayout parent = new FrameLayout(this);
|
||||||
@@ -67,6 +75,7 @@ public final class TransuiInputMethodService extends InputMethodService {
|
|||||||
record.setAlpha(1);
|
record.setAlpha(1);
|
||||||
record.setOnClickListener(null);
|
record.setOnClickListener(null);
|
||||||
record.setPushToTalkActions(null, null);
|
record.setPushToTalkActions(null, null);
|
||||||
|
resizeRecordButton(config.pushToTalk ? 76 : 64, config.pushToTalk ? 22 : 19);
|
||||||
if (config.pushToTalk) {
|
if (config.pushToTalk) {
|
||||||
record.setImageResource(R.drawable.ic_mic);
|
record.setImageResource(R.drawable.ic_mic);
|
||||||
record.setContentDescription(getString(R.string.hold_to_talk));
|
record.setContentDescription(getString(R.string.hold_to_talk));
|
||||||
@@ -78,6 +87,17 @@ public final class TransuiInputMethodService extends InputMethodService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void resizeRecordButton(int sizeDp, int paddingDp) {
|
||||||
|
float density = getResources().getDisplayMetrics().density;
|
||||||
|
int size = Math.round(sizeDp * density);
|
||||||
|
int padding = Math.round(paddingDp * density);
|
||||||
|
ViewGroup.LayoutParams parameters = record.getLayoutParams();
|
||||||
|
parameters.width = size;
|
||||||
|
parameters.height = size;
|
||||||
|
record.setLayoutParams(parameters);
|
||||||
|
record.setPadding(padding, padding, padding, padding);
|
||||||
|
}
|
||||||
|
|
||||||
private void startRecording() {
|
private void startRecording() {
|
||||||
if (sessionActive) return;
|
if (sessionActive) return;
|
||||||
sessionActive = true;
|
sessionActive = true;
|
||||||
@@ -122,7 +142,7 @@ public final class TransuiInputMethodService extends InputMethodService {
|
|||||||
public void onRecording() {
|
public void onRecording() {
|
||||||
handler.post(() -> {
|
handler.post(() -> {
|
||||||
status.setText(R.string.listening);
|
status.setText(R.string.listening);
|
||||||
record.setImageResource(R.drawable.ic_stop);
|
if (!config.pushToTalk) record.setImageResource(R.drawable.ic_stop);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,8 @@ final class VoiceRecordingController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ByteArrayOutputStream pcm = new ByteArrayOutputStream(SAMPLE_RATE * 8);
|
ByteArrayOutputStream pcm = new ByteArrayOutputStream(SAMPLE_RATE * 8);
|
||||||
short[] samples = new short[bufferBytes / 2];
|
// Keep meter updates quick even when AudioRecord asks for a large internal buffer.
|
||||||
|
short[] samples = new short[Math.min(bufferBytes / 2, 1024)];
|
||||||
long startedAt = SystemClock.elapsedRealtime();
|
long startedAt = SystemClock.elapsedRealtime();
|
||||||
try {
|
try {
|
||||||
recorder.startRecording();
|
recorder.startRecording();
|
||||||
@@ -168,6 +169,7 @@ final class VoiceRecordingController {
|
|||||||
double rootMeanSquare = Math.sqrt(squaredTotal / count);
|
double rootMeanSquare = Math.sqrt(squaredTotal / count);
|
||||||
if (rootMeanSquare <= 0) return 0;
|
if (rootMeanSquare <= 0) return 0;
|
||||||
double decibels = 20 * Math.log10(rootMeanSquare);
|
double decibels = 20 * Math.log10(rootMeanSquare);
|
||||||
return (float) Math.max(0, Math.min(1, (decibels + 50) / 50));
|
// Discard the microphone noise floor and reserve the top of the meter for loud speech.
|
||||||
|
return (float) Math.max(0, Math.min(1, (decibels + 48) / 38));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,66 +17,66 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingStart="20dp"
|
android:paddingStart="18dp"
|
||||||
android:paddingTop="12dp"
|
android:paddingTop="10dp"
|
||||||
android:paddingEnd="20dp">
|
android:paddingEnd="18dp">
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="4dp"
|
android:layout_width="4dp"
|
||||||
android:layout_height="28dp"
|
android:layout_height="24dp"
|
||||||
android:background="@color/signal" />
|
android:background="@color/signal" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/voice_status"
|
android:id="@+id/voice_status"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:paddingStart="12dp"
|
android:paddingStart="10dp"
|
||||||
android:paddingEnd="12dp"
|
android:paddingEnd="10dp"
|
||||||
android:text="@string/listening"
|
android:text="@string/listening"
|
||||||
android:textColor="@color/ink"
|
android:textColor="@color/ink"
|
||||||
android:textSize="16sp"
|
android:textSize="15sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="64dp"
|
android:layout_height="86dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingStart="14dp"
|
android:paddingStart="12dp"
|
||||||
android:paddingEnd="14dp"
|
android:paddingEnd="12dp"
|
||||||
android:paddingBottom="8dp">
|
android:paddingBottom="10dp">
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/voice_keyboard"
|
android:id="@+id/voice_keyboard"
|
||||||
android:layout_width="48dp"
|
android:layout_width="52dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="52dp"
|
||||||
android:background="@drawable/voice_secondary_button"
|
android:background="@drawable/voice_secondary_button"
|
||||||
android:contentDescription="@string/keyboard"
|
android:contentDescription="@string/keyboard"
|
||||||
android:padding="12dp"
|
android:padding="13dp"
|
||||||
android:src="@drawable/ic_keyboard"
|
android:src="@drawable/ic_keyboard"
|
||||||
android:tint="@color/ink" />
|
android:tint="@color/ink" />
|
||||||
|
|
||||||
<io.github.transui.android.AmplitudeView
|
<io.github.transui.android.AmplitudeView
|
||||||
android:id="@+id/voice_amplitude"
|
android:id="@+id/voice_amplitude"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="44dp"
|
android:layout_height="58dp"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:importantForAccessibility="no"
|
android:importantForAccessibility="no"
|
||||||
android:paddingTop="8dp"
|
android:paddingTop="7dp"
|
||||||
android:paddingBottom="8dp" />
|
android:paddingBottom="7dp" />
|
||||||
|
|
||||||
<io.github.transui.android.PushToTalkButton
|
<io.github.transui.android.PushToTalkButton
|
||||||
android:id="@+id/voice_record"
|
android:id="@+id/voice_record"
|
||||||
android:layout_width="48dp"
|
android:layout_width="72dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="72dp"
|
||||||
android:background="@drawable/voice_primary_button"
|
android:background="@drawable/voice_primary_button"
|
||||||
android:contentDescription="@string/stop"
|
android:contentDescription="@string/stop"
|
||||||
android:padding="12dp"
|
android:padding="21dp"
|
||||||
android:src="@drawable/ic_stop"
|
android:src="@drawable/ic_stop"
|
||||||
android:tint="@color/paper" />
|
android:tint="@color/paper" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
Reference in new issue
Block a user