自定义刻度尺,通过Scroller实现的滚动效果,一般用于金额选择,样子为:
1,创建自定view,并在values文件下创建attrs文件
<resources>
<declare-styleable name="ScrollDividingRuleView">
<attr name="line_height" format="dimension|reference" />
<attr name="dividing_text_size" format="dimension|reference" />
declare-styleable>
resources>
ScrollDividingRuleView为创建的自定义view 的名
line_height为刻度线的高度
dividing_text_size 刻度显示文字的大小
2,初始化获取view的属性
private void init(Context context, AttributeSet attrs) {
for (int i = 0; i < attrs.getAttributeCount(); i++) {
String name = attrs.getAttributeName(i);
if ("layout_width".equals(name)) {
String value = attrs.getAttributeValue(i);
if (value.length() > 2) {
if (value.endsWith("dp")) {
mScaleWidth = Utils.dp2px(context, Float.valueOf(value.substring(0, value.length() - 2)));
} else {
mScaleWidth = Float.valueOf(value.substring(0, value.length() - 2));
}
} else if (value.equals("-1") || value.equals("-2") || value.equals("0")) {
mScaleWidth = 0;
}
}else if ("line_height".equals(name)) {
String value = attrs.getAttributeValue(i);
if (value.length() > 2) {
if (value.endsWith("dp")) {
mLineHeight = Utils.dp2px(context, Float.valueOf(value.substring(0, value.length() - 2)));
} else {
mLineHeight = Float.valueOf(value.substring(0, value.length() - 2));
}
} else {
mLineHeight = 50;
}
} else if ("dividing_text_size".equals(name)) {
String value = attrs.getAttributeValue(i);
if (value.length() > 2) {
if (value.endsWith("sp")) {
mTextSize = Utils.sp2px(context, Float.valueOf(value.substring(0, value.length() - 2)));
} else {
mTextSize = Float.valueOf(value.substring(0, value.length() - 2));
}
} else {
mTextSize = 32;
}
}
}
// 画笔
mPaint = new Paint();
//总的高度,因为text的高度和设置的textSize会有误差所以加上20的高度
mRectHeight = (int) (mLineHeight +mTextSize+mTextLineMargin+20);
//初始设置每个刻度间距为30px
mScaleMargin = 30;
mTextList = new ArrayList<>();
//计算宽度
mScaleWidth = (mTextList.size() * 5 - 5) * mScaleMargin;
mScroller = new Scroller(context);
mVelocityTracker = VelocityTracker.obtain();
}
其中VelocityTracker类是为了计算滚动速度,VelocityTracke具体介绍参见Android 官方文档VelocityTracke链接
3,重写onMeasure,onDraw方法
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.GRAY);
// 抗锯齿
mPaint.setAntiAlias(true);
// 设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰
mPaint.setDither(true);
// 空心
mPaint.setStyle(Paint.Style.STROKE);
// 文字居中
mPaint.setTextAlign(Paint.Align.CENTER);
onDrawScale(canvas, mPaint); //画刻度
onDrawLine(canvas, mPaint);//画刻度中间横线
onDrawCenter(canvas, mPaint);//画中心远点
super.onDraw(canvas);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = View.MeasureSpec.makeMeasureSpec(mRectHeight, View.MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, height);
//view宽度
mScaleRange = getMeasuredWidth();
//初始化开始位置
mInitDistance = mScaleRange / 2-mInitPosition*mScaleMargin*5;
}
private void onDrawScale(Canvas canvas, Paint paint) {
paint.setColor(Color.GRAY);
paint.setStrokeWidth(2);
paint.setTextSize(mTextSize);
for (int i = 0, k = 0; i < mTextList.size() * 5 - 4; i++) {
if (i % 5 == 0) { //整值
canvas.drawLine(i * mScaleMargin + mInitDistance, mRectHeight, i * mScaleMargin + mInitDistance, mRectHeight - mLineHeight, paint);
//整值文字
canvas.drawText(mTextList.get(k), i * mScaleMargin + mInitDistance, mRectHeight -mLineHeight-mTextLineMargin, paint);
k++;
} else {
canvas.drawLine(i * mScaleMargin + mInitDistance, mRectHeight - mLineHeight / 4, i * mScaleMargin + mInitDistance, mRectHeight - mLineHeight + mLineHeight / 4, paint);
}
}
}
private void onDrawLine(Canvas canvas, Paint paint) {
paint.setColor(Color.GRAY);
paint.setStrokeWidth(2);
canvas.drawLine(mInitDistance, mRectHeight - mLineHeight / 2, mScaleWidth + mInitDistance, mRectHeight - mLineHeight / 2, paint);
}
private void onDrawCenter(Canvas canvas, Paint paint) {
paint.setColor(Color.GRAY);
paint.setStrokeWidth(6);
for (int i = 0; i < mTextList.size() * 5 - 4; i++) {
if (i % 5 == 0) { //整值
canvas.drawCircle(i * mScaleMargin + mInitDistance, mRectHeight-mLineHeight/2, 4, paint);
}
}
}
mInitDistance 是为了计算中间点显示初始化中间点为哪个位置
4,处理触摸事件
这里就涉及到了一些计算,当手指抬起如果为快速滑动则滑动到开始或者结尾,慢速滑动则会滚动到比较近的主刻度,如果滑动超过也会回到起点或者重点
@Override
public boolean onTouchEvent(MotionEvent event) {
mVelocityTracker.addMovement(event);
int x = (int) event.getX();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mScroller != null && !mScroller.isFinished()) {
mScroller.abortAnimation();
}
mScrollLastX = x;
mStartX = x;
return true;
case MotionEvent.ACTION_MOVE:
mVelocityTracker.computeCurrentVelocity(1000);
int dataX = mScrollLastX - x;
smoothScrollBy(dataX, 0);
mScrollLastX = x;
return true;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
int moveX = mStartX - x;
//如果快速滑动则滚动到滑动方向结尾的位置
if(Math.abs(mVelocityTracker.getXVelocity())>4000) {
if(moveX>0) {
mListener.onScaleScrollChanged(mTextList.size()-1);
mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), (int) (mScaleWidth - mScaleRange / 2 + mInitDistance)-mScroller.getFinalX(), 0, 800);
}else{
mListener.onScaleScrollChanged(0);
mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), -mScaleRange / 2 + mInitDistance-mScroller.getFinalX(), 0, 800);
}
}else {
dealActionUp(moveX);
}
return true;
}
return super.onTouchEvent(event);
}
/**
* 处理手势抬起之后的操作
*/
private void dealActionUp(int moveX) {
int finalX = mScroller.getFinalX();//当前位置
//计算获取手指抬起之后要回到的位置
if (moveX >= 0 && finalX > mScaleWidth - mScaleRange / 2 + mInitDistance) {
finalX = (int) (mScaleWidth - mScaleRange / 2 + mInitDistance);
} else if (moveX <= 0 && finalX <= -mScaleRange / 2 + mInitDistance) {
finalX = -mScaleRange / 2 + mInitDistance;
} else {
int round = Math.abs(Math.round((float) finalX / mScaleMargin));
if (round % 5 > 2) {
if(finalX>0) {
if (finalX / mScaleMargin < round) {
finalX = finalX + (5 - round % 5) * mScaleMargin - finalX % mScaleMargin + mScaleMargin;
} else {
finalX = finalX + (5 - round % 5) * mScaleMargin - finalX % mScaleMargin;
}
}else{
if (finalX / mScaleMargin > -round) {
finalX = finalX + ( round % 5-5) * mScaleMargin - finalX % mScaleMargin - mScaleMargin;
} else {
finalX = finalX + (round % 5-5) * mScaleMargin - finalX % mScaleMargin;
}
}
} else {
if(finalX>0) {
if (finalX / mScaleMargin < round) {
finalX = finalX - (round % 5) * mScaleMargin + (mScaleMargin - finalX % mScaleMargin);
} else {
finalX = finalX - (round % 5) * mScaleMargin - finalX % mScaleMargin;
}
}else{
if (finalX / mScaleMargin > -round) {
finalX = finalX - (-round % 5) * mScaleMargin - finalX % mScaleMargin-mScaleMargin;
} else {
finalX = finalX - (-round % 5) * mScaleMargin - finalX % mScaleMargin;
}
}
}
}
mListener.onScaleScrollChanged(finalX/ mScaleMargin / 5+mInitPosition);//返回滚动选中的位置
mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), finalX - mScroller.getFinalX(), 0, 800);//纠正指针位置
postInvalidate();
}
/**
* 处理手势抬起之后的操作
*/
private void dealActionUp(int moveX) {
int finalX = mScroller.getFinalX();//当前位置
//计算获取手指抬起之后要回到的位置
if (moveX >= 0 && finalX > mScaleWidth - mScaleRange / 2 + mInitDistance) {
finalX = (int) (mScaleWidth - mScaleRange / 2 + mInitDistance);
} else if (moveX <= 0 && finalX <= -mScaleRange / 2 + mInitDistance) {
finalX = -mScaleRange / 2 + mInitDistance;
} else {
int round = Math.abs(Math.round((float) finalX / mScaleMargin));
if (round % 5 > 2) {
if(finalX>0) {
if (finalX / mScaleMargin < round) {
finalX = finalX + (5 - round % 5) * mScaleMargin - finalX % mScaleMargin + mScaleMargin;
} else {
finalX = finalX + (5 - round % 5) * mScaleMargin - finalX % mScaleMargin;
}
}else{
if (finalX / mScaleMargin > -round) {
finalX = finalX + ( round % 5-5) * mScaleMargin - finalX % mScaleMargin - mScaleMargin;
} else {
finalX = finalX + (round % 5-5) * mScaleMargin - finalX % mScaleMargin;
}
}
} else {
if(finalX>0) {
if (finalX / mScaleMargin < round) {
finalX = finalX - (round % 5) * mScaleMargin + (mScaleMargin - finalX % mScaleMargin);
} else {
finalX = finalX - (round % 5) * mScaleMargin - finalX % mScaleMargin;
}
}else{
if (finalX / mScaleMargin > -round) {
finalX = finalX - (-round % 5) * mScaleMargin - finalX % mScaleMargin-mScaleMargin;
} else {
finalX = finalX - (-round % 5) * mScaleMargin - finalX % mScaleMargin;
}
}
}
}
mListener.onScaleScrollChanged(finalX/ mScaleMargin / 5+mInitPosition);//返回滚动选中的位置
mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), finalX - mScroller.getFinalX(), 0, 800);//纠正指针位置
postInvalidate();
}
private void smoothScrollBy(int dx, int dy) {
mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
postInvalidate();
}
5,传值回调方法
使用Scroller 必须实现的方法
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), 0);
postInvalidate();
}
}
public interface OnScrollListener {
void onScaleScrollChanged(int scale);
}
/**
* 初始化数据
* @param textList 刻度尺显示文字
* @param initPosition 初始位置
* @param listener 滚动监听
*/
public void bindDataAndListener(ArrayList textList,int initPosition,OnScrollListener listener){
mInitPosition=initPosition;
mTextList = textList;
mScaleWidth = (mTextList.size() * 5 - 5) * mScaleMargin;
mListener=listener;
}
/**
* 设置每个刻度间距
* @param margin 间距
* @return 返回当前view 链式编程
*/
public ScrollDividingRuleView setScaleMargin(int margin){
mScaleMargin=margin;
mRectHeight = (int) (mLineHeight +mTextSize+mTextLineMargin+20);
return this;
}
/**
* 设置文字和刻度线的间距
*/
public ScrollDividingRuleView setTextLineMargin(int textLineMargin){
mTextLineMargin=textLineMargin;
mRectHeight = (int) (mLineHeight +mTextSize+mTextLineMargin+20);
return this;
}
最后附上github 上的地址ScrollDividingRule