文章目录
前言
SeekBar是Android的常用控件之一,简单总结下几种用法。
一、SeekBar基础用法
SeekBar是ProgressBar的扩展,是一个可以拖动的进度条,这样用户就可以通过拖动控制条来改变进度。系统默认只有水平样式。
1、水平样式
2、常用属性
- 设置进度条范围最大值
android:max=“” - 设置进度条范围最小值
android:min=“” - 设置当前进度值
android:progress=“” - 设置第二进度值(类似缓冲进度)
android:secondaryProgress =“” - 设置进度条的图片
android:progressDrawable = “” - 设置进度条的滑块图片
android:thumb = “”
2、布局写法
<SeekBar
android:id="@+id/sb_number"
android:layout_marginStart="@dimen/x38"
android:layout_marginEnd="@dimen/x42"
android:background="@null"
android:splitTrack="false"
android:layout_width="@dimen/x540"
android:layout_height="wrap_content"
android:maxHeight="@dimen/x10"
android:progressDrawable="@drawable/seekbar_volume"
android:thumb="@drawable/icon_seekbar_thumb"/>
3、代码调用
SeekBar seekBar = contentView.findViewById(R.id.sb_number);
seekBar.setProgress(100);
seekBar.setMin(0);
seekBar.setMax(finalMaxTypeVolume);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// 进度变化时的回调
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// 开始拖动时的回调
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// 停止拖动时的回调
}
});
二、垂直seekBar
默认的SeekBar没有设置方向的功能,垂直方向需要重新SeekBar.
1.关键代码,重新onDraw方法,将控件旋转90度
protected void onDraw(Canvas c) {
c.rotate(-90);
c.translate(-getHeight(), 0);
super.onDraw(c);
}
三、圆弧seekBar
圆弧seekBar可以用来在复杂的界面使用,相对代码更加复杂,本文方法自定义view绘制。
1. 自定义圆弧SeekBar
/**
* 圆弧 SeekBar
*/
public class ArcSeekBarPro extends View {
/**
* 画笔
*/
private Paint mPaint;
/**
* 笔画描边的宽度
*/
private float mStrokeWidth;
private Paint.Cap mStrokeCap = Paint.Cap.ROUND;
/**
* 默认的控件宽度,也是圆弧直径
*/
private int defaultArcDiameter;
/**
* 开始角度
*/
private int mStartAngle = 0;
/**
* 路径角度
*/
private int mSweepAngle = 30;
/**
* 圆心坐标x
*/
private float mCircleCenterX;
/**
* 圆心坐标y
*/
private float mCircleCenterY;
/**
* 弧形 正常颜色
*/
private int mNormalColor = 0x80D0D4D9;
/**
* 进度颜色
*/
private int mProgressColor = 0x99FFFFFF;
/**
* 半径
*/
private float mRadius;
/**
* 最大进度
*/
private int mMax = 10;
/**
* 当前进度
*/
private int mProgress = 0;
/**
* 进度百分比
*/
private int mProgressPercent;
private Bitmap mThumbBitmap;
private Matrix mMatrix;
/**
* 拖动按钮的画笔宽度
*/
private float mThumbStrokeWidth;
/**
* 拖动按钮的颜色
*/
private int mThumbColor = 0xFFD5BDAD;
/**
* 拖动按钮的半径
*/
private float mThumbRadius;
/**
* 拖动按钮的中心点X坐标
*/
private float mThumbCenterX;
/**
* 拖动按钮的中心点Y坐标
*/
private float mThumbCenterY;
/**
* 触摸时可偏移距离
*/
private float mAllowableOffsets;
/**
* 触摸时按钮半径放大量
*/
private float mThumbRadiusEnlarges;
/**
* 是否显示拖动按钮
*/
private boolean isShowThumb = true;
/**
* 手势,用来处理点击事件
*/
private GestureDetector mDetector;
/**
* 是否可以拖拽
*/
private boolean isCanDrag = false;
/**
* 是否启用拖拽改变进度
*/
private boolean isEnabledDrag = true;
/**
* 是否启用点击改变进度
*/
private boolean isEnabledSingle = true;
private OnChangeListener mOnChangeListener;
public ArcSeekBarPro(Context context) {
this(context, null);
}
public ArcSeekBarPro(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ArcSeekBarPro(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ArcSeekBarPro);
mThumbBitmap = Utils.loadBitmap(R.drawable.icon_seekbar_pro_thumb, context);
mMatrix = new Matrix();
DisplayMetrics displayMetrics = getDisplayMetrics();
defaultArcDiameter = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, context.getResources().getDimension(R.dimen.arc_diameter), getDisplayMetrics());
mStrokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, context.getResources().getDimension(R.dimen.eac_control_thumb), displayMetrics);
mThumbRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, context.getResources().getDimension(R.dimen.thumb_drawable_radius), displayMetrics);
mThumbStrokeWidth = mThumbRadius;
mAllowableOffsets = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, displayMetrics);
mThumbRadiusEnlarges = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, displayMetrics);
int size = a.getIndexCount();
for (int i = 0; i < size; i++) {
int attr = a.getIndex(i);
if (attr == R.styleable.ArcSeekBarPro_arcStrokeWidth) {
mStrokeWidth = a.getDimension(attr, mStrokeWidth);
} else if (attr == R.styleable.ArcSeekBarPro_arcStrokeCap) {
mStrokeCap = getStrokeCap(a.getInt(attr, 3));
} else if (attr == R.styleable.ArcSeekBarPro_arcNormalColor) {
mNormalColor = a.getColor(attr, mNormalColor);
} else if (attr == R.styleable.ArcSeekBarPro_arcProgressColor) {
mProgressColor = a.getColor(attr, mProgressColor);
} else if (attr == R.styleable.ArcSeekBarPro_arcStartAngle) {
mStartAngle = a.getInt(attr, mStartAngle);
} else if (attr == R.styleable.ArcSeekBarPro_arcSweepAngle) {
mSweepAngle = a.getInt(attr, mSweepAngle);
} else if (attr == R.styleable.ArcSeekBarPro_arcMax) {
int max = a.getInt(attr, mMax);
if (max > 0) {
mMax = max;
}
} else if (attr == R.styleable.ArcSeekBarPro_arcProgress) {
mProgress = a.getInt(attr, mProgress);
} else if (attr == R.styleable.ArcSeekBarPro_arcThumbStrokeWidth) {
mThumbStrokeWidth = a.getDimension(attr, mThumbStrokeWidth);
} else if (attr == R.styleable.ArcSeekBarPro_arcThumbColor) {
mThumbColor = a.getColor(attr, mThumbColor);
} else if (attr == R.styleable.ArcSeekBarPro_arcThumbRadius) {
mThumbRadius = a.getDimension(attr, mThumbRadius);
} else if (attr == R.styleable.ArcSeekBarPro_arcThumbRadiusEnlarges) {
mThumbRadiusEnlarges = a.getDimension(attr, mThumbRadiusEnlarges);
} else if (attr == R.styleable.ArcSeekBarPro_arcShowThumb) {
isShowThumb = a.getBoolean(attr, isShowThumb);
} else if (attr == R.styleable.ArcSeekBarPro_arcAllowableOffsets) {
mAllowableOffsets = a.getDimension(attr, mAllowableOffsets);
} else if (attr == R.styleable.ArcSeekBarPro_arcEnabledDrag) {
isEnabledDrag = a.getBoolean(attr, true);
} else if (attr == R.styleable.ArcSeekBarPro_arcEnabledSingle) {
isEnabledSingle = a.getBoolean(attr, true);
}
}
a.recycle();
mProgressPercent = (int) (mProgress * 100.0f / mMax);
mPaint = new Paint();
mDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent event) {
if (isInArc(event.getX(), event.getY())) {
updateDragThumb(event.getX(), event.getY(), true);
if (mOnChangeListener != null) {
mOnChangeListener.onSingleTapUp();
}
return true;
}
return super.onSingleTapUp(event);
}
});
}
private Paint.Cap getStrokeCap(int value) {
switch (value) {
case 1:
return Paint.Cap.BUTT;
case 2:
return Paint.Cap.SQUARE;
default:
return Paint.Cap.ROUND;
}
}
private DisplayMetrics getDisplayMetrics() {
return getResources().getDisplayMetrics();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = measureHandler(widthMeasureSpec, defaultArcDiameter);
int height = measureHandler(heightMeasureSpec, defaultArcDiameter);
//圆心坐标
mCircleCenterX = (width + getPaddingLeft() - getPaddingRight()) / 2.0f;
mCircleCenterY = (height + getPaddingTop() - getPaddingBottom()) / 2.0f;
//计算间距
int padding = Math.max(getPaddingLeft() + getPaddingRight(), getPaddingTop() + getPaddingBottom());
//半径=视图宽度-横向或纵向内间距值 - 画笔宽度
mRadius = (width - padding - Math.max(mStrokeWidth, mThumbStrokeWidth)) / 2.0f - mThumbRadius;
setMeasuredDimension(width, height);
}
/**
* 测量
*
* @param measureSpec
* @param defaultSize
* @return
*/
private int measureHandler(int measureSpec, int defaultSize) {
int result = defaultSize;
int measureMode = MeasureSpec.getMode(measureSpec);
int measureSize = MeasureSpec.getSize(measureSpec);
if (measureMode == MeasureSpec.EXACTLY) {
result = measureSize;
} else if (measureMode == MeasureSpec.AT_MOST) {
result = Math.min(defaultSize, measureSize);
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawArc(canvas);
drawThumb(canvas);
}
/**
* 绘制弧形
*
* @param canvas
*/
private void drawArc(Canvas canvas) {
mPaint.reset();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setShader(null);
mPaint.setStrokeCap(mStrokeCap);
//进度圆半径
float diameter = mRadius * 2;
float startX = mCircleCenterX - mRadius;
float startY = mCircleCenterY - mRadius;
RectF rectF1 = new RectF(startX, startY, startX + diameter, startY + diameter);
if (mNormalColor != 0) {
mPaint.setColor(mNormalColor);
//绘制底层弧形
canvas.drawArc(rectF1, mStartAngle, mSweepAngle, false, mPaint);
}
mPaint.setColor(mProgressColor);
float ratio = getRatio();
if (ratio != 0) {
//绘制当前进度弧形
canvas.drawArc(rectF1, mStartAngle, mSweepAngle * ratio, false, mPaint);
}
}
private void drawThumb(Canvas canvas) {
if (isShowThumb) {
mPaint.reset();
// mPaint.setAntiAlias(true);
// mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
// mPaint.setStrokeWidth(mThumbStrokeWidth);
// mPaint.setColor(mThumbColor);
float thumbAngle = mStartAngle + mSweepAngle * getRatio();
//已知圆心,半径,角度,求圆上的点坐标
mThumbCenterX = (float) (mCircleCenterX + mRadius * Math.cos(Math.toRadians(thumbAngle)));
mThumbCenterY = (float) (mCircleCenterY + mRadius * Math.sin(Math.toRadians(thumbAngle)));
// if (isCanDrag) {
// canvas.drawCircle(mThumbCenterX, mThumbCenterY, mThumbRadius + mThumbRadiusEnlarges, mPaint);
// } else {
// canvas.drawCircle(mThumbCenterX, mThumbCenterY, mThumbRadius, mPaint);
// }
if (null != mThumbBitmap) {
mMatrix.setTranslate(mThumbCenterX - mThumbRadius, mThumbCenterY - mThumbRadius);
mMatrix.preScale(1, 1, 0, 0);
mPaint.setAlpha(255);
canvas.drawBitmap(mThumbBitmap, mMatrix, mPaint);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isEnabledDrag) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
checkCanDrag(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
if (isCanDrag) {
updateDragThumb(event.getX(), event.getY(), false);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
getParent().requestDisallowInterceptTouchEvent(false);
if (mOnChangeListener != null) {
mOnChangeListener.onStopTrackingTouch(isCanDrag);
}
isCanDrag = false;
invalidate();
break;
}
}
if (isEnabledSingle) {
mDetector.onTouchEvent(event);
}
return isEnabledSingle || isEnabledDrag || super.onTouchEvent(event);
}
/**
* 判断坐标点是否在弧形上
*
* @param x
* @param y
* @return
*/
private boolean isInArc(float x, float y) {
float distance = getDistance(mCircleCenterX, mCircleCenterY, x, y);
if (Math.abs(distance - mRadius) <= mStrokeWidth / 2f + mAllowableOffsets) {
if (mSweepAngle < 360) {
float angle = (getTouchDegrees(x, y) + mStartAngle) % 360;
if (mSweepAngle < 0) {
if (mStartAngle + mSweepAngle + 360 <= 360) {
return angle <= mStartAngle || angle >= (mStartAngle + mSweepAngle + 360) % 360;
} else {
return angle <= mStartAngle && angle >= mStartAngle + mSweepAngle + 360;
}
}
if (mStartAngle + mSweepAngle <= 360) {
return angle >= mStartAngle && angle <= mStartAngle + mSweepAngle;
} else {
return angle >= mStartAngle || angle <= (mStartAngle + mSweepAngle) % 360;
}
}
return true;
}
return false;
}
/**
* 获取两点间距离
*
* @param x1
* @param y1
* @param x2
* @param y2
* @return
*/
private float getDistance(float x1, float y1, float x2, float y2) {
return (float) Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}
/**
* 更新多拽进度
*
* @param x
* @param y
* @param isSingle
*/
private void updateDragThumb(float x, float y, boolean isSingle) {
int progress = getProgressForAngle(getTouchDegrees(x, y));
if (!isSingle) {
int tempProgressPercent = (int) (progress * 100.0f / mMax);
//当滑动至至边界值时,增加进度校准机制
if (mProgressPercent < 10 && tempProgressPercent > 90) {
progress = 0;
} else if (mProgressPercent > 90 && tempProgressPercent < 10) {
progress = mMax;
}
int progressPercent = (int) (progress * 100.0f / mMax);
//拖动进度突变不允许超过30%
if (Math.abs(progressPercent - mProgressPercent) > 30) {
return;
}
}
setProgress(progress, true);
}
/**
* 通过弧度换算得到当前精度
*
* @param angle
* @return
*/
private int getProgressForAngle(float angle) {
int touchProgress = 0;
if (mSweepAngle < 0) {
touchProgress = Math.round(1.0f * mMax / mSweepAngle * (angle - 360));
} else {
touchProgress = Math.round(1.0f * mMax / mSweepAngle * angle);
}
return touchProgress;
}
/**
* 获取触摸坐标的夹角度数
*
* @param x
* @param y
* @return
*/
private float getTouchDegrees(float x, float y) {
float x1 = x - mCircleCenterX;
float y1 = y - mCircleCenterY;
//求触摸点弧形的夹角度数
float angle = (float) (Math.atan2(y1, x1) * 180 / Math.PI);
angle -= mStartAngle;
while (angle < 0) {
angle += 360;
}
return angle;
}
/**
* 检测是否可拖拽
*
* @param x
* @param y
*/
private void checkCanDrag(float x, float y) {
float distance = getDistance(mThumbCenterX, mThumbCenterY, x, y);
isCanDrag = distance <= mThumbRadius + mAllowableOffsets;
if (mOnChangeListener != null) {
mOnChangeListener.onStartTrackingTouch(isCanDrag);
}
invalidate();
}
/**
* 进度比例
*
* @return
*/
private float getRatio() {
return mProgress * 1.0f / mMax;
}
/**
* 设置最大进度
*
* @param max
*/
public void setMax(int max) {
if (max > 0) {
this.mMax = max;
invalidate();
}
}
/**
* 设置当前进度
*
* @param progress
*/
public void setProgress(int progress) {
setProgress(progress, false);
}
private void setProgress(int progress, boolean fromUser) {
if (progress < 0) {
progress = 0;
} else if (progress > mMax) {
progress = mMax;
}
this.mProgress = progress;
mProgressPercent = (int) (mProgress * 100.0f / mMax);
invalidate();
if (mOnChangeListener != null) {
mOnChangeListener.onProgressChanged(mProgress, mMax, fromUser);
}
}
/**
* 设置正常颜色
*
* @param color
*/
public void setNormalColor(int color) {
this.mNormalColor = color;
invalidate();
}
/**
* 设置进度颜色(纯色)
*
* @param color
*/
public void setProgressColor(int color) {
this.mProgressColor = color;
invalidate();
}
/**
* 设置进度颜色
*
* @param resId
*/
public void setProgressColorResource(int resId) {
int color = getResources().getColor(resId);
setProgressColor(color);
}
public int getStartAngle() {
return mStartAngle;
}
public int getSweepAngle() {
return mSweepAngle;
}
public float getCircleCenterX() {
return mCircleCenterX;
}
public float getCircleCenterY() {
return mCircleCenterY;
}
public float getRadius() {
return mRadius;
}
public int getMax() {
return mMax;
}
public int getProgress() {
return mProgress;
}
public float getThumbRadius() {
return mThumbRadius;
}
public float getThumbCenterX() {
return mThumbCenterX;
}
public float getThumbCenterY() {
return mThumbCenterY;
}
public float getAllowableOffsets() {
return mAllowableOffsets;
}
public boolean isEnabledDrag() {
return isEnabledDrag;
}
public boolean isEnabledSingle() {
return isEnabledSingle;
}
public boolean isShowThumb() {
return isShowThumb;
}
public float getThumbRadiusEnlarges() {
return mThumbRadiusEnlarges;
}
/**
* 触摸时按钮半径放大量
*
* @param thumbRadiusEnlarges
*/
public void setThumbRadiusEnlarges(float thumbRadiusEnlarges) {
this.mThumbRadiusEnlarges = thumbRadiusEnlarges;
}
/**
* 是否显示拖动按钮
*
* @param showThumb
*/
public void setShowThumb(boolean showThumb) {
isShowThumb = showThumb;
invalidate();
}
/**
* 触摸时可偏移距离:偏移量越大,触摸精度越小
*
* @param allowableOffsets
*/
public void setAllowableOffsets(float allowableOffsets) {
this.mAllowableOffsets = allowableOffsets;
}
/**
* 是否启用拖拽
*
* @param enabledDrag 默认为 true,为 false 时 相当于{@link android.widget.ProgressBar}
*/
public void setEnabledDrag(boolean enabledDrag) {
isEnabledDrag = enabledDrag;
}
/**
* 设置是否启用点击改变进度
*
* @param enabledSingle
*/
public void setEnabledSingle(boolean enabledSingle) {
isEnabledSingle = enabledSingle;
}
/**
* 进度百分比
*
* @return
*/
public int getProgressPercent() {
return mProgressPercent;
}
/**
* 设置进度改变监听
*
* @param onChangeListener
*/
public void setOnChangeListener(OnChangeListener onChangeListener) {
this.mOnChangeListener = onChangeListener;
}
public interface OnChangeListener {
/**
* 跟踪触摸事件开始时回调此方法 {@link MotionEvent#ACTION_DOWN}
*
* @param isCanDrag
*/
void onStartTrackingTouch(boolean isCanDrag);
/**
* 进度改变时回调此方法
*
* @param progress
* @param max
* @param fromUser
*/
void onProgressChanged(int progress, int max, boolean fromUser);
/**
* 跟踪触摸事件停止时回调此方法 {@link MotionEvent#ACTION_UP}
*/
void onStopTrackingTouch(boolean isCanDrag);
/**
* 通过点击事件改变进度后回调此方法 {@link GestureDetector#GestureDetector#onSingleTapUp()}
*/
void onSingleTapUp();
}
}
三种SeekBar效果如图所示
三、常见问题
- 自定义图标滑到最小或者最大时,图标异常
android:thumbOffset="0px"
- 滑块间隔是否覆盖滑轨,系统默认是间隔
android:splitTrack=“false” //默认为true
- 解决 seekBar 宽度不撑满布局
android:paddingStart="0dp"
android:paddingEnd="0dp"
四、参考链接
- https://github.com/jenly1314/ArcSeekBar
- https://blog.csdn.net/liosen/article/details/124304685