之前做项目的时候,有个图片编辑页面,用到拖动条添加编辑效果。老板很喜欢Instagram那种拖动条的效果,0点从中间开始,左减右增,也要做成这样。Android原生的SeekBar,是从左边开始,一直往右边增加的,不能将起点设在中间。然后在GitHub上找了几遍,也没有找到符合要求的,只能自己实现了。
先看一下效果图:
思路:
1、一根线,一个活动圆
2、线分三段,活动圆未到达的前后两段,活动圆扫过的中间段
3、监听触摸事件,刷新活动圆的位置,和三段线
具体实现
1、资源初始化
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MiddleSeekBar, defStyleAttr, R.style.MiddleSeekBar);
mThumbRadius = a.getDimensionPixelSize(R.styleable.MiddleSeekBar_MB_ThumbRadius, 0);
mThumbColor = a.getColor(R.styleable.MiddleSeekBar_MB_ThumbColor, 0);
mTextColor = a.getColor(R.styleable.MiddleSeekBar_MB_TextColor, 0);
mProgressColor = a.getColor(R.styleable.MiddleSeekBar_MB_ProgressColor, 0);
mProgressBackgroundColor = a.getColor(R.styleable.MiddleSeekBar_MB_ProgressBackgroundColor, 0);
mTextSize = a.getDimensionPixelSize(R.styleable.MiddleSeekBar_MB_TextSize, 0);
mProgressWidth = a.getDimensionPixelOffset(R.styleable.MiddleSeekBar_MB_ProgressWidth, 0);
a.recycle();
//初始化提示文字画笔
mTextPaint.setTextSize(mTextSize);
mTextPaint.setColor(mTextColor);
//初始化进度条画笔
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
mProgressPaint.setStyle(Paint.Style.STROKE);
mProgressPaint.setStrokeWidth(mProgressWidth);
//初始化活动圆画笔
mThumbShadowRadius = (int) (getResources().getDisplayMetrics().density * THUMB_SHADOW);
mYOffset = (int) (getResources().getDisplayMetrics().density * Y_OFFSET);
if(!isInEditMode()) {
ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, mThumbPaint);
mThumbPaint.setShadowLayer(mThumbShadowRadius, 0, mYOffset, KEY_SHADOW_COLOR);
}
mThumbPaint.setColor(mThumbColor);
}
2、测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
//suggestHeight算出拖动条的高度
int suggestHeight = (int) (3 * mThumbRadius + mThumbShadowRadius - fontMetrics.top + fontMetrics.bottom + getPaddingTop() + getPaddingBottom());
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
resolveSizeAndState(suggestHeight, heightMeasureSpec, 0));
}
3、绘制
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
//设置最左边的起始点
mStartPoint.set(getPaddingLeft() + mThumbRadius, h - getPaddingBottom() - mThumbRadius - mThumbShadowRadius);
mProgressLength = w - getPaddingLeft() - getPaddingRight() - 2 * mThumbRadius;
//设置活动圆作用区域的长方形
mThumbRect.set(mStartPoint.x + mProgressLength * mThumbRatio - HIT_SCOPE_RATIO * mThumbRadius, mStartPoint.y - HIT_SCOPE_RATIO * mThumbRadius,
mStartPoint.x + mProgressLength * mThumbRatio + HIT_SCOPE_RATIO * mThumbRadius, mStartPoint.y + HIT_SCOPE_RATIO * mThumbRadius);
}
@Override
protected void onDraw(Canvas canvas) {
int y = mStartPoint.y;
int firstX = mStartPoint.x;//最左边点
int secondX = mStartPoint.x + mProgressLength/2;//中间点
int thirdX = (int) mThumbRect.centerX();//活动圆所在点
//draw progress将进度条分成三段,前段没有到达时显示背景色,中段显示进度颜色,后端没有到达时显示背景色
mProgressPaint.setColor(mProgressBackgroundColor);
canvas.drawLine(firstX, y, secondX<thirdX?secondX:thirdX, y, mProgressPaint);
mProgressPaint.setColor(mProgressColor);
if (secondX < thirdX){
canvas.drawLine(secondX, y, thirdX, y, mProgressPaint);
}else {
canvas.drawLine(thirdX, y, secondX, y, mProgressPaint);
}
mProgressPaint.setColor(mProgressBackgroundColor);
canvas.drawLine(thirdX>secondX?thirdX:secondX, y, firstX + mProgressLength, y, mProgressPaint);
//画活动圆
canvas.drawCircle(thirdX, y, mThumbRadius, mThumbPaint);
//画提示文字
mPromptString = ratio2String(mThumbRatio);
mTextPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(mPromptString, mThumbRect.centerX(), mThumbRect.top - mThumbRadius, mTextPaint);
}
4、触摸事件
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//判断触摸点在活动圆方形作用域内
if (mThumbRect.contains(x, y)) {
selectInfo.pointerId = event.getPointerId(0);
selectInfo.isCaptured = true;
}
break;
case MotionEvent.ACTION_MOVE:
if (selectInfo.isCaptured) {
if (moveThumb(x)) return false;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
selectInfo.invalid();
break;
}
if (selectInfo.isCaptured) {
if(mOnSeekBarChangeListener!=null){
mOnSeekBarChangeListener.onProgressChanged(this, mThumbRatio);
}
invalidate();
}
//让父View的 onInterceptTouchEvent(MotionEvent)失效
if(ViewCompat.isAttachedToWindow(this)){
getParent().requestDisallowInterceptTouchEvent(selectInfo.isCaptured);
}
return selectInfo.isCaptured;
}
private boolean moveThumb(float x) {
if (x > mStartPoint.x) {
mThumbRatio = (x - mStartPoint.x) / mProgressLength;
if (mThumbRatio > 1) {
mThumbRatio = 1;
return true;
}
float offsetX = x - mThumbRect.centerX();
//活动圆位置进行偏移调整
mThumbRect.offset(offsetX, 0);
}
return false;
}
总结
就这样,一个中间为起点可以两边拖动的拖动条实现了。
完整代码GitHub