
<com.example.simplemusic.widget.TimeSeekBar
android:id="@+id/timeSeekBar"
android:layout_width="match_parent"
android:layout_height="30dp"
android:max="180000"
android:paddingHorizontal="16dp"
android:progress="0"
android:progressDrawable="@drawable/custom_seekbar_progress"
android:thumb="@android:color/transparent" /> <!-- 隐藏默认 thumb -->
<com.example.simplemusic.widget.FloatingThumbSeekBar
android:id="@+id/timeSeekBar1"
android:layout_width="match_parent"
android:layout_height="60dp"
android:max="180000"
android:progress="0"
android:paddingHorizontal="16dp"/>
custom_seekbar_progress.xml
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<size android:height="4dp"/> <!-- 比 thumb 矮很多 -->
<solid android:color="#D0D0D0"/>
<corners android:radius="2dp"/>
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<shape>
<size android:height="4dp"/>
<solid android:color="#90CAF9"/>
<corners android:radius="2dp"/>
</shape>
</item>
<item android:id="@android:id/progress">
<shape>
<size android:height="4dp"/>
<solid android:color="#2196F3"/>
<corners android:radius="2dp"/>
</shape>
</item>
</layer-list>
package com.haitun.music.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.SeekBar;
import androidx.appcompat.widget.AppCompatSeekBar;
/**
* FloatingThumbSeekBar timeSeekBar = findViewById(R.id.timeSeekBar1);
* <p>
* // 假设你的歌曲总时长是 5 分 30 秒(即 330000 毫秒)
* long totalDurationMs = 330000;
* <p>
* // 最重要的一步:设置总时长
* timeSeekBar.setTotalDuration(totalDurationMs);
* timeSeekBar.updateProgress(totalDurationMs);
* // 设置一个监听器来获取滑动后的新位置
* timeSeekBar.setOnSeekStopListener(newPositionMs -> {
* // 在这里调用你的播放器方法来改变播放位置
* // 你可以打印日志来验证
* Log.d("SeekBar", "用户滑动到新的位置 (毫秒): " + newPositionMs);
* });
*/
public class FloatingThumbSeekBar extends AppCompatSeekBar {
private Paint trackPaint;
private Paint progressPaint;
private Paint thumbPaint;
private Paint textPaint;
private Paint borderPaint;
// 进度条高度
private float trackHeight = 5f;
private float thumbHeight = 65f;
private float thumbOffsetY = -0f;
private float thumbPadding = 20f;
private String progressText = "00:00 / 00:00";
private long totalDuration = 0;
// 添加自定义接口
private OnSeekStopListener onSeekStopListener;
public interface OnSeekStopListener {
void onSeekStopped(long newPositionMs);
}
public void setOnSeekStopListener(OnSeekStopListener listener) {
this.onSeekStopListener = listener;
}
public FloatingThumbSeekBar(Context context) {
super(context);
init();
setupProgressListener();
}
public FloatingThumbSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
init();
setupProgressListener();
}
private void init() {
setThumb(null);
trackPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
trackPaint.setColor(Color.LTGRAY);
progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
progressPaint.setColor(Color.WHITE);
thumbPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
thumbPaint.setColor(Color.WHITE);
borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
borderPaint.setColor(Color.GRAY);
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setStrokeWidth(2f);
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(Color.BLACK);
textPaint.setTextSize(35f);
textPaint.setTextAlign(Paint.Align.CENTER);
}
private void setupProgressListener() {
setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (totalDuration > 0) {
long currentMs = (long) (progress * 1.0f / getMax() * totalDuration);
progressText = formatTime(currentMs, totalDuration);
invalidate();
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// 滑动停止时,调用外部监听器
if (onSeekStopListener != null) {
long newPositionMs = (long) (seekBar.getProgress() * 1.0f / getMax() * totalDuration);
onSeekStopListener.onSeekStopped(newPositionMs);
}
}
});
}
/**
* 将毫秒转换为 时分秒 格式
*/
private String formatTime(long currentMs, long totalMs) {
long totalSeconds = totalMs / 1000;
long totalMinutes = (totalSeconds / 60) % 60;
long totalHours = totalSeconds / 3600;
String totalTimeStr;
if (totalHours > 0) {
totalTimeStr = String.format("%02d:%02d:%02d", totalHours, totalMinutes, totalSeconds % 60);
} else {
totalTimeStr = String.format("%02d:%02d", totalMinutes, totalSeconds % 60);
}
long currentSeconds = currentMs / 1000;
long currentMinutes = (currentSeconds / 60) % 60;
long currentHours = currentSeconds / 3600;
String currentTimeStr;
if (totalHours > 0) {
currentTimeStr = String.format("%02d:%02d:%02d", currentHours, currentMinutes, currentSeconds % 60);
} else {
currentTimeStr = String.format("%02d:%02d", currentMinutes, currentSeconds % 60);
}
return String.format("%s / %s", currentTimeStr, totalTimeStr);
}
@Override
public void drawableStateChanged() {
// 不调用 super,防止状态切换触发 ripple
}
@Override
protected synchronized void onDraw(Canvas canvas) {
float centerY = getHeight() / 2f;
float startX = getPaddingLeft();
float endX = getWidth() - getPaddingRight();
float trackTop = centerY - trackHeight / 2;
float trackBottom = centerY + trackHeight / 2;
canvas.drawRect(startX, trackTop, endX, trackBottom, trackPaint);
float progressEndX = startX + (getProgress() * 1f / getMax()) * (endX - startX);
canvas.drawRect(startX, trackTop, progressEndX, trackBottom, progressPaint);
float scale = getProgress() * 1f / getMax();
float thumbCenterX = startX + scale * (endX - startX);
float textWidth = textPaint.measureText(progressText);
float thumbWidth = textWidth + (thumbPadding * 2);
RectF rect = new RectF(
thumbCenterX - thumbWidth / 2,
centerY - thumbHeight / 2 + thumbOffsetY,
thumbCenterX + thumbWidth / 2,
centerY + thumbHeight / 2 + thumbOffsetY
);
canvas.drawRoundRect(rect, thumbHeight / 2, thumbHeight / 2, borderPaint);
thumbPaint.setStyle(Paint.Style.FILL);
canvas.drawRoundRect(rect, thumbHeight / 2, thumbHeight / 2, thumbPaint);
Paint.FontMetrics fm = textPaint.getFontMetrics();
float textBaseY = rect.centerY() - (fm.ascent + fm.descent) / 2;
canvas.drawText(progressText, rect.centerX(), textBaseY, textPaint);
}
/**
* 设置总时长
*/
public void setTotalDuration(long totalDurationMs) {
this.totalDuration = totalDurationMs;
progressText = formatTime(0, totalDurationMs);
invalidate();
}
/**
* 更新进度条
*/
public void updateProgress(long currentMs) {
if (totalDuration <= 0) return;
int progress = (int) (currentMs * 1.0f / totalDuration * getMax());
setProgress(progress);
}
// Existing style setter methods
public void setTrackHeight(float height) {
this.trackHeight = height;
invalidate();
}
public void setTrackColor(int color) {
trackPaint.setColor(color);
invalidate();
}
public void setProgressColor(int color) {
progressPaint.setColor(color);
invalidate();
}
public void setTextSize(float size) {
textPaint.setTextSize(size);
invalidate();
}
}
FloatingThumbSeekBar timeSeekBar = findViewById(R.id.timeSeekBar1);
// 假设你的歌曲总时长是 5 分 30 秒(即 330000 毫秒)
long totalDurationMs = 330000;
// 最重要的一步:设置总时长
timeSeekBar.setTotalDuration(totalDurationMs);
timeSeekBar.updateProgress(totalDurationMs);
// 设置一个监听器来获取滑动后的新位置
timeSeekBar.setOnSeekStopListener(newPositionMs -> {
// 在这里调用你的播放器方法来改变播放位置
// 你可以打印日志来验证
Log.d("SeekBar", "用户滑动到新的位置 (毫秒): " + newPositionMs);
});
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/splash"
android:orientation="vertical"
android:paddingTop="40dp">
<com.haitun.music.widget.TimeSeekBar
android:id="@+id/timeSeekBar"
android:layout_width="match_parent"
android:layout_height="30dp"
android:max="180000"
android:paddingHorizontal="16dp"
android:progress="0"
android:progressDrawable="@drawable/custom_seekbar_progress"
android:thumb="@android:color/transparent" /> <!-- 隐藏默认 thumb -->
<com.haitun.music.widget.FloatingThumbSeekBar
android:id="@+id/timeSeekBar1"
android:layout_width="match_parent"
android:layout_height="60dp"
android:max="180000"
android:progress="0"
android:paddingHorizontal="16dp"/>
</LinearLayout>