仿微信听书进度条

复制代码
    <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>
相关推荐
月夜风雨磊2 天前
Android NDK从r10c版本到r29版本的下载链接
android·gitee·android ndk
似霰9 天前
安卓系统属性之androidboot.xxx转换成ro.boot.xxx
android·gitee
liansmo11 天前
Git与TortoiseGit在Gitee平台的应用
git·gitee
eduics11 天前
Pull Request 中提示`commits incorrectly signed off`
gitee·github
不念霉运12 天前
Gitee推出“移动软件工厂“解决方案 解决嵌入式与涉密场景研发困局
gitee
不念霉运12 天前
DevOps平台大比拼:Gitee、Jenkins与CircleCI如何选型?
gitee·jenkins·devops
fly五行13 天前
Git基础玩法简单描述
大数据·git·搜索引擎·gitee
不念霉运13 天前
Gitee:本土化DevOps平台如何助力中国企业实现高效研发协作
运维·gitee·devops