類似iOS的控制中心里,音量的上下滑動(dòng)增大甩牺、減小音量。
簡(jiǎn)單說一下需求:
- 需要進(jìn)度條有圓角累奈,使用Canvas裁切即可
- 手勢(shì)拖動(dòng)進(jìn)度條更新進(jìn)度贬派,也可以代碼調(diào)用更新進(jìn)度
- 支持XML和Java代碼設(shè)置最大值、最小值澎媒、當(dāng)前值搞乏,以及進(jìn)度的顏色、圓角半徑等
自定義屬性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="VerticalSeekBar">
<!-- 背景進(jìn)度顏色 -->
<attr name="vsb_bg" format="color" />
<!-- 已有進(jìn)度顏色 -->
<attr name="vsb_progress_bg" format="color" />
<!-- 進(jìn)度條圓角半徑 -->
<attr name="vsb_bg_radius" format="integer|float|dimension" />
<!-- 當(dāng)前進(jìn)度值 -->
<attr name="vsb_progress" format="float" />
<!-- 最小進(jìn)度值 -->
<attr name="vsb_min_progress" format="float" />
<!-- 最大進(jìn)度值 -->
<attr name="vsb_max_progress" format="float" />
</declare-styleable>
</resources>
完整代碼
/**
* 垂直拽托進(jìn)度條
*/
public class VerticalSeekBar extends View {
/**
* View默認(rèn)最小寬度
*/
private int mDefaultWidth;
/**
* View默認(rèn)最小高度
*/
private int mDefaultHeight;
/**
* 控件寬
*/
private int mViewWidth;
/**
* 控件高
*/
private int mViewHeight;
/**
* 背景顏色
*/
private int mBgColor;
/**
* 進(jìn)度背景顏色
*/
private int mProgressBgColor;
/**
* 進(jìn)度條的圓角半徑
*/
private int mBgRadius;
/**
* 當(dāng)前進(jìn)度
*/
private float mProgress;
/**
* 最小進(jìn)度值
*/
private float mMin;
/**
* 最大進(jìn)度值
*/
private float mMax;
/**
* 背景畫筆
*/
private Paint mBgPaint;
/**
* 進(jìn)度畫筆
*/
private Paint mProgressPaint;
/**
* 進(jìn)度更新監(jiān)聽
*/
private VerticalSeekBar.OnProgressUpdateListener mOnProgressUpdateListener;
public VerticalSeekBar(Context context) {
this(context, null);
}
public VerticalSeekBar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public VerticalSeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
initAttr(context, attrs, defStyleAttr);
//取消硬件加速
setLayerType(LAYER_TYPE_SOFTWARE, null);
//背景畫筆
mBgPaint = new Paint();
mBgPaint.setAntiAlias(true);
mBgPaint.setColor(mBgColor);
mBgPaint.setStyle(Paint.Style.FILL);
//進(jìn)度畫筆
mProgressPaint = new Paint();
mProgressPaint.setColor(mProgressBgColor);
mProgressPaint.setAntiAlias(true);
mProgressPaint.setStyle(Paint.Style.FILL);
//計(jì)算默認(rèn)寬戒努、高
mDefaultWidth = dip2px(context, 36f);
mDefaultHeight = dip2px(context, 114f);
}
private void initAttr(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
int defaultBgColor = Color.parseColor("#EDF0FA");
int defaultProgressBgColor = Color.parseColor("#6D79FE");
int defaultBgRadius = dip2px(context, 8f);
float defaultProgress = 0;
float defaultMinProgress = 0;
int defaultMaxProgress = 100;
if (attrs != null) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.VerticalSeekBar, defStyleAttr, 0);
//進(jìn)度背景顏色
mBgColor = array.getColor(R.styleable.VerticalSeekBar_vsb_bg, defaultBgColor);
//已有進(jìn)度的背景顏色
mProgressBgColor = array.getColor(R.styleable.VerticalSeekBar_vsb_progress_bg, defaultProgressBgColor);
//進(jìn)度條的圓角
mBgRadius = array.getDimensionPixelSize(R.styleable.VerticalSeekBar_vsb_bg_radius, defaultBgRadius);
//當(dāng)前進(jìn)度值
mProgress = array.getFloat(R.styleable.VerticalSeekBar_vsb_progress, defaultProgress);
//最小進(jìn)度值
mMin = array.getFloat(R.styleable.VerticalSeekBar_vsb_min_progress, defaultMinProgress);
//最大進(jìn)度值
mMax = array.getFloat(R.styleable.VerticalSeekBar_vsb_max_progress, defaultMaxProgress);
array.recycle();
} else {
mBgColor = defaultBgColor;
mProgressBgColor = defaultProgressBgColor;
mProgress = defaultProgress;
mMin = defaultMinProgress;
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//裁切圓角
clipRound(canvas);
//畫背景
drawBg(canvas);
//畫進(jìn)度條
drawProgress(canvas);
}
//------------ getFrameXxx()方法都是處理padding ------------
private float getFrameLeft() {
return getPaddingStart();
}
private float getFrameRight() {
return mViewWidth - getPaddingEnd();
}
private float getFrameTop() {
return getPaddingTop();
}
private float getFrameBottom() {
return mViewHeight - getPaddingBottom();
}
//------------ getFrameXxx()方法都是處理padding ------------
/**
* 裁剪圓角
*/
private void clipRound(Canvas canvas) {
Path path = new Path();
RectF roundRect = new RectF(getFrameLeft(), getFrameTop(), getFrameRight(), getFrameBottom());
path.addRoundRect(roundRect, mBgRadius, mBgRadius, Path.Direction.CW);
canvas.clipPath(path);
}
/**
* 畫背景
*/
private void drawBg(Canvas canvas) {
canvas.drawRect(new RectF(getFrameLeft(), getFrameTop(),
getFrameRight(), getFrameBottom()),
mBgPaint);
}
/**
* 畫進(jìn)度
*/
private void drawProgress(Canvas canvas) {
float contentHeight = mViewHeight - getPaddingTop() - getPaddingBottom();
//計(jì)算出當(dāng)前進(jìn)度應(yīng)該有個(gè)top值请敦,因?yàn)檫M(jìn)度是從小往上,所以百分比要被1減去
float progressRatio = getProgressRatio();
float top;
if (progressRatio == 0) {
top = contentHeight;
} else {
top = contentHeight * (1 - progressRatio);
}
//畫進(jìn)度矩形
RectF rect = new RectF(getFrameLeft(),
top,
getFrameRight(),
getFrameBottom());
canvas.drawRect(rect, mProgressPaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(handleMeasure(widthMeasureSpec, true),
handleMeasure(heightMeasureSpec, false));
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int action = event.getAction();
//攔截Down事件储玫,然后讓父類不進(jìn)行攔截
if (action == MotionEvent.ACTION_DOWN) {
getParent().requestDisallowInterceptTouchEvent(true);
if (mOnProgressUpdateListener != null) {
mOnProgressUpdateListener.onStartTrackingTouch(this);
}
return true;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
int contentHeight = mViewHeight - getPaddingTop() - getPaddingBottom();
if (action == MotionEvent.ACTION_DOWN) {
return true;
} else if (action == MotionEvent.ACTION_MOVE || action == MotionEvent.ACTION_UP) {
//Move或Up的時(shí)候侍筛,計(jì)算拽托進(jìn)度
float endY = event.getY();
//限制拉到頂
if (endY < 0) {
endY = 0;
}
//限制拉到底
if (endY > contentHeight) {
endY = contentHeight;
}
//計(jì)算觸摸點(diǎn)和高度的差值
float distanceY = Math.abs(contentHeight - endY);
float ratio = distanceY / contentHeight;
//計(jì)算百分比應(yīng)該有的進(jìn)度:進(jìn)度 = 總進(jìn)度 * 進(jìn)度百分比值
float progress = mMax * ratio;
setProgress(progress, true);
if (action == MotionEvent.ACTION_UP) {
if (mOnProgressUpdateListener != null) {
mOnProgressUpdateListener.onStopTrackingTouch(this);
}
}
return true;
}
return super.onTouchEvent(event);
}
/**
* 處理MeasureSpec
*/
private int handleMeasure(int measureSpec, boolean isWidth) {
int result;
if (isWidth) {
result = mDefaultWidth;
} else {
result = mDefaultHeight;
}
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
//處理wrap_content的情況
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
/**
* 設(shè)置進(jìn)度背景顏色
*/
public void setBgColor(int bgColor) {
//沒有變化,不重繪
if (bgColor == mBgColor) {
return;
}
this.mBgColor = bgColor;
mBgPaint.setColor(bgColor);
invalidate();
}
/**
* 設(shè)置已有進(jìn)度的背景顏色
*/
public void setProgressBgColor(int progressBgColor) {
//沒有變化撒穷,不重繪
if (progressBgColor == mProgressBgColor) {
return;
}
this.mProgressBgColor = progressBgColor;
mProgressPaint.setColor(progressBgColor);
invalidate();
}
/**
* 設(shè)置進(jìn)度
*/
public void setProgress(float progress) {
setProgress(progress, false);
}
/**
* 設(shè)置進(jìn)度
*
* @param fromUser 是否是用戶觸摸發(fā)生的改變
*/
public void setProgress(float progress, boolean fromUser) {
//忽略相同進(jìn)度的設(shè)置
if (mProgress == progress) {
return;
}
// if (progress > mMin && progress < mMax) {
// }
this.mProgress = progress;
invalidate();
if (mOnProgressUpdateListener != null) {
mOnProgressUpdateListener.onProgressUpdate(this, progress, fromUser);
}
}
/**
* 獲取當(dāng)前進(jìn)度
*/
public float getProgress() {
return mProgress;
}
/**
* 設(shè)置進(jìn)度最小值
*/
public void setMin(float min) {
this.mMin = min;
invalidate();
}
/**
* 獲取最小進(jìn)度
*/
public float getMin() {
return mMin;
}
/**
* 設(shè)置進(jìn)度最大值
*/
public void setMax(float max) {
this.mMax = max;
invalidate();
}
/**
* 獲取最大進(jìn)度
*/
public float getMax() {
return mMax;
}
public interface OnProgressUpdateListener {
/**
* 按下時(shí)回調(diào)
*/
void onStartTrackingTouch(VerticalSeekBar seekBar);
/**
* 進(jìn)度更新時(shí)回調(diào)
*
* @param progress 當(dāng)前進(jìn)度
* @param fromUser 是否是用戶改變的
*/
void onProgressUpdate(VerticalSeekBar seekBar, float progress, boolean fromUser);
/**
* 松手時(shí)回調(diào)
*/
void onStopTrackingTouch(VerticalSeekBar seekBar);
}
public static class SimpleProgressUpdateListener implements OnProgressUpdateListener {
@Override
public void onStartTrackingTouch(VerticalSeekBar seekBar) {
}
@Override
public void onProgressUpdate(VerticalSeekBar seekBar, float progress, boolean fromUser) {
}
@Override
public void onStopTrackingTouch(VerticalSeekBar seekBar) {
}
}
public void setOnProgressUpdateListener(
VerticalSeekBar.OnProgressUpdateListener onProgressUpdateListener) {
mOnProgressUpdateListener = onProgressUpdateListener;
}
/**
* 獲取當(dāng)前進(jìn)度值比值
*/
public float getProgressRatio() {
return mProgress / (mMax * 1.0f);
}
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
public static int dip2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
}
使用
- xml中添加控件匣椰,記得改控件的包名
<com.zh.cavas.sample.widget.VerticalSeekBar
android:id="@+id/volume_seek_bar"
android:layout_width="36dp"
android:layout_height="114dp"
tools:vsb_progress="50"
app:vsb_min_progress="1"
app:vsb_max_progress="100"
app:vsb_bg="#EDF0FA"
app:vsb_progress_bg="#3DDA9B"
app:vsb_bg_radius="8dp" />
- 設(shè)置監(jiān)聽回調(diào),最小值端礼、最大值窝爪,當(dāng)前進(jìn)度
VerticalSeekBar volumeSeekBar = findViewById(R.id.volume_seek_bar);
volumeSeekBar.setOnProgressUpdateListener(new VerticalSeekBar.SimpleProgressUpdateListener() {
@SuppressLint("SetTextI18n")
@Override
public void onProgressUpdate(VerticalSeekBar seekBar, float progress, boolean fromUser) {
//設(shè)置音量
Log.d(TAG, "VolumeSeekBar onProgressUpdate => progress = " + progress);
}
});
volumeSeekBar.setMin(0f);
volumeSeekBar.setMax(1f);
volumeSeekBar.setProgress(0.6f);