添加pwm控制,后期写到驱动去

This commit is contained in:
peng 2025-07-08 09:13:44 +08:00
parent 9688ebaedf
commit 3e8cefab49
5 changed files with 405 additions and 276 deletions

View File

@ -30,6 +30,7 @@ import com.ismart.ib86.feature.serial.SerialPort.ASR5515DeviceManager;
import com.ismart.ib86.feature.serial.SerialPort.ASR5515Protocol; import com.ismart.ib86.feature.serial.SerialPort.ASR5515Protocol;
import com.ismart.ib86.common.utils.LogManager; import com.ismart.ib86.common.utils.LogManager;
import com.ismart.ib86.feature.gpio.GpioTest; import com.ismart.ib86.feature.gpio.GpioTest;
import com.ismart.ib86.feature.motor.MotorController;
import com.ismart.ib86.view.RobotEyesView; import com.ismart.ib86.view.RobotEyesView;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
@ -47,6 +48,7 @@ public class MainActivity extends AppCompatActivity {
private static final int BT_POWER_EN = 75; private static final int BT_POWER_EN = 75;
private ASR5515DeviceManager deviceManager; private ASR5515DeviceManager deviceManager;
private MotorController motorController;
// 使用弱引用避免内存泄漏 // 使用弱引用避免内存泄漏
private Runnable wearDetectionRunnable = new Runnable() { private Runnable wearDetectionRunnable = new Runnable() {
@Override @Override
@ -80,18 +82,10 @@ public class MainActivity extends AppCompatActivity {
// 初始化UI控件 // 初始化UI控件
ivFace = (ImageView) this.findViewById(R.id.iv_face); ivFace = (ImageView) this.findViewById(R.id.iv_face);
// btnPauseResume = findViewById(R.id.btn_pause_resume);
// btnSwitchFace = findViewById(R.id.btn_switch_face);
// seekBarSpeed = findViewById(R.id.seek_bar_speed);
// tvCurrentFace = findViewById(R.id.tv_current_face);
// tvSpeed = findViewById(R.id.tv_speed);
// 初始化动画管理器 // 初始化动画管理器
animationManager = new RobotFaceAnimationManager(this, ivFace); animationManager = new RobotFaceAnimationManager(this, ivFace);
// 启动随机表情动画
playRandomFaceAnimation();
// 每5秒自动切换一次表情 // 每5秒自动切换一次表情
handler.postDelayed(new Runnable() { handler.postDelayed(new Runnable() {
@Override @Override
@ -101,59 +95,6 @@ public class MainActivity extends AppCompatActivity {
} }
}, 5000); }, 5000);
// 设置暂停/继续按钮点击事件
// btnPauseResume.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// boolean isPaused = animationManager.togglePause();
// btnPauseResume.setText(isPaused ? "继续" : "暂停");
// }
// });
// // 设置切换表情按钮点击事件
// btnSwitchFace.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// String faceName = animationManager.switchToNextFace();
// tvCurrentFace.setText(faceName);
// Toast.makeText(MainActivity.this, "已切换到: " + faceName, Toast.LENGTH_SHORT).show();
// }
// });
// // 设置速度调节滑块变化事件
// seekBarSpeed.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
// @Override
// public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// if (fromUser) {
// // 将进度值(0-100)转换为速度因子(0.5-2.0)
// float speedFactor = 0.5f + (progress / 100.0f) * 1.5f;
// animationManager.setSpeed(speedFactor);
// tvSpeed.setText(String.format("%.1fx", speedFactor));
// }
// }
// @Override
// public void onStartTrackingTouch(SeekBar seekBar) {
// // 不需要实现
// }
// @Override
// public void onStopTrackingTouch(SeekBar seekBar) {
// // 不需要实现
// }
// });
// // 初始化速度显示
// tvSpeed.setText("1.0x");
// // 初始化表情名称显示
// tvCurrentFace.setText(animationManager.getCurrentFaceName());
// mainLayout = findViewById(R.id.main_layout);
// mainLayout.setBackgroundColor(Color.BLACK);
// robotEyesView = new RobotEyesView(this);
// mainLayout.addView(robotEyesView);
// 检查并请求必要的权限 // 检查并请求必要的权限
checkAndRequestPermissions(); checkAndRequestPermissions();
@ -161,14 +102,15 @@ public class MainActivity extends AppCompatActivity {
hideSystemBars(); hideSystemBars();
// 初始化设备管理器 // 初始化设备管理器
initDeviceManager(); initDeviceManager();
//testMotorControl();
// 延迟2秒后开始测试确保设备管理器初始化完成 // 延迟2秒后开始测试确保设备管理器初始化完成
// handler.postDelayed(new Runnable() { handler.postDelayed(new Runnable() {
// @Override @Override
// public void run() { public void run() {
// startProtocolTests(); startProtocolTests();
// } }
// }, 2000); }, 2000);
} }
private void initDeviceManager() { private void initDeviceManager() {
@ -178,6 +120,11 @@ public class MainActivity extends AppCompatActivity {
gpioManager.setDirection(BT_POWER_EN, GpioManager.DIRECTION_OUT); gpioManager.setDirection(BT_POWER_EN, GpioManager.DIRECTION_OUT);
gpioManager.setValue(BT_POWER_EN, GpioManager.VALUE_HIGH); gpioManager.setValue(BT_POWER_EN, GpioManager.VALUE_HIGH);
// 初始化电机控制器
motorController = MotorController.getInstance();
motorController.init();
LogManager.d(TAG, "电机控制器初始化完成");
deviceManager = new ASR5515DeviceManager(this, DEVICE_PATH, BAUD_RATE); deviceManager = new ASR5515DeviceManager(this, DEVICE_PATH, BAUD_RATE);
// 设置设备检查回调 // 设置设备检查回调
deviceManager.setDeviceCheckListener(deviceInfo -> { deviceManager.setDeviceCheckListener(deviceInfo -> {
@ -315,12 +262,12 @@ public class MainActivity extends AppCompatActivity {
// deviceManager.queryBtVersion(); // deviceManager.queryBtVersion();
// } // }
// }, 2000); // }, 2000);
// deviceManager.stopGH3220Measure(ASR5515Protocol.GH3220MeasureType.HR); deviceManager.stopGH3220Measure(ASR5515Protocol.GH3220MeasureType.HR);
// deviceManager.stopGH3220Measure(ASR5515Protocol.GH3220MeasureType.SPO2); deviceManager.stopGH3220Measure(ASR5515Protocol.GH3220MeasureType.SPO2);
// handler.postDelayed(new Runnable() { // handler.postDelayed(new Runnable() {
// @Override // @Override
// public void run() { // public void run() {
// deviceManager.startGH3220Measure(ASR5515Protocol.GH3220MeasureType.SPO2); // deviceManager.startGH3220Measure(ASR5515Protocol.GH3220MeasureType.HR);
// } // }
// }, 1000); // }, 1000);
@ -363,6 +310,16 @@ public class MainActivity extends AppCompatActivity {
} }
gpioManager = null; gpioManager = null;
} }
// 释放电机控制器资源
if (motorController != null) {
try {
motorController.release();
LogManager.d(TAG, "电机控制器资源已释放");
} catch (Exception e) {
LogManager.e(TAG, "释放电机控制器资源失败: " + e.getMessage());
}
}
} catch (Exception e) { } catch (Exception e) {
LogManager.e(TAG, "资源释放过程中发生错误: " + e.getMessage()); LogManager.e(TAG, "资源释放过程中发生错误: " + e.getMessage());
} }
@ -375,26 +332,15 @@ public class MainActivity extends AppCompatActivity {
* 随机播放一个表情动画 * 随机播放一个表情动画
*/ */
private void playRandomFaceAnimation() { private void playRandomFaceAnimation() {
// 可用的表情动画类型数组
int[] faceAnimations = {
RobotFaceAnimationManager.FACE_LOOP_0,
RobotFaceAnimationManager.FACE_LOOP_1,
RobotFaceAnimationManager.FACE_LOOP_2,
RobotFaceAnimationManager.FACE_LOOP_3,
RobotFaceAnimationManager.FACE_LOOP_4,
RobotFaceAnimationManager.FACE_HAPPY,
RobotFaceAnimationManager.FACE_SAD
};
// 随机选择一个表情 // 随机选择一个表情
int randomIndex = (int)(Math.random() * faceAnimations.length); int selectedAnimation = (int)(Math.random() * 5);
int selectedAnimation = faceAnimations[randomIndex];
LogManager.d(TAG, "随机选择表情动画: " + selectedAnimation); LogManager.d(TAG, "随机选择表情动画: " + selectedAnimation);
// 加载选中的动画 // 加载选中的动画
if (animationManager != null) { if (animationManager != null) {
animationManager.loadFaceAnimation(selectedAnimation); animationManager.loadFaceAnimation(selectedAnimation);
animationManager.setSpeed(0.4f);
} else { } else {
LogManager.e(TAG, "动画管理器未初始化"); LogManager.e(TAG, "动画管理器未初始化");
} }
@ -419,6 +365,79 @@ private void hideSystemBars() {
} }
} }
/**
* 测试电机控制功能
*/
private void testMotorControl() {
if (motorController == null) {
LogManager.e(TAG, "电机控制器未初始化");
return;
}
new Thread(() -> {
try {
// 打开电机
LogManager.d(TAG, "打开电机");
if (!motorController.turnOnMotor()) {
LogManager.e(TAG, "打开电机失败");
return;
}
// 设置PWM参数
final int period = 1000000;
final int dutyCycle = 1000000; // 使用90%的占空比避免duty_cycle等于period
Thread.sleep(10000);
// LogManager.d(TAG, "电机正转");
// motorController.rotateReverse(period, 0); // 确保反转PWM关闭
// if (!motorController.rotateForward(period, dutyCycle)) {
// LogManager.e(TAG, "电机正转失败");
// }
// 循环20次正反转
for (int i = 0; i < 100; i++) {
LogManager.d(TAG, "开始第" + (i + 1) + "次正反转测试");
// 电机正转
LogManager.d(TAG, "电机正转");
motorController.rotateReverse(period, 0); // 确保反转PWM关闭
if (!motorController.rotateForward(period, dutyCycle)) {
LogManager.e(TAG, "电机正转失败");
break;
}
// 等待100ms
Thread.sleep(100);
// 停止正转
motorController.rotateForward(period, 0);
// 电机反转
LogManager.d(TAG, "电机反转");
if (!motorController.rotateReverse(period, dutyCycle)) {
LogManager.e(TAG, "电机反转失败");
break;
}
// 等待100ms
Thread.sleep(100);
// 停止反转
motorController.rotateReverse(period, 0);
}
// 测试完成关闭电机
LogManager.d(TAG, "电机测试完成,关闭电机");
motorController.turnOffMotor();
} catch (InterruptedException e) {
LogManager.e(TAG, "电机测试被中断: " + e.getMessage());
motorController.turnOffMotor();
} catch (Exception e) {
LogManager.e(TAG, "电机测试异常: " + e.getMessage());
motorController.turnOffMotor();
}
}).start();
}
@Override @Override
public void onWindowFocusChanged(boolean hasFocus) { public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus); super.onWindowFocusChanged(hasFocus);

View File

@ -17,13 +17,13 @@ import androidx.core.content.ContextCompat;
public class RobotFaceAnimationManager { public class RobotFaceAnimationManager {
// 动画类型常量 // 动画类型常量
public static final int FACE_LOOP_0 = 1; // 循环表情0 public static final int FACE_LOOP_0 = 0; // 循环表情0
public static final int FACE_LOOP_1 = 2; // 循环表情1 public static final int FACE_LOOP_1 = 1; // 循环表情1
public static final int FACE_LOOP_2 = 3; // 循环表情2 public static final int FACE_LOOP_2 = 2; // 循环表情2
public static final int FACE_LOOP_3 = 4; // 循环表情3 public static final int FACE_LOOP_3 = 3; // 循环表情3
public static final int FACE_LOOP_4 = 5; // 循环表情4 public static final int FACE_LOOP_4 = 4; // 循环表情4
public static final int FACE_HAPPY = 6; // 开心表情 public static final int FACE_HAPPY = 5; // 开心表情
public static final int FACE_SAD = 7; // 悲伤表情 public static final int FACE_SAD = 6; // 悲伤表情
// 动画资源ID数组 // 动画资源ID数组
private static final int[] FACE_ANIMATIONS = { private static final int[] FACE_ANIMATIONS = {
@ -98,9 +98,9 @@ public class RobotFaceAnimationManager {
} }
// 应用速度设置 // 应用速度设置
if (speedFactor != 1.0f) { // if (speedFactor != 1.0f) {
applySpeed(speedFactor); // applySpeed(speedFactor);
} // }
// 设置动画到ImageView // 设置动画到ImageView
imageView.setImageDrawable(currentAnimation); imageView.setImageDrawable(currentAnimation);

View File

@ -0,0 +1,249 @@
package com.ismart.ib86.feature.motor;
import android.util.Log;
import com.ismart.ib86.common.utils.LogManager;
import com.ismart.ib86.feature.gpio.GpioManager;
import java.io.FileWriter;
import java.io.IOException;
/**
* 电机控制器类用于控制机器人抬头点头的电机
* 采用单例模式确保全局只有一个控制实例
*/
public class MotorController {
private static final String TAG = "MotorController";
private static volatile MotorController instance;
// GPIO控制常量
private static final int MOTOR_CONTROL_GPIO = 59; // 电机开关GPIO
private static final int MOTOR_SLEEP_GPIO = 65;
private static final int GPIO_HIGH = 1; // GPIO高电平
private static final int GPIO_LOW = 0; // GPIO低电平
// PWM路径常量
private static final String PWM_FORWARD_PATH = "/sys/class/pwm/pwmchip0/pwm0/";
private static final String PWM_REVERSE_PATH = "/sys/class/pwm/pwmchip1/pwm0/";
private static final String PWM_PERIOD_FILE = "period";
private static final String PWM_DUTY_CYCLE_FILE = "duty_cycle";
private static final String PWM_ENABLE_FILE = "enable";
// 电机状态枚举
public enum MotorState {
STOPPED, // 停止状态
FORWARD, // 正转状态
REVERSE // 反转状态
}
// 当前电机状态
private MotorState currentState = MotorState.STOPPED;
/**
* 获取MotorController单例实例
* @return MotorController实例
*/
public static MotorController getInstance() {
if (instance == null) {
synchronized (MotorController.class) {
if (instance == null) {
instance = new MotorController();
}
}
}
return instance;
}
/**
* 私有构造函数防止外部实例化
*/
private MotorController() {
// 私有构造函数
}
/**
* 初始化电机控制器
* @return 是否初始化成功
*/
public boolean init() {
try {
// 初始化GPIO
GpioManager.getInstance().setDirection(MOTOR_CONTROL_GPIO, GpioManager.DIRECTION_OUT);
// 默认关闭电机
return turnOffMotor();
} catch (Exception e) {
Log.e(TAG, "Failed to initialize motor controller", e);
return false;
}
}
/**
* 打开电机
* @return 是否成功
*/
public boolean turnOnMotor() {
try {
rotateForward(1000000, 0); // 确保正转PWM关闭
enablePwm(PWM_FORWARD_PATH, false);
enablePwm(PWM_FORWARD_PATH, true);
rotateReverse(1000000, 0); // 确保反转PWM关闭
enablePwm(PWM_REVERSE_PATH, true);
boolean state = GpioManager.getInstance().setValue(MOTOR_CONTROL_GPIO, GPIO_HIGH);
// 设置SLEEP
GpioManager.getInstance().setDirection(MOTOR_SLEEP_GPIO, GpioManager.DIRECTION_OUT);
GpioManager.getInstance().setValue(MOTOR_SLEEP_GPIO, GpioManager.VALUE_HIGH);
return state;
} catch (Exception e) {
Log.e(TAG, "Failed to turn on motor", e);
return false;
}
}
/**
* 关闭电机
* @return 是否成功
*/
public boolean turnOffMotor() {
try {
// 根据当前状态将相应PWM通道的参数设为0
String pwmPath;
switch (currentState) {
case FORWARD:
pwmPath = PWM_FORWARD_PATH;
break;
case REVERSE:
pwmPath = PWM_REVERSE_PATH;
break;
case STOPPED:
default:
// 如果已经是停止状态直接关闭GPIO
return GpioManager.getInstance().setValue(MOTOR_CONTROL_GPIO, GPIO_LOW);
}
// 将period和dutyCycle设为0
setPwmParameters(pwmPath, 1000000, 0);
// 关闭电机控制GPIO
boolean result = GpioManager.getInstance().setValue(MOTOR_CONTROL_GPIO, GPIO_LOW);
// 更新状态为停止
currentState = MotorState.STOPPED;
return result;
} catch (Exception e) {
Log.e(TAG, "Failed to turn off motor", e);
return false;
}
}
/**
* 控制电机正转
* @param period PWM周期纳秒
* @param dutyCycle PWM占空比纳秒
* @return 是否成功
*/
public boolean rotateForward(int period, int dutyCycle) {
if (setPwmParameters(PWM_FORWARD_PATH, period, dutyCycle)) {
currentState = MotorState.FORWARD;
return true;
}
return false;
}
/**
* 控制电机反转
* @param period PWM周期纳秒
* @param dutyCycle PWM占空比纳秒
* @return 是否成功
*/
public boolean rotateReverse(int period, int dutyCycle) {
if (setPwmParameters(PWM_REVERSE_PATH, period, dutyCycle)) {
currentState = MotorState.REVERSE;
return true;
}
return false;
}
/**
* 设置PWM参数
* @param basePath PWM基础路径
* @param period 周期
* @param dutyCycle 占空比
* @return 是否成功
*/
private boolean setPwmParameters(String basePath, int period, int dutyCycle) {
try {
//enablePwm(basePath, false);
writeToFile(basePath + PWM_PERIOD_FILE, String.valueOf(period));
writeToFile(basePath + PWM_DUTY_CYCLE_FILE, String.valueOf(dutyCycle));
// 使能PWM
//enablePwm(basePath, true);
return true;
} catch (IOException e) {
Log.e(TAG, "Failed to set PWM parameters", e);
return false;
}
}
/**
* 写入文件
* @param filePath 文件路径
* @param content 内容
* @throws IOException IO异常
*/
private void writeToFile(String filePath, String content) throws IOException {
FileWriter writer = null;
try {
writer = new FileWriter(filePath);
writer.write(content);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close file writer", e);
}
}
}
}
/**
* 控制PWM使能状态
* @param basePath PWM基础路径
* @param enable true表示使能false表示禁用
* @return 是否成功
*/
public boolean enablePwm(String basePath, boolean enable) {
try {
writeToFile(basePath + PWM_ENABLE_FILE, enable ? "1" : "0");
return true;
} catch (IOException e) {
Log.e(TAG, "Failed to " + (enable ? "enable" : "disable") + " PWM", e);
return false;
}
}
/**
* 控制电机旋转到指定角度预留API暂不实现
* @param angle 目标角度
* @param speed 旋转速度可选参数
* @return 是否成功
*/
public boolean rotateToAngle(float angle, float speed) {
// TODO: 实现角度控制功能
Log.i(TAG, "rotateToAngle API called, but not implemented yet");
return false;
}
/**
* 释放资源
*/
public void release() {
try {
// 关闭电机
turnOffMotor();
} catch (Exception e) {
Log.e(TAG, "Failed to release resources", e);
}
}
}

View File

@ -199,7 +199,6 @@ public class ASR5515Protocol {
public static final short CMD_HOST_STATUS = 0x0079; // 主机状态同步请求 public static final short CMD_HOST_STATUS = 0x0079; // 主机状态同步请求
public static final short CMD_HOST_STATUS_RESP = 0x007A; // 主机状态同步响应 public static final short CMD_HOST_STATUS_RESP = 0x007A; // 主机状态同步响应
public static final short CMD_HOST_STATUS_BOOT_RESP = 0x007C; //主机状态,启动状态 public static final short CMD_HOST_STATUS_BOOT_RESP = 0x007C; //主机状态,启动状态
// 新增命令
public static final short CMD_BOOT_BIN_CHECK = 0x007D; // boot bin检查请求 [125 0 0] public static final short CMD_BOOT_BIN_CHECK = 0x007D; // boot bin检查请求 [125 0 0]
public static final short CMD_BOOT_BIN_CHECK_RESP = 0x007E; // boot bin检查响应 [126 sn len 0/1] public static final short CMD_BOOT_BIN_CHECK_RESP = 0x007E; // boot bin检查响应 [126 sn len 0/1]
public static final short CMD_COLLECT_FREQ_SET = 0x00C9; // 采集频率设置 [201 0 len 32] public static final short CMD_COLLECT_FREQ_SET = 0x00C9; // 采集频率设置 [201 0 len 32]
@ -240,7 +239,7 @@ public class ASR5515Protocol {
} }
public static byte[] createFrame(short command, byte[] data) { public static byte[] createFrame(short command, byte[] data) {
LogManager.d(TAG, "LEN :" + data.length); //LogManager.d(TAG, "LEN :" + data.length);
int frameLength = 1 + 2 + 2 + 2 + data.length + 1; // start + command(2bytes) + sn(2bytes) + len(2bytes) + data + end int frameLength = 1 + 2 + 2 + 2 + data.length + 1; // start + command(2bytes) + sn(2bytes) + len(2bytes) + data + end
ByteBuffer buffer = ByteBuffer.allocate(frameLength); ByteBuffer buffer = ByteBuffer.allocate(frameLength);
@ -319,7 +318,7 @@ public class ASR5515Protocol {
System.arraycopy(data, startIndex + totalFrameLength, frame.remainingData, 0, frame.remainingData.length); System.arraycopy(data, startIndex + totalFrameLength, frame.remainingData, 0, frame.remainingData.length);
} }
LogManager.d(TAG, "Frame parsed - type: " + type + ", len: " + len + ", data length: " + frameData.length); //LogManager.d(TAG, "Frame parsed - type: " + type + ", len: " + len + ", data length: " + frameData.length);
return frame; return frame;
} catch (Exception e) { } catch (Exception e) {
LogManager.e(TAG, "Parse frame error: " + e.getMessage()); LogManager.e(TAG, "Parse frame error: " + e.getMessage());

View File

@ -11,152 +11,14 @@
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center"> android:gravity="center"
android:layout_marginTop="-150dp">
<ImageView <ImageView
android:id="@+id/iv_face" android:id="@+id/iv_face"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:src="@drawable/anim_face_xunhuan"/> android:src="@drawable/anim_face_xunhuan"/>
</LinearLayout> </LinearLayout>
<!-- <LinearLayout
android:id="@+id/control_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#33000000"
android:padding="16dp"
android:layout_marginTop="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="当前表情:"
android:textColor="#FFFFFF"
android:textSize="14sp"/>
<TextView
android:id="@+id/tv_current_face"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="普通表情"
android:textColor="#FFFFFF"
android:textSize="14sp"
android:layout_marginRight="16dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="速度:"
android:textColor="#FFFFFF"
android:textSize="14sp"/>
<TextView
android:id="@+id/tv_speed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1.0x"
android:textColor="#FFFFFF"
android:textSize="14sp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_marginBottom="8dp">
<Button
android:id="@+id/btn_pause_resume"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="暂停"
android:textColor="#FFFFFF"
android:backgroundTint="#444444"
android:layout_marginRight="8dp"/>
<Button
android:id="@+id/btn_switch_face"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="切换表情"
android:textColor="#FFFFFF"
android:backgroundTint="#444444"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="速度调节"
android:textColor="#FFFFFF"
android:textSize="14sp"
android:layout_marginBottom="4dp"/>
<SeekBar
android:id="@+id/seek_bar_speed"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="50"
android:progressTint="#FFFFFF"
android:thumbTint="#FFFFFF"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="4dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="慢"
android:textColor="#FFFFFF"
android:textSize="12sp"/>
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="正常"
android:textColor="#FFFFFF"
android:textSize="12sp"/>
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="快"
android:textColor="#FFFFFF"
android:textSize="12sp"/>
</LinearLayout>
</LinearLayout>
</LinearLayout> -->
</LinearLayout> </LinearLayout>