1、前言
補一篇文章,github早已提交,文章遲遲未寫,今天就奉上。
前段時間 Android開發正規羣(324345614)有個羣友在問怎麼實現喜馬拉雅6.6.21.3版本的播放進度條,網上搜了一圈並沒有人寫過類似的,羣裏其他人也沒有人想幫他寫一個,自告奮勇,開始擼一個。
2、編寫代碼
java爲例子,kotlin代碼在github 項目中有,文章後面會給出github地址
2.1、編寫思路:
- 繪製前測量
- 背景條繪製
- 緩衝條繪製
- 播放進度框背景繪製
- 播放進度文字繪製
- 進度框計算移動過程的中心點
- 觸摸調整進度
2.2、開始寫代碼了
這裏涉及到一點,進度框的寬度是比較長的。不能像那些小圓點進度那樣可以忽略不計,
所以在播放過程中,這個進度框的中心點會一直往右移,起點的計算就尤其重要了。
起點計算:應該是要根據當前的進度和總進度還有總寬度計算出距左邊的距離,這個距離得出來的還不是最終的,因爲進度框有很大寬度的,不然會在播放結束後跑出右邊。所以還要根據當前進度佔比總進度得出的比例算出進度框佔比大小,然後再用前面計算得到的距離減去這個佔比
終點計算:起點+進度框寬度
float textBgStartX = (float) progress * (progressBarRealWidth) / maxProgress - (float) textBgWidth / maxProgress * progress + dia;
float textBgEndX = textBgStartX + textBgWidth;
Log.e(TAG, "width " + width + ", textBgWidth " + textBgWidth + ", textBgStartX " + textBgStartX);
canvas.drawRoundRect(textBgStartX
, (height >> 1) - (textBgHeight >> 1)
, textBgEndX
, (height >> 1) + (textBgHeight >> 1)
, textBgWidth >> 1 //x半徑
, textBgWidth >> 1 //y半徑
, textBgPaint);
CustomSeekBar.java完整代碼
package com.kincai.customseekbar;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.ColorRes;
import androidx.core.content.ContextCompat;
import java.util.Locale;
/**
* Created by KINCAI
* <p>
* Desc 自定義SeekBar 資源配置屬性暫未實現
* <p>
* Date 2019-10-28 16:38
*/
public class CustomSeekBar extends View {
private final String TAG = this.getClass().getSimpleName();
private int seekBarDefaultWidth;
private int textBgHeight;
private int textBgWidth;
private int cacheProgressBarHeight;
private int progressBarHeight;
private int cacheProgressBarColor;
private int progressBarColor;
private int textColor;
private int textBgColor;
private int maxProgress;
private int progress;
private int cacheProgress;
private int minCircleRadius;
private int textSize;
private Paint cacheProgressBarPaint = new Paint();
private Paint progressBarPaint = new Paint();
private Paint textPaint = new Paint();
private Paint textBgPaint = new Paint();
/**小圓點直徑*/
private float dia;
private int textHeight;
public CustomSeekBar(Context context) {
this(context, null);
}
public CustomSeekBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// 默認寬度爲屏幕寬
Resources resources = this.getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
seekBarDefaultWidth = dm.widthPixels;
textBgWidth = dp2px(200);
textBgHeight = dp2px(16);
cacheProgressBarHeight = dp2px(1.5f);
progressBarHeight = dp2px(1f);
setCircleParam();
textSize = sp2px(10);
progressBarColor = Color.parseColor("#d9ead3");
cacheProgressBarColor = Color.parseColor("#ffffff");
textColor = Color.parseColor("#6e6e6e");
textBgColor = Color.parseColor("#ffffff");
progressBarPaint.setColor(progressBarColor);
progressBarPaint.setAntiAlias(false);
progressBarPaint.setStyle(Paint.Style.FILL);
progressBarPaint.setStrokeCap(Paint.Cap.ROUND);
cacheProgressBarPaint.setColor(cacheProgressBarColor);
cacheProgressBarPaint.setAntiAlias(false);
cacheProgressBarPaint.setStyle(Paint.Style.FILL);
textBgPaint.setColor(textBgColor);
textBgPaint.setAntiAlias(true);
textBgPaint.setStyle(Paint.Style.FILL);
textPaint.setColor(textColor);
textPaint.setAntiAlias(true);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTypeface(Typeface.DEFAULT);
textPaint.setTextSize(textSize);
textHeight = getTextHeight(textPaint, "00:00/00:00");
textBgHeight = textHeight + dp2px(8);
}
private void setCircleParam() {
minCircleRadius = progressBarHeight;
dia = minCircleRadius * 2;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//測量寬高
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = seekBarDefaultWidth;
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(width, widthSize);
}
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
//當wrap_content的時候 高度爲textBgHeight
// 或者是所有部分的最高那個高度
height = textBgHeight;
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(height, heightSize);
}
}
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int height = getHeight();
int width = getWidth();
float progressBarRealWidth = width - dia * 2;
Log.e(TAG, "width:" + width + " realWidth:" + progressBarRealWidth);
// 雞巴 兩頭還有兩個圓點
canvas.drawCircle(minCircleRadius, height >> 1, minCircleRadius, cacheProgressBarPaint);
canvas.drawCircle(width - minCircleRadius, height >> 1, minCircleRadius, progressBarPaint);
// 畫背景條
canvas.drawRect(dia
, (height >> 1) - (progressBarHeight >> 1)
, width - dia
, (height >> 1) + (progressBarHeight >> 1)
, progressBarPaint);
// 畫緩存條
float currentCache = (float) cacheProgress * progressBarRealWidth / maxProgress + dia;
canvas.drawRect(dia
, (height >> 1) - (cacheProgressBarHeight >> 1)
, currentCache, (height >> 1) + (cacheProgressBarHeight >> 1)
, cacheProgressBarPaint);
// 判斷界限
if (progress < 0) {
progress = 0;
}
if (progress > maxProgress) {
progress = maxProgress;
}
// 計算文字,文字長度可能會變 會不斷的測量
String progressText = getProgressText();
int textWidth = getTextWidth(textPaint, progressText);
// 畫文字背景 背景寬高隨文字變化而變化
textBgWidth = textWidth + dp2px(12);
float textBgStartX = (float) progress * (progressBarRealWidth) / maxProgress - (float) textBgWidth / maxProgress * progress + dia;
float textBgEndX = textBgStartX + textBgWidth;
Log.e(TAG, "width " + width + ", textBgWidth " + textBgWidth + ", textBgStartX " + textBgStartX);
canvas.drawRoundRect(textBgStartX
, (height >> 1) - (textBgHeight >> 1)
, textBgEndX
, (height >> 1) + (textBgHeight >> 1)
, textBgWidth >> 1 //x半徑
, textBgWidth >> 1 //y半徑
, textBgPaint);
//畫文字
//計算起點
float textStartY = (height >> 1) + (textHeight >> 1);
float textStartX = textBgStartX + ((textBgWidth >> 1) - (textWidth >> 1));
canvas.drawText(progressText, textStartX, textStartY, textPaint);
}
private int getTextWidth(Paint paint, String str) {
Rect rect = new Rect();
paint.getTextBounds(str, 0, str.length(), rect);
return rect.width();
}
private int getTextHeight(Paint paint, String str) {
Rect rect = new Rect();
paint.getTextBounds(str, 0, str.length(), rect);
return rect.height();
}
private String getProgressText() {
String progressText = formatProgress(this.progress);
String maxProgressText = formatProgress(this.maxProgress);
return progressText + "/" + maxProgressText;
}
/**
* @param seconds 進度(秒)
* @return
*/
private String formatProgress(int seconds) {
String standardTime;
if (seconds <= 0) {
standardTime = "00:00";
} else if (seconds < 60) {
standardTime = String.format(Locale.getDefault(), "00:%02d", seconds % 60);
} else if (seconds < 3600) {
standardTime = String.format(Locale.getDefault(), "%02d:%02d", seconds / 60, seconds % 60);
} else {
standardTime = String.format(Locale.getDefault(), "%02d:%02d:%02d", seconds / 3600, seconds % 3600 / 60, seconds % 60);
}
return standardTime;
}
public void setMaxProgress(int maxProgress) {
this.maxProgress = maxProgress;
}
/**
* @param progress 進度(秒)
*/
public void cacheProgress(int progress) {
this.cacheProgress = progress;
postInvalidate();
}
/**
* @param progress 進度(秒)
*/
public void progress(int progress) {
if (mDrag) {
return;
}
this.progress = progress;
postInvalidate();
}
/**
* @param cacheProgressBarHeight 緩存條高度 單位dp
*/
public void setCacheProgressBarHeight(float cacheProgressBarHeight) {
this.cacheProgressBarHeight = dp2px(cacheProgressBarHeight);
}
/**
* @param progressBarHeight 進度條高度 單位dp
*/
public void setProgressBarHeight(float progressBarHeight) {
this.progressBarHeight = dp2px(progressBarHeight);
setCircleParam();
}
public void setCacheProgressBarColor(@ColorRes int cacheProgressBarColor) {
this.cacheProgressBarColor = ContextCompat.getColor(getContext(), cacheProgressBarColor);
cacheProgressBarPaint.setColor(this.cacheProgressBarColor);
}
public void setProgressBarColor(@ColorRes int progressBarColor) {
this.progressBarColor = ContextCompat.getColor(getContext(), progressBarColor);
progressBarPaint.setColor(this.progressBarColor);
}
public void setTextColor(@ColorRes int textColor) {
this.textColor = ContextCompat.getColor(getContext(), textColor);
textPaint.setColor(this.textColor);
}
public void setTextBgColor(@ColorRes int textBgColor) {
this.textBgColor = ContextCompat.getColor(getContext(), textBgColor);
textBgPaint.setColor(this.textBgColor);
}
/**
* @param textSize 文字大小 單位sp
*/
public void setTextSize(int textSize) {
this.textSize = sp2px(textSize);
}
private boolean mDrag;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDrag = true;
break;
case MotionEvent.ACTION_MOVE:
touchUpdate(event.getX());
break;
case MotionEvent.ACTION_UP:
mDrag = false;
touchUpdate(event.getX());
if (iProgressListener != null) {
iProgressListener.progress(progress);
}
break;
}
return true;
}
private void touchUpdate(float x) {
progress = (int) (x * maxProgress / getWidth());
postInvalidate();
}
IProgressListener iProgressListener;
public void setProgressListener(IProgressListener iProgressListener) {
this.iProgressListener = iProgressListener;
}
public interface IProgressListener {
void progress(int progress);
}
private int dp2px(float var1) {
float var2 = getContext().getResources().getDisplayMetrics().density;
return (int) (var1 * var2 + 0.5F);
}
private int sp2px(float var1) {
float var2 = getContext().getResources().getDisplayMetrics().scaledDensity;
return (int) (var1 * var2 + 0.5F);
}
}
模擬播放
佈局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#8B5A2B"
tools:context=".MainActivity">
<com.kincai.customseekbar.CustomSeekBar
android:id="@+id/seekbar"
android:layout_margin="20dp"
android:layout_width="match_parent"
android:layout_centerInParent="true"
android:layout_height="wrap_content" />
</RelativeLayout>
MainActivity.java
package com.kincai.customseekbar;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
int cacheProgress = 0;
int progress;
int maxProgress = 60;
private CustomSeekBar customSeekBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
customSeekBar = findViewById(R.id.seekbar);
customSeekBar.setMaxProgress(maxProgress);//最大進度s
customSeekBar.setProgressBarHeight(1.0f);//進度條高度dp 默認1.0f
customSeekBar.setCacheProgressBarHeight(1.5f);//緩存條高度dp 默認1.5f
customSeekBar.setProgressBarColor(android.R.color.darker_gray);//進度條顏色colorId
customSeekBar.setCacheProgressBarColor(android.R.color.white);//緩存條顏色colorId
customSeekBar.setTextBgColor(android.R.color.white);//文字背景顏色colorId
customSeekBar.setTextColor(android.R.color.black);//字體顏色colorId
customSeekBar.setTextSize(10);//文字大小sp 默認10sp
// 設置進度拖動監聽
// 手動拖動進度條會返回當前進度
customSeekBar.setProgressListener(new CustomSeekBar.IProgressListener() {
@Override
public void progress(int progress) {
MainActivity.this.progress = progress;
}
});
start();
}
private void start() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
if (cacheProgress < maxProgress) {
cacheProgress += 4;
customSeekBar.cacheProgress(cacheProgress);
}
customSeekBar.progress(progress);
if (progress >= maxProgress) {
break;
}
progress += 1;
Log.e("ss", "ss" + progress);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}