feat: 添加接待模式和HTTP工作流集成
- 新增接待模式:机器人可前往指定位置,检测到人后显示确认按钮 - 添加HttpManager用于执行HTTP工作流,支持Home Base的人员检测触发 - 移除repose监控功能,改用OnReposeStatusChangedListener回调 - 简化界面布局,移除随机表情和说话按钮 - 在AndroidManifest中添加usesCleartextTraffic以允许HTTP通信
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Lzwcaiterminaltemi"
|
android:theme="@style/Theme.Lzwcaiterminaltemi"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.robotemi.sdk.metadata.SKILL"
|
android:name="com.robotemi.sdk.metadata.SKILL"
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.example.lzwcai_terminal_temi
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.OutputStreamWriter
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
object HttpManager {
|
||||||
|
private const val TAG = "HttpManager"
|
||||||
|
|
||||||
|
suspend fun workflow_execute(context: Context, apiKey: String, workflowId: String, inputs: Any): String? = withContext(Dispatchers.IO) {
|
||||||
|
var result: String? = null
|
||||||
|
try {
|
||||||
|
val prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
|
||||||
|
val ip = prefs.getString("network_ip", "") ?: ""
|
||||||
|
if (ip.isEmpty()) {
|
||||||
|
Log.e(TAG, "No IP address configured")
|
||||||
|
return@withContext null
|
||||||
|
}
|
||||||
|
val workflowUrl = "http://$ip:8088/open/workflow/execute"
|
||||||
|
|
||||||
|
val url = URL(workflowUrl)
|
||||||
|
val connection = url.openConnection() as HttpURLConnection
|
||||||
|
connection.requestMethod = "POST"
|
||||||
|
connection.setRequestProperty("Content-Type", "application/json")
|
||||||
|
connection.setRequestProperty("X-API-Key", apiKey)
|
||||||
|
connection.doOutput = true
|
||||||
|
connection.doInput = true
|
||||||
|
connection.connectTimeout = 10000
|
||||||
|
connection.readTimeout = 10000
|
||||||
|
|
||||||
|
val jsonBody = JSONObject()
|
||||||
|
jsonBody.put("workflowId", workflowId)
|
||||||
|
|
||||||
|
when (inputs) {
|
||||||
|
is Map<*, *> -> jsonBody.put("inputs", JSONObject(inputs))
|
||||||
|
is List<*> -> jsonBody.put("inputs", JSONArray(inputs))
|
||||||
|
is Array<*> -> jsonBody.put("inputs", JSONArray(inputs))
|
||||||
|
else -> jsonBody.put("inputs", inputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Request Body: $jsonBody")
|
||||||
|
|
||||||
|
val writer = OutputStreamWriter(connection.outputStream)
|
||||||
|
writer.write(jsonBody.toString())
|
||||||
|
writer.flush()
|
||||||
|
writer.close()
|
||||||
|
|
||||||
|
val responseCode = connection.responseCode
|
||||||
|
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||||
|
val reader = connection.inputStream.bufferedReader()
|
||||||
|
result = reader.readText()
|
||||||
|
reader.close()
|
||||||
|
Log.i(TAG, "Workflow execute success: $result")
|
||||||
|
} else {
|
||||||
|
val errorStream = connection.errorStream
|
||||||
|
val errorMsg = errorStream?.bufferedReader()?.readText() ?: "Unknown error"
|
||||||
|
Log.e(TAG, "Workflow execute failed: $responseCode - $errorMsg")
|
||||||
|
}
|
||||||
|
connection.disconnect()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Workflow execute exception", e)
|
||||||
|
}
|
||||||
|
return@withContext result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,14 +12,25 @@ import com.example.lzwcai_terminal_temi.databinding.ActivityMainBinding
|
|||||||
import com.robotemi.sdk.Robot
|
import com.robotemi.sdk.Robot
|
||||||
import com.robotemi.sdk.TtsRequest
|
import com.robotemi.sdk.TtsRequest
|
||||||
import com.robotemi.sdk.Robot.TtsListener
|
import com.robotemi.sdk.Robot.TtsListener
|
||||||
|
import com.robotemi.sdk.listeners.OnDetectionStateChangedListener
|
||||||
import com.robotemi.sdk.listeners.OnGoToLocationStatusChangedListener
|
import com.robotemi.sdk.listeners.OnGoToLocationStatusChangedListener
|
||||||
import com.robotemi.sdk.listeners.OnRobotReadyListener
|
import com.robotemi.sdk.listeners.OnRobotReadyListener
|
||||||
|
import com.robotemi.sdk.listeners.OnDetectionStateChangedListener.Companion.DETECTED
|
||||||
|
import com.robotemi.sdk.listeners.OnDetectionStateChangedListener.Companion.IDLE
|
||||||
|
import com.robotemi.sdk.navigation.listener.OnReposeStatusChangedListener
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnGoToLocationStatusChangedListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnGoToLocationStatusChangedListener,
|
||||||
|
OnDetectionStateChangedListener, OnReposeStatusChangedListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
private lateinit var robot: Robot
|
private lateinit var robot: Robot
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
private var mqttManager: MqttManager? = null
|
private var mqttManager: MqttManager? = null
|
||||||
|
private val mainScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||||
private lateinit var prefs: SharedPreferences
|
private lateinit var prefs: SharedPreferences
|
||||||
private lateinit var navCon: NavController
|
private lateinit var navCon: NavController
|
||||||
private var lastArrivalLocation: String? = null
|
private var lastArrivalLocation: String? = null
|
||||||
@@ -27,6 +38,12 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
|||||||
private val fixedFaceScale = 1.0f
|
private val fixedFaceScale = 1.0f
|
||||||
private val baseFaceSizeDp = 1000f
|
private val baseFaceSizeDp = 1000f
|
||||||
|
|
||||||
|
// Reception mode
|
||||||
|
private var isReceptionMode = false
|
||||||
|
private var receptionLocation: String = ""
|
||||||
|
private var receptionText: String = ""
|
||||||
|
private var receptionDestination: String = ""
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -44,16 +61,16 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
|||||||
startActivity(Intent(this, SettingsActivity::class.java))
|
startActivity(Intent(this, SettingsActivity::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.btnRandomExpression.setOnClickListener {
|
|
||||||
binding.animatedEmojiView.currentExpression = AnimatedEmojiView.Expression.values().random()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.btnSpeak.setOnClickListener {
|
|
||||||
speakLongSentence()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.animatedEmojiView.currentExpression = AnimatedEmojiView.Expression.SMILE
|
binding.animatedEmojiView.currentExpression = AnimatedEmojiView.Expression.SMILE
|
||||||
applyFaceScale(fixedFaceScale)
|
applyFaceScale(fixedFaceScale)
|
||||||
|
|
||||||
|
binding.btnReception.setOnClickListener {
|
||||||
|
val destination = receptionDestination
|
||||||
|
stopReceptionMode()
|
||||||
|
val ttsRequest = TtsRequest.create("接待任务确认,请跟我来", false, language = TtsRequest.Language.ZH_CN)
|
||||||
|
robot.speak(ttsRequest)
|
||||||
|
navCon.goTo(destination, false)
|
||||||
|
}
|
||||||
|
|
||||||
updateMqttConnection()
|
updateMqttConnection()
|
||||||
}
|
}
|
||||||
@@ -63,6 +80,8 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
|||||||
robot.addOnRobotReadyListener(this)
|
robot.addOnRobotReadyListener(this)
|
||||||
robot.addTtsListener(this)
|
robot.addTtsListener(this)
|
||||||
robot.addOnGoToLocationStatusChangedListener(this)
|
robot.addOnGoToLocationStatusChangedListener(this)
|
||||||
|
robot.addOnDetectionStateChangedListener(this)
|
||||||
|
robot.addOnReposeStatusChangedListener(this)
|
||||||
prefs.registerOnSharedPreferenceChangeListener(this)
|
prefs.registerOnSharedPreferenceChangeListener(this)
|
||||||
mqttManager?.connect()
|
mqttManager?.connect()
|
||||||
}
|
}
|
||||||
@@ -72,6 +91,8 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
|||||||
robot.removeOnRobotReadyListener(this)
|
robot.removeOnRobotReadyListener(this)
|
||||||
robot.removeTtsListener(this)
|
robot.removeTtsListener(this)
|
||||||
robot.removeOnGoToLocationStatusChangedListener(this)
|
robot.removeOnGoToLocationStatusChangedListener(this)
|
||||||
|
robot.removeOnDetectionStateChangedListener(this)
|
||||||
|
robot.removeOnReposeStatusChangedListener(this)
|
||||||
prefs.unregisterOnSharedPreferenceChangeListener(this)
|
prefs.unregisterOnSharedPreferenceChangeListener(this)
|
||||||
mqttManager?.disconnect()
|
mqttManager?.disconnect()
|
||||||
}
|
}
|
||||||
@@ -80,6 +101,7 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
|||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
mqttManager?.disconnect()
|
mqttManager?.disconnect()
|
||||||
LogManager.stopLogcatListener()
|
LogManager.stopLogcatListener()
|
||||||
|
mainScope.cancel()
|
||||||
Log.i("MainActivity", "All resources released on destroy.")
|
Log.i("MainActivity", "All resources released on destroy.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,16 +111,8 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun speakLongSentence() {
|
|
||||||
val longSentence = "你好,我是一个智能机器人。我很高兴能为您服务。现在,我将为您展示我的各种表情和语音能力。希望您喜欢!"
|
|
||||||
val ttsRequest = TtsRequest.create(longSentence, false, language = TtsRequest.Language.ZH_CN)
|
|
||||||
robot.speak(ttsRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTtsStatusChanged(ttsRequest: TtsRequest) {
|
override fun onTtsStatusChanged(ttsRequest: TtsRequest) {
|
||||||
// Forward TTS status to MqttManager for stream queue handling
|
|
||||||
mqttManager?.handleTtsStatusChange(ttsRequest)
|
mqttManager?.handleTtsStatusChange(ttsRequest)
|
||||||
|
|
||||||
when (ttsRequest.status) {
|
when (ttsRequest.status) {
|
||||||
TtsRequest.Status.STARTED -> {
|
TtsRequest.Status.STARTED -> {
|
||||||
Log.i("MainActivity", "TTS started: ${ttsRequest.speech}")
|
Log.i("MainActivity", "TTS started: ${ttsRequest.speech}")
|
||||||
@@ -137,6 +151,92 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
|||||||
Log.i("MainActivity", "Arrived at $location, announcement sent.")
|
Log.i("MainActivity", "Arrived at $location, announcement sent.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetectionStateChanged(state: Int) {
|
||||||
|
// Reception mode logic
|
||||||
|
if (isReceptionMode && lastArrivalLocation == receptionLocation) {
|
||||||
|
when (state) {
|
||||||
|
DETECTED -> {
|
||||||
|
if (binding.btnReception.visibility != android.view.View.VISIBLE) {
|
||||||
|
binding.btnReception.visibility = android.view.View.VISIBLE
|
||||||
|
val ttsRequest = TtsRequest.create(receptionText, false, language = TtsRequest.Language.ZH_CN)
|
||||||
|
robot.speak(ttsRequest)
|
||||||
|
Log.i("MainActivity", "Reception: Person detected (new session) at $receptionLocation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IDLE -> {
|
||||||
|
binding.btnReception.visibility = android.view.View.GONE
|
||||||
|
Log.i("MainActivity", "Reception: Person left (IDLE)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Home Base logic
|
||||||
|
if (lastArrivalLocation?.lowercase() == "home base") {
|
||||||
|
when (state) {
|
||||||
|
DETECTED -> {
|
||||||
|
mainScope.launch {
|
||||||
|
HttpManager.workflow_execute(
|
||||||
|
context = this@MainActivity,
|
||||||
|
apiKey = "wf_865e80f5fc1a4a319474a21d47470863",
|
||||||
|
workflowId = "2031297462423851009",
|
||||||
|
inputs = emptyMap<String, Any>()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IDLE -> {
|
||||||
|
mainScope.launch {
|
||||||
|
HttpManager.workflow_execute(
|
||||||
|
context = this@MainActivity,
|
||||||
|
apiKey = "wf_c02aa853371345dbb29572641d083c24",
|
||||||
|
workflowId = "2031634633458520065",
|
||||||
|
inputs = emptyMap<String, Any>()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startReceptionMode(location: String, text: String, destination: String) {
|
||||||
|
isReceptionMode = true
|
||||||
|
receptionLocation = location
|
||||||
|
receptionText = text
|
||||||
|
receptionDestination = destination
|
||||||
|
Log.i("MainActivity", "Reception mode started: location=$location, text=$text, dest=$destination")
|
||||||
|
if (lastArrivalLocation != location) {
|
||||||
|
navCon.goTo(location, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopReceptionMode() {
|
||||||
|
isReceptionMode = false
|
||||||
|
receptionLocation = ""
|
||||||
|
receptionText = ""
|
||||||
|
receptionDestination = ""
|
||||||
|
binding.btnReception.visibility = android.view.View.GONE
|
||||||
|
Log.i("MainActivity", "Reception mode stopped")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReposeStatusChanged(status: Int, description: String) {
|
||||||
|
when (status) {
|
||||||
|
OnReposeStatusChangedListener.REPOSING_COMPLETE -> {
|
||||||
|
val ttsRequest = TtsRequest.create("重新定位成功", false, language = TtsRequest.Language.ZH_CN)
|
||||||
|
robot.speak(ttsRequest)
|
||||||
|
Log.i("MainActivity", "Repose complete")
|
||||||
|
}
|
||||||
|
OnReposeStatusChangedListener.REPOSING_ABORT -> {
|
||||||
|
val ttsRequest = TtsRequest.create("重新定位被中断", false, language = TtsRequest.Language.ZH_CN)
|
||||||
|
robot.speak(ttsRequest)
|
||||||
|
Log.w("MainActivity", "Repose aborted")
|
||||||
|
}
|
||||||
|
OnReposeStatusChangedListener.REPOSING_START -> {
|
||||||
|
val ttsRequest = TtsRequest.create("正在进行重新定位", false, language = TtsRequest.Language.ZH_CN)
|
||||||
|
robot.speak(ttsRequest)
|
||||||
|
Log.i("MainActivity", "Repose started")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||||
if (key == "network_ip") {
|
if (key == "network_ip") {
|
||||||
Log.i("MainActivity", "IP address changed, re-initializing MQTT connection.")
|
Log.i("MainActivity", "IP address changed, re-initializing MQTT connection.")
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ class MqttManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun disconnect() {
|
fun disconnect() {
|
||||||
robot.removeTtsListener(this)
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
reconnectJob?.cancel()
|
reconnectJob?.cancel()
|
||||||
@@ -178,9 +177,7 @@ class MqttManager(
|
|||||||
}
|
}
|
||||||
"repose" -> {
|
"repose" -> {
|
||||||
val ok = navController.repose()
|
val ok = navController.repose()
|
||||||
speak("正在进行重新定位")
|
|
||||||
Log.i(TAG, "Repose command sent: $ok")
|
Log.i(TAG, "Repose command sent: $ok")
|
||||||
monitorReposeStatus()
|
|
||||||
}
|
}
|
||||||
"stop" -> {
|
"stop" -> {
|
||||||
navController.stop()
|
navController.stop()
|
||||||
@@ -189,6 +186,12 @@ class MqttManager(
|
|||||||
"patrol" -> {
|
"patrol" -> {
|
||||||
navController.randomPatrol()
|
navController.randomPatrol()
|
||||||
}
|
}
|
||||||
|
"reception" -> {
|
||||||
|
val location = obj.optString("location", "前台")
|
||||||
|
val text = obj.optString("text", "你是我要接待的贵宾吗?")
|
||||||
|
val destination = obj.optString("destination", "会议室")
|
||||||
|
(context as? MainActivity)?.startReceptionMode(location, text, destination)
|
||||||
|
}
|
||||||
else -> Log.w(TAG, "Unknown command action: $action")
|
else -> Log.w(TAG, "Unknown command action: $action")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,35 +241,6 @@ class MqttManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun monitorReposeStatus() {
|
|
||||||
scope.launch {
|
|
||||||
Log.i(TAG, "Starting repose monitoring...")
|
|
||||||
val timeout = 30000L
|
|
||||||
val startTime = System.currentTimeMillis()
|
|
||||||
var lastStatus: String? = null
|
|
||||||
|
|
||||||
while (System.currentTimeMillis() - startTime < timeout) {
|
|
||||||
delay(1000L)
|
|
||||||
val status = robot.reposeStatus.toString().uppercase()
|
|
||||||
|
|
||||||
if (status != lastStatus) {
|
|
||||||
Log.i(TAG, "Repose status: $status")
|
|
||||||
lastStatus = status
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status == "COMPLETE") {
|
|
||||||
speak("重新定位成功", null)
|
|
||||||
return@launch
|
|
||||||
} else if (status == "FAILURE" || status == "ABORTED") {
|
|
||||||
speak("重新定位失败", null)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.w(TAG, "Repose monitoring timed out.")
|
|
||||||
speak("重新定位超时", null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun goTo(location: String, backwards: Boolean = false) {
|
private fun goTo(location: String, backwards: Boolean = false) {
|
||||||
val target = location.trim()
|
val target = location.trim()
|
||||||
if (target.isEmpty()) {
|
if (target.isEmpty()) {
|
||||||
@@ -298,7 +272,7 @@ class MqttManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleTtsStatusChange(ttsRequest: TtsRequest) {
|
fun handleTtsStatusChange(ttsRequest: TtsRequest) {
|
||||||
scope.launch(Dispatchers.Main) {
|
scope.launch(Dispatchers.Main) {
|
||||||
when (ttsRequest.status) {
|
when (ttsRequest.status) {
|
||||||
TtsRequest.Status.STARTED -> {
|
TtsRequest.Status.STARTED -> {
|
||||||
@@ -307,7 +281,7 @@ class MqttManager(
|
|||||||
TtsRequest.Status.COMPLETED,
|
TtsRequest.Status.COMPLETED,
|
||||||
TtsRequest.Status.CANCELED,
|
TtsRequest.Status.CANCELED,
|
||||||
TtsRequest.Status.ERROR,
|
TtsRequest.Status.ERROR,
|
||||||
TtsRequest.Status.ABORTED -> {
|
TtsRequest.Status.NOT_ALLOWED -> {
|
||||||
isTtsBusy = false
|
isTtsBusy = false
|
||||||
processNextTts()
|
processNextTts()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,26 +22,16 @@
|
|||||||
android:layout_height="500dp"
|
android:layout_height="500dp"
|
||||||
android:layout_centerInParent="true" />
|
android:layout_centerInParent="true" />
|
||||||
|
|
||||||
<LinearLayout
|
<Button
|
||||||
|
android:id="@+id/btnReception"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:layout_marginBottom="32dp"
|
android:layout_marginBottom="100dp"
|
||||||
android:orientation="horizontal">
|
android:text="是的"
|
||||||
|
android:textSize="24sp"
|
||||||
<Button
|
android:padding="20dp"
|
||||||
android:id="@+id/btnRandomExpression"
|
android:visibility="gone" />
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:text="@string/btn_random_expression" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnSpeak"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/btn_speak" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user