样式类似iOS控制中心的音量、亮度控制。
源码
- 自定义属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="VerticalSeekBar">
<!-- 背景进度颜色 -->
<attr name="vsb_bg" format="color" />
<!-- 已有进度颜色 -->
<attr name="vsb_progress_bg" format="color" />
<!-- 已有进度的蒙版颜色 -->
<attr name="vsb_progress_mask_bg" format="color" />
<!-- 蒙版是否开启 -->
<attr name="vsb_has_progress_mask_enable" format="boolean" />
<!-- 进度条圆角半径 -->
<attr name="vsb_bg_radius" format="integer|float|dimension" />
<!-- 当前进度值 -->
<attr name="vsb_progress" format="float" />
<!-- 最小进度值 -->
<attr name="vsb_min_progress" format="float" />
<!-- 最大进度值 -->
<attr name="vsb_max_progress" format="float" />
<!-- 保留高度,可以让最小值处于的高度不在最底部,值为dp -->
<attr name="vsb_retain_progress_height" format="integer|float|dimension"/>
<!-- 是否debug模式,debug模式会把当前进度绘制到View上,方便调试 -->
<attr name="vsb_is_debug" format="boolean"/>
</declare-styleable>
</resources>
- 代码实现
/**
* 垂直拽托进度条
*/
public class VerticalSeekBar extends View {
/**
* View默认最小宽度
*/
private int mDefaultWidth;
/**
* View默认最小高度
*/
private int mDefaultHeight;
/**
* 控件宽
*/
private int mViewWidth;
/**
* 控件高
*/
private int mViewHeight;
/**
* 背景颜色
*/
private int mBgColor;
/**
* 进度背景颜色
*/
private int mProgressBgColor;
/**
* 进度蒙版颜色
*/
private int mProgressMaskBgColor;
/**
* 进度蒙版是否开启
*/
private boolean mProgressMaskEnable;
/**
* 进度条的圆角半径
*/
private int mBgRadius;
/**
* 当前进度
*/
private float mProgress;
/**
* 最小进度值
*/
private float mMin;
/**
* 最大进度值
*/
private float mMax;
/**
* 最小保留高度,可以让进度条不拖到底
*/
private int mRetainProgressHeight;
/**
* 是否debug调试
*/
private boolean mIsDebug;
/**
* 背景画笔
*/
private Paint mBgPaint;
/**
* 进度画笔
*/
private Paint mProgressPaint;
/**
* 进度蒙版画笔
*/
private Paint mProgressMarkBgPaint;
/**
* Debug信息画笔
*/
private Paint mDebugPaint;
/**
* 进度更新监听
*/
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);
//进度画笔
mProgressPaint = new Paint();
mProgressPaint.setColor(mProgressBgColor);
mProgressPaint.setAntiAlias(true);
mProgressPaint.setStyle(Paint.Style.FILL);
//进度蒙版画笔
mProgressMarkBgPaint = new Paint();
mProgressMarkBgPaint.setColor(mProgressMaskBgColor);
mProgressMarkBgPaint.setAntiAlias(true);
mProgressMarkBgPaint.setStyle(Paint.Style.FILL);
//Debug信息画笔
mDebugPaint = new Paint();
mDebugPaint.setColor(Color.parseColor("#FF0000"));
mDebugPaint.setTextSize(sp2px(context, 12f));
mDebugPaint.setTextAlign(Paint.Align.CENTER);
mDebugPaint.setAntiAlias(true);
mDebugPaint.setStyle(Paint.Style.FILL);
//计算默认宽、高
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 defaultProgressMaskBgColor = Color.parseColor("#33000000");
int defaultBgRadius = dip2px(context, 8f);
float defaultProgress = 0;
float defaultMinProgress = 0;
int defaultMaxProgress = 100;
//默认不设置保留高度
int defaultRetainProgressHeight = 0;
if (attrs != null) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.VerticalSeekBar, defStyleAttr, 0);
//进度背景颜色
mBgColor = array.getColor(R.styleable.VerticalSeekBar_vsb_bg, defaultBgColor);
//已有进度的背景颜色
mProgressBgColor = array.getColor(R.styleable.VerticalSeekBar_vsb_progress_bg, defaultProgressBgColor);
//已有进度的蒙版颜色
mProgressMaskBgColor = array.getColor(R.styleable.VerticalSeekBar_vsb_progress_mask_bg, defaultProgressMaskBgColor);
//是否开启蒙版
mProgressMaskEnable = array.getBoolean(R.styleable.VerticalSeekBar_vsb_has_progress_mask_enable, false);
//进度条的圆角
mBgRadius = array.getDimensionPixelSize(R.styleable.VerticalSeekBar_vsb_bg_radius, defaultBgRadius);
//当前进度值
mProgress = array.getFloat(R.styleable.VerticalSeekBar_vsb_progress, defaultProgress);
//最小进度值
mMin = array.getFloat(R.styleable.VerticalSeekBar_vsb_min_progress, defaultMinProgress);
//最大进度值
mMax = array.getFloat(R.styleable.VerticalSeekBar_vsb_max_progress, defaultMaxProgress);
//保留高度
mRetainProgressHeight = array.getDimensionPixelSize(R.styleable.VerticalSeekBar_vsb_retain_progress_height, defaultRetainProgressHeight);
//是否Debug调试
mIsDebug = array.getBoolean(R.styleable.VerticalSeekBar_vsb_is_debug, false);
array.recycle();
} else {
mBgColor = defaultBgColor;
mProgressBgColor = defaultProgressBgColor;
mProgressMaskBgColor = defaultProgressMaskBgColor;
mProgressMaskEnable = false;
mProgress = defaultProgress;
mMin = defaultMinProgress;
mMax = defaultMaxProgress;
mRetainProgressHeight = defaultRetainProgressHeight;
mIsDebug = false;
}
}
@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);
//画进度条
drawProgress(canvas);
if (mProgressMaskEnable) {
//画进度条蒙版
drawProgressMark(canvas);
}
if (mIsDebug) {
//画Debug信息
drawDebugInfo(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);
}
/**
* 画进度
*/
private void drawProgress(Canvas canvas) {
drawProgressByPaint(canvas, mProgressPaint);
}
/**
* 画进度蒙版
*/
private void drawProgressMark(Canvas canvas) {
drawProgressByPaint(canvas, mProgressMarkBgPaint);
}
/**
* 画Debug信息
*/
private void drawDebugInfo(Canvas canvas) {
float halfWidth = (mViewWidth - getPaddingStart() - getPaddingEnd()) / 2f;
float halfHeight = (mViewHeight - getPaddingTop() - getPaddingBottom()) / 2f;
//计算baseline
Paint.FontMetrics fontMetrics = mDebugPaint.getFontMetrics();
float distance = ((fontMetrics.bottom - fontMetrics.top) / 2f) - fontMetrics.bottom;
float baseline = halfHeight + distance;
//获取当前进度值,并只取1位小数值
float progressRatio = getProgressRatio();
String text = floatValueRetainFormat(1, progressRatio) + "";
canvas.drawText(
text,
halfWidth,
baseline,
mDebugPaint
);
}
/**
* 画进度,传画笔,因为进度背景和蒙版其实是一样的,只是用的画笔不同
*/
private void drawProgressByPaint(Canvas canvas, Paint paint) {
float contentHeight = mViewHeight - getPaddingTop() - getPaddingBottom();
float progressRatio = getProgressRatio();
//计算进度条可用进度,总高度 - 设置的保留高度
float usableHeight = contentHeight - mRetainProgressHeight;
//计算出当前进度的top值
float top;
if (progressRatio == 0) {
top = usableHeight;
} else {
top = usableHeight * (1 - progressRatio);
}
//画进度矩形
RectF rect = new RectF(getFrameLeft(),
top,
getFrameRight(),
getFrameBottom());
canvas.drawRect(rect, paint);
}
@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事件,然后让父类不进行拦截
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的时候,计算拽托进度
float endY = event.getY();
//限制拉到顶
if (endY < 0) {
endY = 0;
}
//限制拉到底
if (endY > contentHeight) {
endY = contentHeight;
}
//计算触摸点和高度的差值
float distanceY = Math.abs(contentHeight - endY);
float ratio = distanceY / contentHeight;
//计算百分比应该有的进度:进度 = 总进度 * 进度百分比值
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;
}
/**
* 设置进度背景颜色
*/
public void setBgColor(int bgColor) {
//没有变化,不重绘
if (bgColor == mBgColor) {
return;
}
this.mBgColor = bgColor;
mBgPaint.setColor(bgColor);
invalidate();
}
/**
* 设置已有进度的背景颜色
*/
public void setProgressBgColor(int progressBgColor) {
//没有变化,不重绘
if (progressBgColor == mProgressBgColor) {
return;
}
this.mProgressBgColor = progressBgColor;
mProgressPaint.setColor(progressBgColor);
invalidate();
}
/**
* 设置进度
*/
public void setProgress(float progress) {
setProgress(progress, false);
}
/**
* 设置进度
*
* @param fromUser 是否是用户触摸发生的改变
*/
public void setProgress(float progress, boolean fromUser) {
//忽略相同进度的设置
if (mProgress == progress) {
return;
}
// if (progress > mMin && progress < mMax) {
// }
this.mProgress = progress;
invalidate();
if (mOnProgressUpdateListener != null) {
mOnProgressUpdateListener.onProgressUpdate(this, progress, fromUser);
}
}
/**
* 获取当前进度
*/
public float getProgress() {
return mProgress;
}
/**
* 设置进度最小值
*/
public void setMin(float min) {
this.mMin = min;
invalidate();
}
/**
* 获取最小进度
*/
public float getMin() {
return mMin;
}
/**
* 设置进度最大值
*/
public void setMax(float max) {
this.mMax = max;
invalidate();
}
/**
* 获取最大进度
*/
public float getMax() {
return mMax;
}
/**
* 是否开启进度蒙版
*/
public void setProgressMaskEnable(boolean enable) {
this.mProgressMaskEnable = enable;
invalidate();
}
/**
* 设置进度最小保留高度
*
* @param retainProgressHeight 像素值
*/
public void setRetainProgressHeight(int retainProgressHeight) {
mRetainProgressHeight = retainProgressHeight;
}
/**
* 是否调试模式
*/
public boolean isDebug() {
return mIsDebug;
}
/**
* 设置是否调试模式
*/
public void setDebug(boolean debug) {
mIsDebug = debug;
invalidate();
}
public interface OnProgressUpdateListener {
/**
* 按下时回调
*/
void onStartTrackingTouch(VerticalSeekBar seekBar);
/**
* 进度更新时回调
*
* @param progress 当前进度
* @param fromUser 是否是用户改变的
*/
void onProgressUpdate(VerticalSeekBar seekBar, float progress, boolean fromUser);
/**
* 松手时回调
*/
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;
}
/**
* 获取当前进度值比值
*/
public float getProgressRatio() {
return mProgress / (mMax * 1.0f);
}
/**
* Float值保留几位小数,返回字符串
*
* @param digits 保留几位
* @param value 要格式化的值
*/
private String floatValueRetainFormat(int digits, float value) {
StringBuilder builder = new StringBuilder("0.");
for (int i = 0; i < digits; i++) {
builder.append("#");
}
DecimalFormat format = new DecimalFormat(builder.toString());
//结果补0
StringBuilder resultBuilder = new StringBuilder(format.format(value));
//如果格式化后是整数,则添加一个.0,例如1,就是1.0,2是2.0
if (!resultBuilder.toString().contains(".")) {
resultBuilder.append(".");
for (int i = 0; i < digits; i++) {
resultBuilder.append("0");
}
}
return resultBuilder.toString();
}
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.psyone.brainmusic.view.wiget.VerticalSeekBar
android:id="@+id/volume_seek_bar"
android:layout_width="36dp"
android:layout_height="114dp"
app:vsb_min_progress="1"
app:vsb_max_progress="100"
app:vsb_bg="#EDF0FA"
app:vsb_progress_bg="#3DDA9B"
app:vsb_bg_radius="8dp" />
- Java代码
//查找控件
VerticalSeekBar volumeSeekBar = findViewById(R.id.volume_seek_bar);
//设置进度背景颜色
volumeSeekBar.setBgColor(Color.parseColor("#00FF00"));
//设置已有进度的背景颜色
volumeSeekBar.setProgressBgColor(Color.parseColor("#FF0000"));
//设置当前进度
volumeSeekBar.setProgress(0.5f);
//设置最小值
volumeSeekBar.setMin(0f);
//设置最大值
volumeSeekBar.setMax(1f);
//设置进度监听
volumeSeekBar.setOnProgressUpdateListener(new VerticalSeekBar.SimpleProgressUpdateListener() {
@Override
public void onProgressUpdate(VerticalSeekBar seekBar, float progress, boolean fromUser) {
super.onProgressUpdate(seekBar, progress, fromUser);
//进度更新回调
}
@Override
public void onStopTrackingTouch(VerticalSeekBar seekBar) {
super.onStopTrackingTouch(seekBar);
//手势触摸结束回调
}
});